# PLAN ACTION CONCRET REFACTOR DOC Source: https://doc.fluximmo.io/PLAN_ACTION_CONCRET_REFACTOR_DOC # PLAN D'ACTION CONCRET — Refonte doc Fluximmo > Fichier d'**exécution** (≠ `RECOMMENDATION_IMPROVE_DOC.md` qui est la spec/recommandation). > Objectif : refondre `doc.fluximmo.io` en **6 phases** (dont 4 parallélisées). > Estimation totale : **8-12 jours-homme** (compressible à 4-6 jours calendaires si exécution parallèle agressive). *** ## Pré-requis (à valider avant lancement) | # | Pré-requis | Owner | Status | | - | ----------------------------------------------------------------------------------------------------- | --------- | ----------- | | 1 | **Clé API test gratuite** active sur `my.fluximmo.io` (1 semaine) — pour validation live des exemples | Tim | À fournir | | 2 | URL de base API publique confirmée (`https://api.fluximmo.io/v2/...` ou autre) | Tim | À confirmer | | 3 | Accès lecture aux 3 repos : `api_fluximmo_v2`, `elasticimmo`, `prod_fluximmo/v2/_workers` | OK | ✅ | | 4 | `mintlify` CLI installé localement (`npm i -g mintlify`) pour preview | Doc owner | À installer | | 5 | Clé API test pour validator agent (lecture seule) — peut être la même que #1 | Tim | À fournir | | 6 | Décision palette validée : indigo `#1E40AF` / emerald `#10B981` | ✅ | Acté | | 7 | Accès écriture sur le repo doc (branche `feature/refonte-doc-v2`) | Doc owner | OK | > ⚠️ **Sans pré-requis #1 + #2 + #5**, la **Phase 3 (validation live)** est bloquée. Les autres phases peuvent démarrer. *** ## Vue d'ensemble — 6 phases ``` Phase 1 ──> Foundation (mint.json, frontmatter, palette, snippets) [parallèle x3] ~1j Phase 2 ──> Concepts + Onboarding + Modèles [parallèle x3] ~1.5j Phase 3 ──> API Reference enrichie + EXEMPLES + VALIDATION LIVE [parallèle + V] ~2-3j ⚠️ critique Phase 4 ──> Playbooks (8 playbooks) [parallèle x4] ~2j Phase 5 ──> LLM-ready + Ressources + UI polish [parallèle x4] ~1.5j Phase 6 ──> QA finale (links + re-validation + journeys) [parallèle x3] ~0.5j ``` **Dépendances strictes** : * Phase 2 dépend de Phase 1 (structure mint.json en place) * Phase 3 dépend de Phase 2 (concepts en place pour cross-link) * Phase 4 dépend de Phase 3 (exemples validés réutilisés dans playbooks) * Phase 5 partiellement indépendant (peut démarrer pendant Phase 4) * Phase 6 dépend de tout *** ## Phase 1 — Foundation (parallèle x3, \~1 jour) **Goal** : poser une base saine (navigation, titres, brand) avant tout enrichissement. **Lancer en parallèle** (single message, 3 tool calls simultanés) : ### Agent 1.A — Refonte `mint.json` * **Mission** : appliquer la structure 5 sections **Démarrer / Concepts / Playbooks / API Reference / Ressources** (cf RECOMMENDATION §6). * **Actions** : * Réorganiser `navigation` selon §6 * Retirer entrées orphelines / doublons (mais **garder Analytics tel quel** per consigne — placeholder v3) * Retirer toute référence aux endpoints `/v1/*` (legacy supprimés de la doc publique) * Mettre à jour `colors` : primary `#1E40AF`, accent `#10B981`, gradient anchors * Ajouter `background.decoration: "gradient"` + colors light/dark * Activer `appearance.default: "system"` * Topbar : retirer `Réserver un appel` (Calendly), garder `Mon compte` + `Nous contacter` (email) * **Output** : `mint.json` propre, validé via `mintlify dev`. * **Acceptance** : 0 lien cassé, structure 5 sections visible en sidebar. ### Agent 1.B — Override frontmatter MDX OpenAPI (\~70 fichiers) * **Mission** : remplacer les titres auto-générés illisibles par des titres FR clairs. * **Actions** : pour chaque `.mdx` sous `api-v2-reference/`, `api-ai-reference/`, `api-geocoding-reference/` : * Ajouter frontmatter complet : ```yaml theme={null} --- title: "" sidebarTitle: "" description: "<1 phrase orientée cas d'usage>" icon: "" tag: "STABLE" | "BETA" | "DEPRECATED" openapi: "" --- ``` * **Convention icônes** : Properties = `home`, Adverts = `newspaper`, Alerts = `bell`, AI = `sparkles`, GEO = `map-pin`, Sample = `flask-conical`. * **Output** : tous les `.mdx` OpenAPI ont un frontmatter custom. * **Acceptance** : `grep -c '^title:'` sur chaque fichier MDX OpenAPI = 1. ### Agent 1.C — Snippets Mintlify réutilisables * **Mission** : créer les snippets centralisés pour les contenus dédupliqués. * **Actions** : créer `/snippets/` : * `decision-matrix-property-vs-advert.mdx` (le tableau §5 de la reco) * `cta-cle-test-gratuite.mdx` (footer : "Clé test gratuite 1 semaine sur my.fluximmo.io") * `banniere-llm-context.mdx` ("🤖 Page LLM-ready disponible") * `banniere-startups.mdx` (encart discret early-stage) * `warning-city-deprecated.mdx` (city ignoré → utiliser postalCode/inseeCode/department) * `warning-cycle-vie-alerte.mdx` (perte historique sur recreate) * **Output** : 6 snippets réutilisables. * **Acceptance** : chaque snippet rendu en standalone via `mintlify dev`. *** ## Phase 2 — Concepts + Onboarding + Modèles (parallèle x3, \~1.5 jour) **Goal** : poser les pages pédagogiques fondatrices (autonomie #1) avant d'enrichir les endpoints. **Dépend de** : Phase 1. ### Agent 2.A — Concepts (5 pages) * `concepts/property-vs-advert.mdx` — decision matrix (snippet) + diagramme Mermaid + cas d'usage * `concepts/webhooks.mdx` (refonte) — archi, IDs vs payload complet, samples webhook live, Mermaid pipeline * `concepts/match-types-cycle-alerte.mdx` — `CREATED` / `MERGED` / `ADVERT_EVENT`, **perte historique sur recreate** (warning gras), pas de backfill, events `PRICE`/`REPUBLISHED`/`UNPUBLISHED` * `concepts/filtres-communs.mdx` — `location` / `offer` / `price` / `habitation` / `meta.isTotallyOffline` + warning `city` ignoré (snippet) * `concepts/recherche-geographique.mdx` — 4 modes (`postalCode`/`inseeCode`/`department` admin + `geoBoundingBox` + `geoDistance` + `geoShape` + multi-zones OR), contraintes (cf RECOMMENDATION §C.1), anti-patterns * **Style** : exemples > texte. Mermaid systématique. ### Agent 2.B — Onboarding (4 pages) * `introduction.mdx` (réécrite) — vision FR-first depuis 2017, **0 mention proéminente Calendly**, lien samples no-auth + clé test gratuite * `demarrer/quickstart.mdx` — 5 min sans appel : sample → clé test → 1er search (curl + Python + Node via ``) * `demarrer/sample-data.mdx` — valoriser les 4 endpoints `/v2/sample/*` (no-auth) + bouton "Try it" Mintlify activé * `authentification.mdx` (réécrite) — clé test gratuite via my.fluximmo.io + clé prod, mention discrète email contact * **Acceptance** : un lecteur sans clé peut atteindre un sample no-auth en ≤ 2 clics. ### Agent 2.C — Modèles Property / Advert * `api-v2-reference/_api-models/property.mdx` — schéma champs + descriptions + warning `city` + `meta.isTotallyOffline` clé prod + lien decision matrix (snippet) * `api-v2-reference/_api-models/advert.mdx` — schéma champs + différences avec property + lien decision matrix * **Source** : DTOs réels dans `api_fluximmo_v2/src/modules/app/dto/` (cf RECOMMENDATION §C.7). *** ## Phase 3 — API Reference enrichie + EXEMPLES + VALIDATION LIVE (parallèle + Validator, \~2-3 jours) **Goal** : enrichir chaque endpoint Search/Alerts/AI/GEO avec prose + exemples curated, **et valider chaque exemple PROPERTIES search live contre l'API**. **Dépend de** : Phase 1 + Phase 2. **Bloquant** : pré-requis #1, #2, #5. ### Agent 3.A — Enrichir Search Properties (full + lite + similar) Ajouter prose au-dessus de l'OpenAPI block + section **Exemples** avec **6 exemples curated** : 1. ★★ **Combo standard** : `postalCode` Paris achat `[FLAT, HOUSE]` (pas PROGRAM), prix 100-350k€, surface 30-110m², `isTotallyOffline=false`, sort `FIRST_SEEN_AT DESC`, size 100 2. Recherche par `department` (ex: 13) location maison surface ≥ 80m² 3+ chambres 3. ★ `geoDistance` rayon 20 km autour d'une adresse géocodée 4. ★ `geoBoundingBox` carte interactive Paris 5. ★ `geoShape` POLYGON quartier custom (Le Marais) 6. Multi-zones via `location: [{postalCode}, {postalCode}, ...]` Pagination cursor `searchAfterHash` documentée. ### Agent 3.B — Enrichir Search Adverts + tous les Alerts * **Search Adverts** : 4 exemples (achat appart dpt, geoDistance autour métro, seller/agence, propertyFlxId) * **Alerts ADVERT CREATE** : 3 exemples (chasseur dpt simple, +events `ADVERT_EVENT`, multi-dpt) * **Alerts PROPERTIES CREATE** : 3 exemples * Combo standard `match=[CREATED]` (défaut) * Dpt + isTotallyOffline=false `match=[CREATED]` * **`match=[CREATED, MERGED]` en exemple AVANCÉ uniquement** (ne pas mettre en avant) * **Tous les autres alerts** (LIST, GET, PATCH, DELETE, ACTIVATE, HISTORY) avec prose courte + cas d'usage ### Agent 3.C — Enrichir AI / GEO / Sample * **AI** : `estimate/property/lite` + `experimental/estimate` — input/output, exemples appart Paris + maison province * **GEO** : préciser **origine BAN/BANO Gouv** (pas proprio Fluximmo) + exemples search/reverse/CSV * **Sample** : valoriser les 4 endpoints, "Try it" actifs ### ⚠️ Agent 3.V — VALIDATOR (CRITIQUE — lancé après 3.A et 3.B) **Mission CRITIQUE** : pour **chaque exemple PROPERTIES search** créé en Phase 3.A et 3.B, exécuter live + vérifier que les résultats respectent les filtres. **Procédure par exemple** : 1. Parser le payload JSON depuis le `.mdx` 2. `POST` vers `https://api.fluximmo.io/v2/protected/properties/search` (header `x-api-key`) 3. Récupérer la réponse (jusqu'à `size` résultats) 4. **Vérifier filtre par filtre** que chaque résultat respecte les contraintes (cf Annexe A) 5. Émettre rapport `__validation_reports/.md` 6. **Bloquer le merge si ≥ 1 exemple échoue** **Output** : dossier `__validation_reports/` à la racine doc : * 1 rapport markdown par exemple * 1 rapport agrégé `_summary.md` **Acceptance** : 100% des exemples PROPERTIES retournent ≥ 1 résultat ET passent toutes les vérifications de filtre. > 💡 **Pourquoi un validator dédié** : c'est la consigne explicite. Détecte en amont les exemples mal formés (impossible en review humain pur) et révèle de potentiels bugs API. *** ## Phase 4 — Playbooks (parallèle x4, \~2 jours) **Goal** : 8 playbooks orientés cas d'usage, réutilisant les exemples validés Phase 3. **Dépend de** : Phase 3 (exemples validés disponibles). **Lancer 4 agents en parallèle, chacun produit 2 playbooks** : ### Agent 4.A * `playbooks/alerte-advert-departement.mdx` — chasseur immo (use case #1) * `playbooks/search-properties-analytique.mdx` — analytics Paris achat (use case #2) ### Agent 4.B * `playbooks/recherche-geographique-avancee.mdx` — bbox / distance / polygon / multi-zones (★ feature distinctive) * `playbooks/replicate-bdd-adverts.mdx` — sync continue marketplace ### Agent 4.C * `playbooks/track-price-changes.mdx` — events `PRICE` / `REPUBLISHED` / `UNPUBLISHED` (alerte ADVERT) * `playbooks/estimer-un-bien.mdx` — AVM AI lite ### Agent 4.D * `playbooks/geocoder-pour-search.mdx` — chaîne BAN/BANO → search Fluximmo * `playbooks/webhook-volume-architecture.mdx` — RabbitMQ / SQS, idempotence, ack rapide, retry/DLX **Format de chaque playbook** : 1. **Goal** (1 phrase, orienté outcome client) 2. **Scénario** (qui je suis, ce que je veux) 3. **Étapes** (`` Mintlify) avec curl + Python + Node (``) 4. **Diagramme Mermaid** du flow 5. **Pièges fréquents** (``) 6. **Liens** vers concepts + endpoints API *** ## Phase 5 — LLM-ready + Ressources + UI polish (parallèle x4, \~1.5 jour) **Goal** : finaliser autonomie #2 (LLM) + ressources support + polish visuel. **Peut démarrer en parallèle de Phase 4** (pas de dépendance forte). ### Agent 5.A — Page LLM-ready * `llms-context.mdx` — manuel (cf RECOMMENDATION §7.27.A) : * Identité Fluximmo 3 lignes * Decision matrix condensée * Schémas TS-like des DTOs (FilterProperty, FilterAdvert, FilterLocation, FilterPrice, FilterTypeHabitation, FilterMeta, SaveSearchPropertiesPayload, SaveSearchAdvertsPayload) * Enums littéraux * Limites strictes (size 25/100, max keywords 20, distance > 0) * Match types & events * 8 archetypes payloads JSON minifiés (issus de Phase 3 **validés**) * Anti-patterns (city ignoré, GeoJSON \[lon,lat] vs API , pas de backfill) * Cible : 2 000–4 000 tokens * Activer `llms.txt` + `llms-full.txt` natifs Mintlify dans `mint.json` ### Agent 5.B — Ressources (5 pages) * `ressources/faq.mdx` — top 15 questions (collecter auprès du support) * `ressources/codes-erreur.mdx` — codes 10001/10002/10003 + format réponse erreur * `ressources/webhooks-livraison.mdx` — `x-webhook-key`, retry 2+10×5min, DROP après \~50min, codes 200-205, SLO 30-50s * `ressources/bonnes-pratiques.mdx` — pagination, retry, idempotence, refetch, validation webhook par x-webhook-key * `ressources/limites-rate-limits.mdx` — quotas (neutre, sans surpromettre) ### Agent 5.C — Pages stratégiques (3 pages) * `concepts/dvf-vs-fluximmo.mdx` — comparatif honnête + DVF dispo sur demande dès aujourd'hui * `api-reference/capabilities-sur-demande.mdx` — DPE, similar, market analytics, properties analytics, agences, consumption (descriptif, no OpenAPI) * `ressources/startups-et-sur-mesure.mdx` — discount sans %, qualif sur demande, **0 noms clients publics**, études de cas anonymisées OK ### Agent 5.D — UI polish * Confirmer palette `#1E40AF` / `#10B981` dans `mint.json` (raffinage Phase 1) * Ajouter Mermaid diagrammes (style **Default Mintlify**) dans : * `concepts/webhooks.mdx` * `concepts/match-types-cycle-alerte.mdx` * `concepts/recherche-geographique.mdx` * `playbooks/replicate-bdd-adverts.mdx` * `playbooks/webhook-volume-architecture.mdx` * Activer API playground sur les **4 sample endpoints uniquement** * Vérifier dark mode lisible sur toutes les pages *** ## Phase 6 — QA finale (parallèle x3, \~0.5 jour) **Goal** : validation cross-platform + zéro régression avant release. **Dépend de** : tout. ### Agent 6.A — Link checker + structure * `mintlify dev` lancé → vérifier 0 lien cassé * Vérifier que `mint.json` valide tous les paths référencés * Recompter les `.mdx` orphelins (objectif : 0) * Test Lighthouse sur 5 pages représentatives → Performance ≥ 90, Accessibility ≥ 95 ### Agent 6.B — Re-validation exemples PROPERTIES * **Re-exécuter** l'agent 3.V VALIDATOR sur **tous** les exemples PROPERTIES (incluant ceux dans les playbooks et `llms-context.mdx`). * Garantit zéro régression depuis Phase 3. ### Agent 6.C — User journeys cold-start Simuler 3 parcours : 1. **Découverte sans clé** : depuis `/`, atteindre un sample no-auth en ≤ 2 clics → succès copy-paste curl 2. **Quickstart 5 min** : suivre `demarrer/quickstart.mdx` from scratch sur clé test → premier search OK 3. **Génération payload via LLM** : copier `llms-context.mdx` dans Claude/ChatGPT, demander "alerte ADVERT achat appartement département 75 prix max 600k€" → vérifier que le LLM produit un payload valide (re-validate via agent 3.V) **Acceptance globale (release-ready)** : * ✅ 0 lien cassé * ✅ 100% des exemples PROPERTIES validés (Phase 3 + Phase 6) * ✅ Les 3 user journeys passent * ✅ Lighthouse Performance ≥ 90, Accessibility ≥ 95 * ✅ 0 mention proéminente Calendly avant la 3ᵉ page profondeur * ✅ Decision matrix Property vs Advert accessible en ≤ 2 clics *** ## Récap effort + parallélisation | Phase | Agents parallèles | Durée séquentielle | Durée si parallèle | Bloquant | | --------- | ----------------- | ------------------ | ---------------------- | ---------------------------- | | 1 | 3 | 3j | **1j** | Pré-req #4, #6 | | 2 | 3 | 4.5j | **1.5j** | Phase 1 | | 3 | 3 + Validator | 7j | **2-3j** | Phase 2 + Pré-req #1, #2, #5 | | 4 | 4 | 8j | **2j** | Phase 3 | | 5 | 4 | 6j | **1.5j** | Phase 2 (partiel) | | 6 | 3 | 1.5j | **0.5j** | Tout | | **Total** | — | **30j** | **\~8.5j calendaires** | — | > Compression possible : Phase 5 démarre en chevauchement avec Phase 4 → **5-6j calendaires** réalistes. *** ## Critère "stop the line" Bloquer la progression si : * Phase 3 Validator détecte ≥ 1 exemple PROPERTIES qui échoue → corriger l'exemple ou remonter un bug API * Lighthouse Accessibility \< 90 sur une page → corriger avant Phase suivante * Un user journey échoue en Phase 6 → ne pas release *** ## Annexe A — Spec technique de l'agent VALIDATOR (Phase 3.V & 6.B) **Inputs** : * Liste des exemples à valider (extraits depuis tous les `.mdx` — chercher les blocks ` ```json ` à but "exemple search PROPERTIES") * Clé API test (`FLUXIMMO_TEST_API_KEY`) * Base URL API (`FLUXIMMO_API_BASE_URL`) **Pour chaque exemple** : 1. Parser le payload JSON 2. ``` curl -X POST "$BASE/v2/protected/properties/search" \ -H "x-api-key: $KEY" \ -H "Content-Type: application/json" \ -d "$payload" ``` 3. Parser `response.results[]` 4. Pour chaque result, vérifier filtre par filtre selon mapping ci-dessous 5. Émettre rapport markdown **Mapping filtre → vérification** (à coder en Python avec `shapely` pour la géométrie) : | Filtre payload | Vérification sur chaque result | | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `filterProperty.location[].postalCode` | `result.location.postalCode == filter` | | `filterProperty.location[].inseeCode` | `result.location.inseeCode == filter` | | `filterProperty.location[].department` | `result.location.department == filter` | | `location[].locationCoordinate.location.geoDistance` | `plane_distance(filter.pin, result.location) <= filter.distanceKm` (DistanceType=Plane côté code, **pas Haversine**) | | `location[].locationCoordinate.location.geoBoundingBox` | `topLeft.lat ≥ result.lat ≥ bottomRight.lat` ET `topLeft.lon ≤ result.lon ≤ bottomRight.lon` | | `location[].locationCoordinate.location.geoShape` POLYGON | `point_in_polygon(result.lat, result.lon, filter.coordinates)` (shapely ou ray-casting) | | `filterProperty.offer[].type` | `result.offer.type ∈ filter` | | `filterProperty.price.initial.value.{min,max}` | `filter.min ≤ result.price.initial.value ≤ filter.max` | | `filterProperty.habitation.surface.total.{min,max}` | `filter.min ≤ result.habitation.surface.total ≤ filter.max` | | `filterProperty.habitation.bedroomCount.{min,max}` | `filter.min ≤ result.habitation.bedroomCount ≤ filter.max` | | `filterProperty.type[]` | `result.type ∈ filter` | | `filterProperty.meta.isTotallyOffline` | `result.meta.isTotallyOffline == filter` | **Multi-zones** (`location` est un array de N>1 entries) : → `result.location` matche **AU MOINS UNE** des entries (OR logic, cf RECOMMENDATION §C elasticimmo `bool.should`) **Output** : * `__validation_reports/.md` (1 par exemple) * `__validation_reports/_summary.md` (agrégé : passes / fails / warnings) * Exit code != 0 si ≥ 1 fail (pour bloquer CI/merge) *** ## Pour démarrer l'exécution Une fois ce plan validé : 1. Créer la branche `feature/refonte-doc-v2` sur le repo doc 2. Confirmer pré-requis #1 + #2 + #5 (clé API test + URL base) 3. Lancer Phase 1 (3 agents en parallèle dans un seul message Agent) 4. Attendre Phase 1 → lancer Phase 2 (3 agents) 5. Etc. > Référence spec complète : `RECOMMENDATION_IMPROVE_DOC.md` à la racine du repo doc. # RECOMMENDATION IMPROVE DOC Source: https://doc.fluximmo.io/RECOMMENDATION_IMPROVE_DOC # RECOMMENDATION\_IMPROVE\_DOC — Plan de refonte doc Fluximmo > Document destiné à devenir `/RECOMMENDATION_IMPROVE_DOC.md` à la racine du repo une fois validé. > Audience : Tim (CEO/Tech Fluximmo) + équipe doc/produit. > Objectif : refonte UX/DX de la doc Mintlify `doc.fluximmo.io`. > > ⚠️ **Scope de cette session** : production UNIQUEMENT du fichier `RECOMMENDATION_IMPROVE_DOC.md` à la racine. **Aucune modification des `.mdx`** ni de `mint.json` n'est entreprise dans cette session — la refonte effective sera planifiée en sprints séparés (cf §11). *** ## 1. Contexte Fluximmo est un acteur historique (depuis 2017) de l'API d'intelligence immobilière FR. La doc actuelle souffre de friction d'onboarding (3 mentions Calendly), d'absence de quickstart/playbooks/FAQ, de nombreux fichiers `.mdx` orphelins, et de pages API référence quasi-vides (1 ligne `openapi:` ref). Les concepts critiques (Property vs Advert, cycle de vie alerte, match types) sont sous-documentés ou éparpillés. **Objectifs primaires de la refonte :** 1. **Autonomie totale** : un client doit pouvoir intégrer l'API de bout en bout **sans appel** ni email, juste avec la doc + sample no-auth + clé test gratuite. Métrique : ratio "tickets posés vs comptes créés". 2. **Compatibilité LLM** : en 2026 la majorité des clients passent par ChatGPT/Claude pour générer leurs payloads. Vu la complexité (filtres nested, geo, match types), les LLMs échouent souvent → les clients accusent l'API. Nouvelle exigence : **page `llms-context.mdx` synthétique copiable/collable** + activation du standard `llms.txt`/`llms-full.txt` Mintlify. 3. **Supériorité perçue** : design premium face à Casafari/Melo, identity visuelle modernisée (couleurs, hero, illustrations, diagrammes). 4. **DX par l'exemple** : privilégier exemples concrets > texte brut. Pas d'usine à gaz. ## 2. État des lieux (synthèse) | Domaine | Constat | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | **Navigation** | 75 .mdx, \~61 orphelins/non référencés, doublons (`market-analytics` x2, `properties-analytics` x2, `estimations` x2) | | **Onboarding** | 0 quickstart, 0 playbook, 0 FAQ, 0 troubleshooting, 0 errors, 0 rate-limits, 0 changelog | | **Friction** | 3 mentions Calendly (intro, auth, baas). Clé test gratuite my.fluximmo.io non mentionnée. Sample endpoints (no-auth) enterrés | | **Concepts** | Property vs Advert expliqué partiellement. Cycle de vie alerte (perte historique sur recreate) jamais évoqué. Match types non détaillés | | **API Reference** | OpenAPI exhaustif côté swagger, mais .mdx minimalistes — aucune prose, aucun exemple, aucun lien croisé | | **AI / GEO** | 16 endpoints quasi non documentés en prose | | **Composants Mintlify** | Sous-utilisés : 0 Card/CardGroup, 0 Tabs, 0 CodeGroup, 0 Steps, 0 Mermaid, 0 Warning | ## 3. Décisions confirmées (Q\&A) | Sujet | Décision | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Calendly / appel intro | Mention discrète conservée + CTAs Mintlify gardés. Mais retirer des intros + valoriser self-serve | | Personas cibles | 4 : chasseurs (alertes ADVERT temps réel), marketplaces (réplication BDD), analytics/fintech (search PROPERTIES), AVM (estimations) | | Scope endpoints | Search/Alerts, Sample/Auth/Quickstart, AI, GEO, et "Sur demande" (capabilities descriptives) | | Structure | 5 sections : **Démarrer / Concepts / Playbooks / API Reference / Ressources** | | Match types (vérité code) | **Adverts** : `ALERT_MATCH_CREATED` (nouvelle advert matchée 1x) + `ALERT_MATCH_ADVERT_EVENT` (event sur advert historiquement matchée) · **Properties** : `ALERT_MATCH_CREATED` + `ALERT_MATCH_MERGED` (= property existante mise à jour, nom legacy au niveau worker = `ALERT_PROPERTIES_MATCH_UPDATED`) · `UNKNOWN` legacy. **Pas de backfill à la création.** | | Endpoints sur demande | Page unique **Capabilities** descriptive (pas de schéma OpenAPI exposé) | | Langue | FR uniquement | | Exemples payloads | Mix : webhook live `/v2/sample/webhook/*` + request bodies curated par cas d'usage | | Doc style | Privilégier exemples > texte brut. Pas d'usine à gaz. Tabs/CodeGroup/Cards systématiquement | ## 4. User journeys cibles Avant de penser endpoints, on pense parcours utilisateur. 8 journeys prioritaires : 1. **Découverte sans clé** → samples no-auth + visualisation Property/Advert/webhook payload 2. **Onboarding 5 min** → clé test gratuite my.fluximmo.io → premier appel curl → première recherche 3. **Alerte ADVERT temps réel** → créer alerte achat appartement département → recevoir webhook → traiter events (prix, dépub/repub) 4. **Recherche analytique PROPERTIES** → filtrer Paris achat appartement avec `meta.isTotallyOffline=false` + range prix m² + paginer par cursor 5. **★ Recherche géographique avancée** → filtrer par `geoBoundingBox` (carte), `geoDistance` (rayon autour d'un point), polygone GeoJSON (zone dessinée), polygones administratifs (IRIS, canton, commune…) 6. **Réplication BDD adverts** → alerte ADVERT large + persistance locale + sync continue via events 7. **Estimation d'un bien** → AI lite estimation → exploitation des price ranges 8. **Géocodage** → résoudre une adresse FR → utiliser l'output dans un filtre `location` search Chaque journey aura son **playbook** dédié (cf §7). ## 5. Décision matrix — ADVERT vs PROPERTY | Critère | ADVERT | PROPERTY | | ------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | **Définition** | Annonce individuelle sur un portail (1 URL = 1 advert) | Bien physique unique = agrégation de plusieurs adverts (déduplication multi-portails) | | **Identifiant** | `flxId` (advert) + `propertyFlxId` (parent) | `flxId` (property) + `adverts[]` (constituants) | | **API Search** | `POST /v2/protected/adverts/search` — flux brut, doublons inter-portails | `POST /v2/protected/properties/search` — vue unique du bien, déduplication | | **Filtre clé spécifique** | `isOnline`, `isPro`, `isExclusive`, `source`, `seller[]` | `meta.isTotallyOffline`, filtre nested `adverts.*` | | **Webhook payload** | **Payload COMPLET** de chaque advert + events | **IDs uniquement** → refetch obligatoire via `GET /v2/protected/properties/{flxId}` | | **Events supportés** | ✅ OUI : changement prix, online/offline, republication (`ALERT_MATCH_ADVERT_EVENT`) | ❌ NON : seulement `CREATED` et `MERGED` (fusion ex-post) | | **Cas d'usage idéal** | Monitoring temps réel, alertes chasseurs, réplication BDD, tracking source-par-source | Recherche analytique, déduplication, valorisation, market intel | | **Anti-pattern** | Utiliser pour analytics agrégés (doublons inter-portails) | Utiliser pour suivre les changements de prix d'une annonce précise | | **Coût/perf** | Volume plus élevé (toutes les annonces) | Volume réduit, fetch supplémentaire si webhook | > ⚠️ **Cycle de vie alerte (concept critique non documenté aujourd'hui)** : les events d'une alerte ne couvrent QUE les adverts historiquement matchées par cette même alerte. **Supprimer + recréer une alerte = perte d'historique** (l'alerte recréée ne recevra plus les events des annonces matchées par l'ancienne). À documenter en gras. > ⚠️ **Pas de backfill à la création** (vérifié dans les workers) : une alerte ne match QUE les adverts/properties traitées **après** sa création. Les annonces antérieures qualifiantes ne seront jamais émises. → Toujours expliquer comment combiner `search` (one-shot pour le rétro) + `alert` (continuous). > ⚠️ **Events ADVERT exhaustifs réels** (workers) : `PRICE` (toute variation, même 1 €), `REPUBLISHED` (status passe à online après offline), `UNPUBLISHED` (status passe à offline). `CHECK` (vérification sans changement) ne déclenche pas de webhook. ## 6. Navigation Mintlify proposée ``` Démarrer ├── Introduction (vision, depuis 2017, FR-first, supériorité) ├── Quickstart (5 min, sans appel) ├── Données d'exemple (sample, no-auth) ★ valorisé ├── Authentification (clé test gratuite + clé prod) └── Limites & rate limits Concepts principaux ├── Property vs Advert (decision matrix) ├── Webhooks (architecture, retry, signature, déploiement) ├── Filtres communs (location, offer, price, habitation…) ├── Match types & cycle de vie d'une alerte ★ critique └── BaaS — vue d'ensemble des moteurs Playbooks ├── Recevoir une alerte ADVERT par département ├── Rechercher des PROPERTIES analytiques (Paris, achat appartement) ├── Répliquer la BDD adverts chez le client ├── Suivre les changements de prix en temps réel ├── Estimer un bien via l'API AI └── Géocoder une adresse pour filtrer une recherche API Reference ├── Search & Alerts │ ├── Properties (search, search lite, alerts CRUD) │ └── Adverts (search, alerts CRUD, get by id/url, raise issue) ├── AI (estimations lite + experimental) ├── GEO (search, reverse, batch sync, batch async) ├── Modèles │ ├── Property │ └── Advert └── Capabilities sur demande (DPE, DVF prochainement, similar, market analytics, properties analytics, agences, consumption) Ressources ├── FAQ ├── Codes d'erreur ├── Bonnes pratiques (pagination, retry, idempotence, refetch) ├── Changelog └── Contact ``` > Onglets Mintlify (`tabs`) à conserver pour navigation rapide par domaine, mais avec la sidebar au-dessus comme source de vérité. ## 7. Recommandations par endpoint (Search & Alerts) Pour chaque endpoint : **but business · ce qu'il faut documenter · exemples de payloads à créer (filtres, pas JSON) · priorité**. ### 7.1 `POST /v2/protected/properties/search` * **But** : recherche analytique avec déduplication (1 ligne = 1 bien physique). * **À documenter** : tous les filtres (`location` avec ses 4+ modes géographiques — cf §7.27 ★, `offer`, `price`, `habitation`, `land`, `parking`, `type`, `meta.isTotallyOffline`, `meta.firstSeenAt/lastSeenAt`, `tags`, `process`, `hasAnomaly`, `isUrgent`, nested `adverts.*`) ; pagination cursor (`searchAfterHash`) ; sortBy (5 valeurs) + orderBy ; `count` semantics ; usage typique vs `lite`. * **Exemples prod-driven (cf Annexe D)** — ordre par fréquence d'usage réel : 1. ★★ **Combo standard dominant** : Paris (`postalCode` ou `inseeCode`, **pas `city` qui est ignoré**) achat appartement, types `[FLAT, HOUSE, PROGRAM]`, prix 100-350k€, surface 30-110m², 1-3 chambres, `isTotallyOffline=false`, sort `FIRST_SEEN_AT DESC`, size 100 2. ★ Avec `geoDistance` rayon 20 km autour d'une adresse géocodée (20% prod, cas investisseur) 3. Variante achat avec range prix m² (ex: Paris 8-15k€/m²) 4. Bouches-du-Rhône location maison surface ≥ 80 m² 3+ chambres (cas exhaustivité non-prod) 5. ★ Investisseur terrains : combo standard + filtre `land` 6. ★ `geoBoundingBox` carte interactive (feature distinctive sous-utilisée à promouvoir) 7. `geoShape` POLYGON quartier custom (feature distinctive) 8. Multi-zones via `location: [...]` (Paris + 92/93/94) 9. Pagination cursor `searchAfterHash` sur 5 000 résultats * **Priorité** : **P0** ### 7.2 `POST /v2/protected/properties/search/lite` * **But** : variante allégée, perf plus rapide, schéma de réponse réduit. * **À documenter** : différences vs full (filtres absents : `adverts` nested, `parking`, `process`, `tags`, `hasAnomaly`, `isUrgent`) ; quand utiliser (UI temps réel, mobile, autocomplete). * **Exemples** : 1 même cas que full pour comparer · 1 cas mobile/autocomplete. * **Priorité** : **P1** ### 7.3 `POST /v2/protected/properties/search/lite/similar` * **But** : trouver des biens similaires (à clarifier : similar à un flxId ? à un payload ?). * **À documenter** : input attendu, distance/scoring, cas d'usage (recommandation, AVM peer comparison). * **Exemples** : 1 cas peer comparison. * **Priorité** : **P3** (sur demande / capability) ### 7.4 `POST /v2/protected/adverts/search` * **But** : recherche brute annonces, avec filtres source-spécifiques. * **À documenter** : filtres communs + spécifiques adverts (`isOnline`, `isPro`, `isExclusive`, `isAuction`, `source`, `seller[]`, `firstSeenAt`, `lastModifiedAt`, `propertyFlxId`, `mainPropertyAdFlxId`, `scope`, `currency`) ; même `location` riche (geoBoundingBox/geoDistance/polygon) — cf §7.27 ★ ; pagination + sorting ; quand préférer à properties search. * **Exemples** : 1. Achat appartement département (idem playbook chasseur) 2. ★ `geoDistance` rayon autour d'une station de métro (chasseur urbain) 3. Adverts d'un seller spécifique (agence) 4. Adverts d'une property donnée (`propertyFlxId`) 5. Adverts pro vs particulier (`isPro`) * **Priorité** : **P0** ### 7.5 `PUT /v2/protected/adverts/search/alerts` (CREATE) * **But** : créer une alerte ADVERT → réception webhook temps réel. * **À documenter** : body (`search_query` = même schéma que search · `webhook_url` · `match` enum · `search_name` · `context`) ; comportement à la création (back-fill ? non, uniquement nouveaux matches) ; ⚠️ **les events ne s'appliquent qu'aux adverts historiquement matchées** ; activate/deactivate vs delete (perte historique). * **Exemples** : 1. Achat appartement département `match=ALERT_MATCH_CREATED` (use case #1) 2. Achat + events `match=ALERT_MATCH_ADVERT_EVENT` 3. Multi-départements via array 4. Range prix + surface + chambres * **Priorité** : **P0** ### 7.6 `PUT /v2/protected/adverts/search/alerts/many` (BULK CREATE) * **But** : créer N alertes en un appel. * **À documenter** : limite max, atomicité, cas d'usage (multi-territoires). * **Exemples** : 1 cas multi-départements. * **Priorité** : **P2** ### 7.7 `GET /v2/protected/adverts/search/alerts/all` (LIST) * **À documenter** : pas de pagination visible, format réponse. * **Priorité** : **P1** ### 7.8 `GET /v2/protected/adverts/search/alerts/{alertId}` (READ) * **À documenter** : utilité (réinspecter config), réponse complète. * **Priorité** : **P2** ### 7.9 `PATCH /v2/protected/adverts/search/alerts/{alertId}` (UPDATE) * **À documenter** : ⚠️ **modifier l'alerte conserve l'historique** (≠ delete + recreate), champs modifiables. * **Exemples** : 1 changement webhook url · 1 ajout filtre prix. * **Priorité** : **P1** ### 7.10 `DELETE /v2/protected/adverts/search/alerts/{alertId}` (DEACTIVATE) * **À documenter** : sémantique réelle (deactivate vs hard delete) ; impact sur historique events. * **Priorité** : **P1** ### 7.11 `GET /v2/protected/adverts/search/alerts/{alertId}/activate` (REACTIVATE) * **À documenter** : pourquoi GET (legacy), comportement post-réactivation (events repris ?). * **Priorité** : **P2** ### 7.12 `GET /v2/protected/adverts/search/alerts/history/{flxId}` (HISTORY) * **À documenter** : que retourne ce endpoint ? historique des matches d'une alerte ; pagination offset (skip/take, ⚠️ incohérence vs cursor du search) ; cas d'usage (rejouer un backlog). * **Exemples** : 1 fetch backlog 30 derniers jours. * **Priorité** : **P1** ### 7.13–7.20 Properties alerts (CREATE, MANY, LIST, GET, PATCH, DELETE, ACTIVATE, HISTORY) * **Structure miroir** des adverts alerts. Même grille de doc. * **Différence clé à mettre en gras** : webhook = IDs only · pas d'events sur changement de prix · `match` enum = `CREATED | MERGED | UNKNOWN` (pas `ADVERT_EVENT`). **Combo prod par défaut : `match=[CREATED, MERGED]`**. * **Exemples spécifiques (cf Annexe D — patterns prod)** : 1. ★★ **Combo standard 88% prod** : alerte ville+CP achat `[FLAT, HOUSE, PROGRAM]` prix 100-350k€ `isTotallyOffline=false` `match=[CREATED, MERGED]` 2. Properties alert sur département + `meta.isTotallyOffline=false` + refetch IDs reçus 3. Use case fintech : alerte properties analytics (Paris achat) → fetch périodique pour stats * **Priorité** : **P0** (CREATE + LIST), **P1** (UPDATE, DELETE, HISTORY), **P2** (autres) ### 7.21 `GET /v2/protected/adverts/{flxId}` & `/v2/protected/adverts/by-url` * **But** : refetch unitaire d'une advert. * **À documenter** : codes 404 (advert supprimée du portail), différence by-id vs by-url. * **Priorité** : **P1** ### 7.22 `GET /v2/protected/properties/{flxId}` & `/by-url` & `/bulk` & `/by-advert` * **But** : refetch après webhook properties (IDs only). * **À documenter** : `bulk` = batch fetch (taille max, perf) ; quand utiliser `by-advert` ; lien direct webhook → refetch. * **Exemples** : 1 batch fetch 50 IDs après webhook · 1 cas by-advert. * **Priorité** : **P0** (bulk + by-id), **P1** (autres) ### 7.23 Sample endpoints (`GET /v2/sample/properties|adverts|webhook/properties|webhook/adverts`) * **But** : visualisation publique sans auth. * **À documenter** : ★ **mettre en avant fortement** dans Démarrer ; usage : tester structure avant d'avoir une clé, debug local, fixtures de tests. * **Priorité** : **P0** ### 7.24 AI : `POST /v2/protected/estimate/property/lite` & `/v2/protected/experimental/estimate` * **À documenter** : input (location, habitation, type, offer), output (price, priceRange, confidence/metadata), différence lite vs experimental, cas d'usage AVM. * **Exemples** : 1 estimation appartement Paris · 1 estimation maison province. * **Priorité** : **P1** ### 7.25 GEO : `/search`, `/reverse`, `/search/csv`, `/reverse/csv`, async projects * **Nature** : ⚠️ **C'est le BAN / BANO du gouvernement français** (api-adresse.data.gouv.fr) que Fluximmo déploie/proxy — **pas une brique propriétaire**. À assumer franchement dans la doc : "service public BAN/BANO opéré pour vous, sans rate limit gouv". Évite de survendre. * **À documenter** : origine BAN/BANO (transparence + crédibilité) ; usage typique (résoudre adresse → injecter dans `location` search Fluximmo) ; index (address, parcel, poi) ; format GeoJSON ; batch CSV sync vs async ; lien externe vers la spec officielle BAN. * **Exemples** : 1 search "Tour Eiffel" · 1 reverse via lat/lon · 1 batch CSV 10k adresses. * **Priorité** : **P1** ### 7.26 Capabilities sur demande (DPE/DVF, similar, market analytics, properties analytics, agences, consumption) * **Page descriptive unique** : présenter la capability + valeur métier + CTA "Activer" (email contact). * **Pas d'exposition OpenAPI**. * **Priorité** : **P2** ### 7.26.A ★ Page comparative "DVF vs Fluximmo" * **Pourquoi** : DVF (gouv) est l'alternative gratuite la plus citée par les startups qui hésitent. Une page comparative honnête désamorce la question avant qu'elle ne devienne un blocage commercial. * **Contenu cible** : * **DVF — forces** : prix de vente **réels** (pas de prix annonce), géolocalisation parcellaire précise, gratuit, donnée notariale fiable. * **DVF — limites** : **aucun temps réel** (publication trimestrielle avec lag de 6+ mois), **pas de caractéristiques détaillées** du bien (pas de photos, pas de description, pas de filtres surface/chambres/équipements précis), pas de webhooks, pas de cycle de vie de l'annonce. * **Fluximmo — forces** : temps réel (webhooks adverts/properties), caractéristiques riches (habitation, climat, équipements, photos, full-text), événements (PRICE/REPUBLISHED/UNPUBLISHED), filtres avancés et géo riche, multi-portails dédupliqués. * **Fluximmo — limites** : prix = prix annonce (pas prix de vente notarial), couverture marketplace (ce qui est publié uniquement). * **Quand choisir lequel** : matrice "Si tu veux X → utilise Y". * **Combinaison gagnante** : Fluximmo (temps réel + caractéristiques) + DVF (prix de vente notariaux historiques) = stack complet. → **DVF est dès aujourd'hui disponible chez Fluximmo en capability sur-demande** : un seul fournisseur, un seul contrat, pas de double intégration. (Endpoints OpenAPI publics arriveront ensuite.) * CTA : "Activer DVF sur votre clé" → email contact. * **Format** : tableau comparatif côte-à-côte + diagramme Mermaid de complémentarité + 1-2 cas d'usage concrets ("AVM" / "compliance scoring"). * **Path** : `concepts/dvf-vs-fluximmo.mdx` * **Priorité** : **P1** ### 7.27.A ★★ Page LLM-ready (`llms-context.mdx` + `llms.txt` / `llms-full.txt`) * **Pourquoi** : objectif primaire #2. Les clients passent par ChatGPT/Claude. Les payloads complexes (FilterLocation imbriqué, filtres nested, match types) font échouer les LLMs → accusations injustes. Solution : fournir un **bloc de contexte compact** que le client copie-colle dans son LLM **avant** de demander un payload. * **2 livrables complémentaires** : 1. **`/llms-context.mdx`** (page humaine + lien "Copier pour LLM") : version condensée et lisible, accessible depuis la sidebar Démarrer + bouton flottant "Copier pour ChatGPT/Claude" (composant Mintlify clipboard). 2. **`/llms.txt` + `/llms-full.txt`** (servis à la racine, standard llms.txt natif Mintlify) : versions plain-text optimisées pour l'ingestion automatique des LLMs (un index court + une version full). * **Contenu cible (synthétique mais suffisant)** : 1. Identité Fluximmo en 3 lignes + URL doc + URL sample no-auth (pour que le LLM puisse vérifier). 2. Décision matrix Property vs Advert ultra-condensée (5 lignes max). 3. Schémas TypeScript-like des principaux DTOs : `FilterProperty`, `FilterAdvert`, `FilterLocation` (4 modes géo + 4 champs administratifs), `FilterPrice`, `FilterTypeHabitation`, `FilterMeta`, `SaveSearchPropertiesPayload`, `SaveSearchAdvertsPayload`. 4. Enums clés littéraux : `EnumPropertyOfferType`, `EnumPropertyClass`, `EnumSearchRequestSortBy`, `EnumPropertyAlertMatch`, `EnumAdvertAlertMatch`, `EnumPricingScope`, `EnumCurrency`. 5. Limites strictes (size 25/100, max keywords 20, distance > 0, bbox topLeft.lat > bottomRight.lat). 6. Match types & events : `CREATED` / `MERGED` (= UPDATED côté worker) / `ADVERT_EVENT` / events `PRICE` / `REPUBLISHED` / `UNPUBLISHED`. Pas de backfill à la création. 7. 8-10 archetypes de payloads (texte uniquement, format JSON minifié) couvrant : alerte ADVERT département, search PROPERTIES Paris, geoBoundingBox, geoDistance, geoShape POLYGON, multi-zones INSEE, filtres prix m², refetch bulk properties. 8. Anti-patterns critiques (1 paragraphe) : ne pas mocker, ne pas mixer GeoJSON `[lon,lat]` avec API `{lat,lon}`, ne pas reposer sur backfill. 9. Lien vers la doc complète et les sample endpoints no-auth. * **Contraintes de format** : * Cible **2 000–4 000 tokens max** (= \~5-10 K caractères) pour tenir dans une fenêtre prompt LLM standard. * Pas de fluff marketing. Notation type schémas TS / JSON / Markdown brut. * Mintlify : utiliser `` ou bloc `` contenant un `` button explicite. * **Maintenance** : * Générer (semi-)automatiquement à partir des DTOs (`api_fluximmo_v2/src/modules/app/dto/`) — script Node ou Python qui parse les classes class-validator/decorators et émet le contexte. Sinon mise à jour manuelle bimensuelle. * Versionnée (timestamp en bas) pour que les LLMs sachent la fraîcheur. * **Bonus DX** : * Endpoint `GET /llms.txt` et `GET /llms-full.txt` côté Mintlify déclaré dans `mint.json` (feature native). * Lien direct dans le footer de chaque page : "🤖 Copier le contexte LLM". * **Priorité** : **P0** (blocant pour l'objectif autonomie #2) ### 7.26.B ★ Page "Recevoir des webhooks à fort volume — bonnes pratiques" * **Pourquoi** : un client mal préparé qui prend tout en synchrone perd des webhooks (timeout, 5xx, surcharge) et accuse l'API. Mécaniser les bonnes pratiques **en amont** réduit massivement le support. * **Contenu cible** : * **Architecture recommandée** : endpoint webhook → ack rapide (200 \< 1s) → push vers queue → worker consume async. * **Outils suggérés** : RabbitMQ, AWS SQS, Google Pub/Sub, Redis Streams, Kafka (par ordre de complexité). Pour 80% des clients : SQS standard ou RabbitMQ suffit. * **Règles de base** : 1. Ne **jamais** traiter en synchrone dans le handler webhook (DB write, fetch externe, ML inference) 2. Idempotence : utiliser `flxId + event_type + timestamp` comme clé de dédup 3. Validation `x-webhook-key` (cf §C.3) avant ack 4. Réponse 2xx rapide (Fluximmo retry 2× + DLX 10× × 5min sinon DROP — cf §C.3) 5. Persister les payloads bruts dans une table `webhook_inbox` avant ack (audit + replay) 6. Dimensionner la queue : SLO Fluximmo 30-50s, batch max 20 adverts → pic possible * **Anti-patterns** : retourner 200 sans persister, ack avant validation, traitement bloquant en handler, pas de queue dead-letter côté client. * **Diagramme Mermaid** : flow webhook → queue → workers → DB. * **Snippets** : exemple Node Express + SQS, exemple Python Flask + Celery/RabbitMQ. * **Path** : `playbooks/webhook-volume-architecture.mdx` (joint à `concepts/webhooks.mdx`) * **Priorité** : **P1** ### 7.27 ★ Filtres géographiques avancés (transverses search + alerts) * **Pourquoi un focus dédié** : argument distinctif Fluximmo très demandé. Aujourd'hui éparpillé/sous-documenté. Doit être un **point d'entrée premium** dans Concepts + **playbook autonome**. * **Vérité code** (cf elasticimmo Go) : 4 modes natifs ES + champs administratifs indexés + multi-zones via `repeated location` (OR avec `minimum_should_match=1`). Champ index : `location` (geo\_point WGS84). DistanceType = `Plane` (à signaler pour longues distances). * **Modes réels à documenter** (tous dans `FilterLocation[]` du payload search/alerts) : 1. **Champs administratifs indexés** : `inseeCode` (commune), `irisCode` (quartier), `postalCode`, `department`. ⚠️ **L'INSEE commune fait l'objet d'une lookup vers centroïde** (3 686 communes mappées). Pas de support dédié `regionCode`/`cantonCode`/`arrondissementCode` au niveau du DTO API actuel — à valider avec produit si on étend. 2. **`geoBoundingBox`** : `topLeft` + `bottomRight` (LatLng). Cas type : carte interactive "search in this map area". 3. **`geoDistance`** : `pin` (LatLng) + `distanceKm`. Cas type : "à X km d'une adresse / station". 4. **`geoInseeCodeDistance`** : `inseeCode` (commune) + `distanceKm`. Lookup centroïde → distance. Cas type : "à 10 km de Bordeaux centre". 5. **`geoShape`** : `type` ∈ `POINT | LINE_STRING | POLYGON | MULTI_POINT | MULTI_LINE_STRING` + `coordinates` (LatLng\[]) + `relation` ∈ `WITHIN | CONTAINS | DISJOINT | INTERSECTS`. Cas type : zone dessinée à la main. 6. **Multi-zones** : `location: [zone1, zone2, ...]` → OR (matching ≥1 zone). Mix bbox + distance + shape autorisé dans la même requête. * **Contraintes & anti-patterns (réels code)** : * `distanceKm > 0` strict (rejet `InvalidArgument`) * `topLeft.lat > bottomRight.lat` (latitudes décroissantes N→S) * DistanceType **Plane** (euclidien) → erreur > \~50 km, à signaler * Polygons : ES default ≈ 10 000 vertices max (silencieux) * bbox immenses : ES tronque silencieusement * Coords WGS84, ordre GeoJSON `[lon, lat]` au stockage interne (l'API reçoit `{lat, lon}`) * **À documenter aussi** : * **Combinaison filtres** : geoDistance + price + propertyType (cas chasseur typique) * **Pipeline complet** : geocoding BAN/BANO → coords → `geoDistance` ou `geoShape` → search Fluximmo * **Performance** : impact pagination cursor, sortBy=RELEVANCE est BM25 + boosts title^2/description^3 (pas distance), recommandation bbox vs polygon * **Exemples à créer** : 1. ★ `geoBoundingBox` Paris zoom 14 (carte interactive SeLoger-like) 2. ★ `geoDistance` 1.5 km autour adresse (chaîné après BAN) 3. ★ `geoInseeCodeDistance` 10 km autour commune (cas province) 4. ★ `geoShape` POLYGON quartier custom (Le Marais) 5. ★ Multi-zones : `inseeCode` Paris + departement `92`/`93`/`94` 6. ★ Multi-polygones (3 secteurs d'une agence) 7. ★ geoDistance + price m² + isTotallyOffline=false (chasseur typique) * **Endpoints concernés** : properties search (full + lite + similar), adverts search, properties alerts CREATE/UPDATE, adverts alerts CREATE/UPDATE. * **Livrables doc** : * Page concept `concepts/recherche-geographique.mdx` (P0) * Playbook `playbooks/recherche-geographique-avancee.mdx` (P0) * Mention systématique avec lien depuis chaque endpoint search/alert * **Priorité** : **P0** ## 7.30 ★ Page "Pour les startups & sur-mesure" (positionnement commercial discret) * **Pourquoi** : les startups early-stage hésitent face à un acteur "B2B mature". Une page dédiée + discount + flexibilité signale l'ouverture, sans surfacer Calendly partout. * **Contenu cible** : * Engagement explicite : on accompagne les startups, on est ouverts aux collaborations sur-mesure, modalités flexibles. * **Discount early-stage** mentionné clairement (sans afficher de prix — invite à contacter pour qualif). * **Solutions sur-mesure** : endpoints non publics, formats de données customs, alertes complexes multi-territoires, intégrations dédiées. * **Crédibilité** : "nous alimentons aujourd'hui les principaux acteurs de l'immo en France" (sans noms). * **Confidentialité clients** (point critique) : nous ne communiquons jamais publiquement les noms de nos clients sauf autorisation explicite. **Références et études de cas disponibles sur demande** dans un cadre commercial confidentiel. * **Cas d'usage typiques que nous accompagnons** : réplication BDD marketplaces, scoring fintech, AVM banques/assurances, alertes chasseurs immo, market intelligence, CRM agences. * CTA discret : email contact + lien `my.fluximmo.io` (clé test gratuite 1 semaine). * **Path** : `ressources/startups-et-sur-mesure.mdx` (visible dans Ressources, pas dans le path d'onboarding pour ne pas alourdir) * **Bonus** : insérer une **bannière discrète** ("Vous êtes une startup early-stage ? Discount + collaboration sur-mesure →") en pied de page de l'introduction et du quickstart. * **Priorité** : **P1** ## 7.31 ★ Confidentialité clients & références sur demande (transverse) * **Règle absolue** : aucun nom client public dans toute la doc. Pas de logos clients, pas de testimonials nominatifs, pas de "they trust us". * À la place : tournures factuelles "leaders du marché immobilier français", "marketplaces, banques, fintechs, scoring", **références disponibles sur demande**. * À mentionner brièvement dans : `introduction.mdx`, `ressources/startups-et-sur-mesure.mdx`, FAQ ("Qui sont vos clients ?"). * **Priorité** : **P1** (consigne éditoriale plus que livrable séparé) ## 7.32 Nettoyage des @TODO existants | Fichier | Ligne | Action | | ------------------------------------ | -------------------------------- | -------------------------------------------------------------------------------------- | | `authentification.mdx:5` | "@TODO PAS D'APPEL MIS EN AVANT" | Aligné avec objectif autonomie #1 — réécrire la page sans Calendly proéminent dans P0. | | `doc/main-concepts/baas.mdx:12` | Phrase tronquée "filtres @TODO" | Réécrire la phrase, lier vers `concepts/filtres-communs.mdx`. P0. | | `swagger-api-geocoding.json:945,950` | `Token XXXXXX` / `Bearer XXXXXX` | Faux positif (placeholder dans description swagger BAN/BANO) — ignorer. | | `todo.md` (racine) | Méta-fichier instructions | Hors scope — fichier de pilotage, pas de doc publique. | ## 7.29 ★ Override des titres OpenAPI dans les pages MDX **Problème actuel** : `https://doc.fluximmo.io/api-v2-reference/properties/get-a-property-by-its-flx_id` affiche un titre auto-généré du swagger collé au nom du tag (`PropertiesGet a property by it's flx_id`) — illisible. **Solution Mintlify confirmée** : on peut override **directement dans le frontmatter du `.mdx`**, sans toucher au swagger : ```yaml theme={null} --- title: "Récupérer une property par son flx_id" sidebarTitle: "Get by flx_id" description: "Retourne une property unique. Utile en post-webhook pour refetch après réception d'un ID." icon: "magnifying-glass" tag: "STABLE" openapi: "/swagger-api-v2.json GET /v2/protected/properties/{flxId}" --- ``` **Action** : passer en revue **tous les fichiers OpenAPI MDX** et ajouter un frontmatter complet (title FR, sidebarTitle court, description orientée cas d'usage, icon Lucide/Font Awesome cohérent par groupe, tag `STABLE` / `BETA` / `DEPRECATED`). **Alternative** (si on préfère central) : `x-mint: { metadata: { title, sidebarTitle, description } }` dans le swagger source. Recommandation : **frontmatter MDX** pour la flexibilité (la doc équipe édite côté MDX, pas côté swagger qui est auto-généré par NestJS). **Priorité** : **P0** (massive amélioration UX/SEO immédiate, \~2h pour tous les endpoints). *** ## 7.28 ★ Refonte UI/UX Mintlify (identité visuelle premium) Objectif : doc qui transpire la maturité (acteur 2017+) et inspire confiance face à Casafari/Melo. Pas un facelift cosmétique — un signal de sérieux et de modernité. ### Capacités Mintlify à exploiter * **Background décoratif** : `"background": { "decoration": "gradient", "color": { "light": "#…", "dark": "#…" } }` → ambiance premium discrète. * **Theme-aware colors** : `light`/`dark` paire par paire pour chaque variable. * **Custom CSS** (Tailwind via composants JSX inline) : hero cards, badges custom. * **Mode dark/light** : ergonomie attendue par les devs. * **Hero React custom** sur la landing (composant `` JSX). * **Mermaid diagrams** natifs (workflows webhook, Property↔Adverts). * **API Playground interactif** (intégré OpenAPI Mintlify) : bouton "Try it" sur chaque endpoint avec edition live du payload. * **``** pour captures d'écran my.fluximmo.io. * **AI search assistant** Mintlify (built-in, à activer) : un autre chemin pour atténuer la friction "je ne trouve pas ma réponse". * **Reusable snippets** (Mintlify) : pour la décision matrix, le LLM banner, le footer contact — déduplication systématique. ### Direction artistique * **Décision** : **rebrand complet de la palette** (palette actuelle `#ED0040` rouge + `#7274E4` violet → abandonnée). Le rouge dominant n'est plus aligné avec le positionnement "API d'intelligence immobilière mature B2B". * **Direction recommandée à valider en atelier brand** : * Primary : `#1E40AF` indigo profond (confiance, FR, données) — ou variante `#0F172A` slate très foncé pour un look encore plus premium/data-driven * Secondary/accent : `#10B981` emerald (succès, signal "live data") OU `#F59E0B` amber (insight, valorisation) — un seul accent fort, choix à valider * Neutres : tons gris/slate Tailwind (`#F8FAFC` light → `#0F172A` dark) * Background : gradient discret depuis le primary (light: `linear-gradient(135deg, #F8FAFC 0%, #EEF2FF 100%)` ; dark: `linear-gradient(135deg, #0F172A 0%, #1E293B 100%)`) * **Livraison palette** : 1 atelier brand (1h) → Figma palette → application progressive. Livrer la doc avec un placeholder palette neutre (slate-only) et appliquer la palette finale en P2. * **Cohérence brand global** : prévenir l'équipe que ce rebrand peut/devrait remonter au site marketing (`fluximmo.io`) pour cohérence — décision au-delà du périmètre doc. * **Typographie** : Mintlify default (Inter) suffit, ne pas surcharger. * **Illustrations** : 3-5 spots SVG sur landing + 1 par section (Démarrer / Concepts / Playbooks / API Reference / Ressources). Style "minimal isometric" ou "abstract gradient" — éviter les stock photos. Source : commande illustrateur freelance (Figma / Storyset payant) ou génération AI cohérente. * **Diagrammes Mermaid** systématiques pour : * Flow ingestion → indexation → match → webhook * Decision tree Property vs Advert * Lifecycle d'une alerte (create → match → event → delete) * Pipeline geocoding → search Fluximmo * Retry/DLX webhook (avec timeline) * **Hero landing** : phrase d'accroche forte ("L'API d'intelligence immobilière FR, depuis 2017"), 3 cards d'entrée (Découvrir / Démarrer en 5 min / Référence API), illustration de fond gradient. ### Détails Mintlify config (mint.json) * Activer : `"theme"`, `"appearance": { "default": "system" }`, `"background": { "decoration": "gradient" }`, `"navigation": { ... }` propre. * Activer **API playground** sur tous les endpoints documentés. * Activer **Mintlify search AI** (clé Mintlify Pro requise — à confirmer plan). * Activer **llms.txt natif** (`"llms_txt": true` ou équivalent — cf §7.27.A). * Topbar : conserver "Mon compte" (CTA fort), retirer ou minimiser "Réserver un appel" (cf objectif autonomie). ### Priorité * **P1** pour la palette + background + Mermaid systématique + API playground * **P2** pour les illustrations sur-mesure + Hero landing custom ## 8. Composants Mintlify à introduire | Composant | Usage prévu | | -------------------------------- | --------------------------------------------------------------- | | `` / `` | Landing pages (Démarrer, Concepts, Playbooks) — choix de chemin | | `` | Code samples curl/Python/Node sur chaque exemple | | `` | Multi-langages dans une seule fenêtre de code | | `` | Quickstart, playbooks | | `` / `` / `` | Subtilités (cycle de vie alerte, refetch obligatoire) | | `` | Filtres avancés, FAQ | | `` | Diagrammes Property/Advert/Webhook flow | | `` | Captures my.fluximmo.io | ## 9. Tableau 1 — Endpoint × Documentation × Exemples × Priorité | Endpoint | Doc à produire | Exemples payloads (filtres) | Prio | | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | `POST /properties/search` | Filtres complets (★ `city` deprecated/ignoré → utiliser `postalCode`/`inseeCode`/`department`), **★ filtres géo avancés (bbox/distance/polygon/INSEE)**, cursor, sortBy, lite vs full, count semantics | Combo standard postalCode achat · département entier · geoDistance rayon · geoBoundingBox carte · polygone GeoJSON · multi-zones · isExclusive nested · pagination | P0 | | `POST /properties/search/lite` | Diff vs full, cas mobile/UI, mêmes modes géo | Idem full · cas autocomplete · ★ bbox temps réel | P1 | | `POST /properties/search/lite/similar` | Input/output, scoring | Peer comparison AVM | P3 | | `POST /adverts/search` | Filtres adverts-specific, source/seller, **★ filtres géo avancés** | Achat appart dpt · ★ geoDistance autour métro · seller agence · propertyFlxId · isPro | P0 | | `PUT /adverts/search/alerts` | matchType ADVERT\_EVENT, cycle de vie, webhook payload | Alerte chasseur dpt · alerte + events · multi-dpt · range complet | P0 | | `PUT /adverts/search/alerts/many` | Limite, atomicité | Multi-dpt | P2 | | `GET /adverts/search/alerts/all` | Format, pas de pagination | — | P1 | | `GET /adverts/search/alerts/{id}` | Réinspecter config | — | P2 | | `PATCH /adverts/search/alerts/{id}` | Préserve historique vs delete+create | Maj webhook url · maj filtres | P1 | | `DELETE /adverts/search/alerts/{id}` | Deactivate vs delete, impact events | — | P1 | | `GET /adverts/search/alerts/{id}/activate` | Sémantique GET legacy | — | P2 | | `GET /adverts/search/alerts/history/{flxId}` | Pagination skip/take, replay backlog | Backlog 30j | P1 | | `PUT /properties/search/alerts` | matchType MERGED, webhook IDs only, refetch | Alerte dpt analytics · isTotallyOffline · multi-dpt | P0 | | `PUT /properties/search/alerts/many` | Bulk | Multi-dpt | P2 | | `GET /properties/search/alerts/all` | Format | — | P1 | | `GET/PATCH/DELETE /properties/search/alerts/{id}` | Idem adverts | Idem adverts | P1/P2 | | `GET /properties/search/alerts/{id}/activate` | Idem adverts | — | P2 | | `GET /properties/search/alerts/history/{flxId}` | Idem adverts | Backlog | P1 | | `GET /adverts/{flxId}` `/by-url` | Refetch unitaire, 404 | — | P1 | | `GET /properties/{flxId}` | Refetch unitaire post-webhook | — | P0 | | `GET /properties/bulk` | Batch fetch post-webhook properties (taille max) | Batch 50 IDs | P0 | | `GET /properties/by-url` `/by-advert` | Cas d'usage | — | P1 | | `GET /v2/sample/*` (4 endpoints, no auth) | ★ Valorisation forte, fixtures | — | P0 | | `POST /estimate/property/lite` | Input/output AVM, confidence | Appart Paris · maison province | P1 | | `POST /experimental/estimate` | Diff vs lite, instabilité experimental | — | P2 | | GEO `/search` `/reverse` `/csv` | ⚠️ **Origine BAN/BANO Gouv** assumée + usage typique → injecter dans search location | Tour Eiffel · reverse coords · batch CSV | P1 | | GEO async projects (7 endpoints) | Workflow batch async (BAN/BANO) | 1 cas batch async 100k adresses | P2 | | Capabilities sur demande (DPE, DVF, similar, market/properties analytics, agences, consumption) | Page descriptive, CTA contact | — | P2 | | ★ Filtres géo avancés (transverse) | Concept + playbook dédié, modes bbox/distance/polygon/INSEE/multi-zones, contraintes, anti-patterns | bbox carte · rayon adresse · polygone custom · multi-zones petite couronne · multi-polygones agence · combo prix+geo | P0 | ## 10. Tableau 2 — MDX × Goal × Priorité × Dépendances | MDX (path proposé) | Goal | Prio | Dépend de | | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | --------------------------------------------------------- | | `introduction.mdx` (réécrit) | Vision FR-first, supériorité 2017+, supprimer Calendly proéminent | P0 | — | | `demarrer/quickstart.mdx` | 5 min sans appel : sample → clé test → 1er search | P0 | sample, auth | | `demarrer/sample-data.mdx` | ★ valoriser endpoints no-auth (4) | P0 | — | | `authentification.mdx` (réécrit) | Clé test gratuite + clé prod, sans imposer Calendly | P0 | — | | `demarrer/limites-rate-limits.mdx` | Quotas, headers, backoff | P1 | — | | `concepts/property-vs-advert.mdx` | Decision matrix (cf §5) | P0 | — | | `concepts/webhooks.mdx` (refondu) | Archi, retry, signature, déploiement, IDs vs payload | P0 | property-vs-advert | | `concepts/filtres-communs.mdx` | Référentiel filtres location/offer/price/habitation | P0 | — | | `concepts/match-types-cycle-alerte.mdx` | ★ MERGED, ADVERT\_EVENT, **perte historique sur recreate** | P0 | property-vs-advert | | `concepts/recherche-geographique.mdx` | ★ Modes géo (INSEE, bbox, distance, polygon, multi-zones), schémas JSON, contraintes, anti-patterns | P0 | filtres-communs | | `llms-context.mdx` (+ `llms.txt` + `llms-full.txt`) | ★★ Page LLM-ready copiable/collable + standards llms.txt natifs Mintlify | P0 | concepts, modèles, filtres | | `concepts/baas.mdx` (allégé) | Vue d'ensemble moteurs | P2 | — | | `playbooks/alerte-advert-departement.mdx` | Use case #1 chasseur immo | P0 | adverts/search/alerts, webhooks | | `playbooks/search-properties-analytique.mdx` | Use case #2 analytics Paris | P0 | properties/search, filtres | | `playbooks/replicate-bdd-adverts.mdx` | Sync continue marketplace | P1 | adverts alerts, webhooks | | `playbooks/track-price-changes.mdx` | Events ADVERT temps réel | P1 | adverts alerts, match types | | `playbooks/estimer-un-bien.mdx` | AVM AI lite | P1 | AI | | `playbooks/geocoder-pour-search.mdx` | GEO (BAN/BANO Gouv) → injection location filter | P1 | GEO, properties/search | | `playbooks/recherche-geographique-avancee.mdx` | ★ Cas concrets bbox/rayon/polygon/multi-zones, chaîne BAN→search | P0 | recherche-geographique, properties/search, adverts/search | | `api-reference/...` (existant) | Enrichir chaque .mdx OpenAPI avec prose + 2-3 exemples curated | P0 (search/alerts), P1 (AI/GEO), P2 (autres) | concepts, playbooks | | `api-reference/_models/property.mdx` (réécrit) | Champs + décisions + lien decision matrix | P0 | property-vs-advert | | `api-reference/_models/advert.mdx` (réécrit) | Idem | P0 | property-vs-advert | | `api-reference/capabilities-sur-demande.mdx` | DPE, DVF, similar, market analytics, agences, consumption | P2 | — | | `ressources/faq.mdx` | Top 15 questions (auth, webhooks, refetch, pagination, alertes) | P1 | concepts | | `ressources/codes-erreur.mdx` | Codes HTTP + métier (10001/10002/10003), exemples body erreur | P1 | — | | `ressources/webhooks-livraison.mdx` | Headers (`x-webhook-key`), pas de signature HMAC, retry 2+10×5min, DROP après \~50min, codes 200-205, SLO 30-50s | P1 | webhooks | | `ressources/bonnes-pratiques.mdx` | Pagination, retry, idempotence, refetch, validation webhook par `x-webhook-key` | P1 | concepts, webhooks-livraison | | `ressources/changelog.mdx` | Versions v2.x | P2 | — | | `ressources/contact.mdx` | Email + Calendly discret | P2 | — | | `ressources/startups-et-sur-mesure.mdx` | Engagement startups, discount early-stage, sur-mesure, références sur demande, **pas de noms clients publics** | P1 | — | | `concepts/dvf-vs-fluximmo.mdx` | Comparatif honnête DVF vs Fluximmo + complémentarité | P1 | property-vs-advert | | `playbooks/webhook-volume-architecture.mdx` | Architecture queue (RMQ/SQS), idempotence, ack rapide, anti-patterns | P1 | webhooks, webhooks-livraison | ## 11. Roadmap priorisée ### P0 — Foundation (livraison sprint 1, \~5-7j) * Nettoyer mint.json (orphelins, doublons), nouvelle structure 5 sections * ★★ **Désactiver/minimiser tous les CTAs Calendly** (objectif autonomie #1) * Nettoyage des **@TODO** existants (authentification, baas) * Réécrire `introduction`, `authentification`, `quickstart`, `sample-data` * Décision matrix Property vs Advert + page concepts associée * Refonte `webhooks.mdx` + page Match types & cycle de vie alerte * Page filtres communs * ★ Page concept **Recherche géographique** (4 modes + multi-zones + INSEE commune) * ★★ **Page LLM-ready** `llms-context.mdx` + activation `llms.txt`/`llms-full.txt` Mintlify * ★ **Override frontmatter title/sidebarTitle/description sur tous les .mdx OpenAPI** (\~2h) * Enrichir 6 endpoints Search/Alerts P0 (properties search, adverts search, properties alert CREATE, adverts alert CREATE, properties bulk, properties get) * 3 playbooks fondateurs : Alerte ADVERT département + Search PROPERTIES analytique + ★ Recherche géographique avancée * Modèles Property et Advert réécrits ### P1 — Coverage + UI/UX (sprint 2, \~5-7j) * Tous les autres endpoints alerts (LIST, UPDATE, DELETE, HISTORY) * Adverts get + properties by-url/by-advert * AI (estimations) + GEO (search/reverse) avec prose et exemples * 5 playbooks : Réplication BDD, Track price changes, Estimer un bien, Géocoder, **Webhook architecture haut volume (queue)** * **Page comparative DVF vs Fluximmo** * **Page Startups & sur-mesure** + bannière discrète early-stage + consigne éditoriale "pas de noms clients publics" * FAQ, Codes erreur, Bonnes pratiques, Limites/rate limits, Webhooks-livraison * ★ **Refonte UI/UX Mintlify** : nouvelle palette validée, background gradient, dark mode, Mermaid diagrammes systématiques (5 diagrammes clés), API playground activé, AI search activé (si plan le permet) ### P2 — Polish (sprint 3, \~3-5j) * Bulk alerts CREATE many (properties + adverts) * Activate endpoints * GEO async batch * Capabilities sur demande (DPE, DVF, similar, market analytics, agences, consumption) * Experimental estimate * Changelog, Contact, BaaS allégé * **Illustrations sur-mesure** (5-10 SVG) + Hero landing custom (composant React) * **Snippets Mintlify réutilisables** (décision matrix, footer, banners) * Script de génération automatique du `llms-context.mdx` à partir des DTOs (CI/CD) ### P3 — Nice-to-have * `properties/search/lite/similar` (sur demande) * Mermaid diagrammes (workflow Property/Advert/Webhook) * Dark mode visuels, captures my.fluximmo.io * Exemples SDK (Python, Node) si lib officielle livrée ## 12. Indicateurs de succès (post-livraison) * **Autonomie** : ratio "tickets / nouveaux comptes" divisé par 2 sur 3 mois * **Time-to-first-call** \< 5 min (depuis landing) * **CTA Calendly** absent avant la 3ᵉ page profondeur * **Couverture exemples** : tout endpoint P0/P1 a ≥ 2 exemples curated + 1 lien playbook * **Decision matrix** Property vs Advert accessible en ≤ 2 clics * **FAQ** couvre les 15 top questions support actuelles (à collecter auprès du support) * **LLM accuracy** : tester `llms-context.mdx` en demandant à GPT-5 / Claude de générer 5 payloads variés → ≥ 4/5 valides au premier essai * **UI** : score Lighthouse Performance ≥ 90, Accessibility ≥ 95, dark mode fluide ## 15. Questions ouvertes / décisions à prendre | Sujet | Question | Recommandation | | --------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Filtre `city` | Garder dans le DTO API (silent-ignore) ou retirer / déprécier ? | **À court terme** : marquer `@deprecated` dans le swagger + warning de validation côté API si fourni seul (sans `postalCode`/`inseeCode`/`department`). **À long terme** : retirer du DTO. | | `ALERT_MATCH_MERGED` naming | Garder le terme "MERGED" public alors que côté worker c'est "UPDATED" ? | Garder MERGED public (breaking change sinon), mais expliquer dans Concepts que sémantiquement = property mise à jour. | | Niveaux INSEE étendus | Étendre `inseeCode` aux niveaux département/région/IRIS ? | À discuter produit — actuellement commune-only via lookup. Fort impact UX si étendu (vrai pouvoir géo administratif). | | AI search Mintlify | Plan tarifaire requis ? | Vérifier si le plan actuel inclut l'AI search built-in. Si non, calculer le ROI. | | Rebrand couleur | Atelier brand 1h pour valider la palette finale | Bloquer un créneau avec design/marketing avant P1. | ## 13. Vérification end-to-end Une fois la refonte livrée : 1. Lancer `mintlify dev` localement → vérifier 0 lien cassé, navigation cohérente 2. Tester journey "découverte" : depuis introduction, atteindre un sample no-auth en ≤ 2 clics 3. Tester journey "quickstart" : suivre le quickstart from scratch sur clé test → premier search OK 4. Lancer un appel curl sur chaque sample endpoint depuis la doc (copier-coller doit marcher) 5. Vérifier que chaque endpoint P0/P1 a au moins 1 exemple, 1 cas d'erreur, 1 lien croisé concept/playbook 6. Search interne (Mintlify) : "alerte département" → playbook chasseur en top 3 résultats *** ## Annexe A — Fichiers clés à modifier | Path | Action | | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `mint.json` | Refonte complète navigation (groupes, ordre, tabs) | | `introduction.mdx` | Réécriture (vision FR-first, supprimer Calendly) | | `authentification.mdx` | Réécriture (clé test gratuite proéminente) | | `doc/main-concepts/baas.mdx` | Allégé, déplacé en Concepts | | `doc/main-concepts/properties-adverts.mdx` | Étendu en `concepts/property-vs-advert.mdx` (decision matrix) | | `doc/main-concepts/webhooks.mdx` | Refonte (signature, retry, déploiement, samples webhook) | | `api-v2-reference/_api-models/property.mdx` | Étendu (champs + décisions) | | `api-v2-reference/_api-models/advert.mdx` | Idem | | `api-v2-reference/{properties-search,adverts-search}/*.mdx` | Enrichir avec prose + exemples curated | | `api-v2-reference/{properties-alerts,adverts-alerts}/*.mdx` | Idem + cycle de vie | | `api-v2-reference/sample-{adverts,properties}/*.mdx` | Valoriser, lier depuis Démarrer | | Nouveaux fichiers | Tous les playbooks, FAQ, codes erreur, bonnes pratiques, limites, capabilities sur demande, quickstart | ## Annexe D — Patterns d'usage réels (analyse échantillon récent prod) Source : `/__samples/{adverts_alerts.csv, properties_alerts.csv, requests.csv}`. > ⚠️ **Lecture précautionneuse** : ces CSV sont un **échantillon récent** (requêtes/alertes des dernières semaines), **non exhaustifs** et **non représentatifs en volume global**. Les pourcentages cités ci-dessous donnent une **tendance qualitative** (quels combos existent et lesquels semblent fréquents), pas une distribution prod officielle. À utiliser pour calibrer les **archétypes** d'exemples, pas pour des assertions chiffrées dans la doc publique. ### D.1 Top filtres réellement utilisés * **Properties (88% des cas) — combo standard** : `price` + `offer` + `location` + `type` + `meta` + `habitation` + `adverts.isOnline`. **C'est l'exemple #1 à mettre partout.** * **Properties (8.8%)** : combo standard + `land` (investisseurs terrains). * **Adverts (54% — bruit)** : `source`-only (probablement tests / monitoring brut). Les clients sérieux passent par Properties. * **Adverts (12% — sérieux)** : `type` + `offer` + `location` + `habitation` + `process`. ### D.2 Modes géo réels (vs feature distinctive) > ⚠️ **CRITICAL** : le champ `city` (string) **ne filtre PAS réellement** (désactivé côté code à cause de la non-normalisation des noms de villes — "Paris" / "PARIS" / "paris 1er" / "Paris 75001"...). Il est silencieusement ignoré. **À documenter en gras comme deprecated / ignoré** + masquer dans les exemples + question ouverte (cf §15) : faut-il le retirer du DTO ou afficher un warning de validation ? → Les clients qui mettent `{ "city": "Paris", "postalCode": "75001" }` filtrent en réalité **uniquement par postalCode**. Les % ci-dessous reflètent le filtre **utile**. | Mode | Usage échantillon | Doc | | ------------------------------------------------ | -------------------------------------------------- | --------------------------------------------------------------------- | | `postalCode` (seul ou avec `city` ignoré) | **dominant** | ★ Exemple par défaut #1 | | `geoDistance` (rayon, médiane 20 km, plage 5-50) | \~20% | ★ Exemple #2 | | `inseeCode` (commune) | rare en échantillon, mais **recommandé** vs `city` | À promouvoir comme alternative robuste à city | | `department` (code 2 chiffres) | présent | ★ Exemple #3 (cas chasseur département entier) | | `irisCode` | rare | Documenter pour cas urbain fin | | `geoBoundingBox` | absent en échantillon | Documenter (cas carte interactive — feature distinctive à promouvoir) | | `geoShape` POLYGON | absent en échantillon | Documenter brièvement (capability distinctive) | | Multi-zones (array) | présent ponctuellement | Documenter (cas agence multi-territoires) | → **Décision** : la doc met en avant **`postalCode` / `inseeCode` / `department` / `geoDistance`** comme modes primaires. `city` est marqué "non utilisé pour le filtrage" + retiré des exemples. Les modes avancés (`geoBoundingBox`, `geoShape`, multi-zones) restent documentés en P0/P1 comme features distinctives. ### D.3 Enums dominants * `offer.type` = `OFFER_BUY` **100%** (RENT quasi-nul). → Exemples primaires en achat, location en exemple secondaire pour exhaustivité. * `type` = `CLASS_FLAT` 85% + `CLASS_HOUSE` 98% + `CLASS_PROGRAM` **66%** (neuf très utilisé). → Toujours inclure `PROGRAM` dans les exemples par défaut. * `meta.isTotallyOffline` = `false` **100%**. → **Best practice à documenter en gras** : c'est le filtre clé pour qualité prod. ### D.4 Ranges réels (à utiliser dans les exemples) * Prix : médiane **200 k€**, p75 358 k€, p95 900 k€. Range typique : 100-350 k€. * Surface : médiane 110 m², p95 295 m². * Rooms min : médiane 2-3. * Rayon `geoDistance` : médiane 20 km, plage 5-50. ### D.5 Pagination réelle * `size`=100 (60% des requêtes), 80 (26%) → **utiliser size=100 dans tous les exemples search**. * `sortBy`=`FIRST_SEEN_AT` `DESC` (62%) → **défaut canonique** dans la doc. * `LAST_SEEN_AT DESC` (26%) → cas "monitoring activité". ### D.6 Match types réels (alerts properties) * Combos fréquents : `[CREATED, MERGED]` est le défaut prod recommandé. * → Documenter ce combo en exemple primaire. ### D.7 À déprioriser dans la doc (peu utilisé en prod) * `keywords` / `fullTexts` (1 cas / 154) — mention brève uniquement. * `process`, `tags`, `hasAnomaly`, `isUrgent` — pas en exemple primaire. * Filtre `climate.epcEnergy` "toutes classes" — non-restrictif, pédagogique seulement. ### D.8 Anti-patterns prod observés à signaler * ★★ **`city` (string) est silencieusement ignoré** — utiliser `postalCode`, `inseeCode`, ou `department` à la place. À mettre en gras dans la doc Concepts/filtres-communs. * Adverts source-only : sans valeur métier pour la plupart des cas, signaler comme "test only". * `filterProperty` vide → renvoie tout le catalogue, danger perf + facturation. * Webhook URL non-HTTPS : rejetée à la création (cf §C.3). ### D.9 Implication concrète sur la roadmap * Les 10 archétypes prod identifiés deviennent les **10 exemples curated obligatoires** à livrer en P0/P1 (cf §7 et tableau §9). * 80% des clients sont couverts par : combo standard properties + city/postalCode + size=100 + sortBy=FIRST\_SEEN\_AT DESC + isTotallyOffline=false + match=\[CREATED,MERGED] + offer=BUY + types=\[FLAT,HOUSE,PROGRAM]. *** ## Annexe C — Vérités code à intégrer dans la doc (deltas vs OpenAPI) Issu de l'exploration des repos `api_fluximmo_v2` (NestJS), `elasticimmo` (Go), `prod_fluximmo/v2/_workers` (Python). ### C.1 Limites & contraintes réelles | Endpoint | Champ | Limite réelle code | | -------------------------------------- | --------------------------- | ------------------------------------------------- | | `POST /properties/search` | `size` | 1–25 (max **25**) | | `POST /properties/search/lite` | `size` | 1–25 | | `POST /properties/search/lite/similar` | `size` | 1–100 | | `POST /adverts/search` | `size` | 1–100 | | Search (tous) | `keywords[]`, `fullTexts[]` | Max **20** items chacun | | Webhooks adverts | batch | **20 adverts** par delivery (`MAX_BATCH_SIZE=20`) | | `geoDistance.distanceKm` | — | Strictement > 0 sinon rejet | | `geoBoundingBox` | — | `topLeft.lat > bottomRight.lat` requis | | `geoShape` polygon | vertices | ≈ 10 000 max (limite ES, silencieuse) | ### C.2 Match types & events (corrigés vs OpenAPI) * **Adverts** : * `ALERT_MATCH_CREATED` : 1× par couple (alert, advert) — dédup garantie via contrainte UNIQUE * `ALERT_MATCH_ADVERT_EVENT` : webhook reçoit `event_type` ∈ `PRICE | REPUBLISHED | UNPUBLISHED` * **Properties** : * `ALERT_MATCH_CREATED` : nouvelle property * `ALERT_MATCH_MERGED` : property existante mise à jour (côté worker = `ALERT_PROPERTIES_MATCH_UPDATED`). Le nom "MERGED" est l'API publique, à expliquer pour éviter la confusion. ### C.3 Webhook delivery (à documenter en P1) * **Headers envoyés** : `Content-Type: application/json`, `x-webhook-key: `, `x-api-key` (legacy duplicate). **Pas de signature HMAC**, pas de timestamp. Le client doit valider sur la valeur statique de `x-webhook-key`. * **Retry** : 2 retries internes + 30 s timeout + 10 s sleep entre. Puis DLX : 10 tentatives × 5 min de delay. Total worst case ≈ **50 minutes**, ensuite **DROP silencieux** (pas de notification client). * **SLO normal** : 30–50 s entre match et delivery. À documenter clairement (pas de "temps réel sub-seconde"). * **Codes succès acceptés** : 200–205 (inclus 203, 204, 205 — atypique). ### C.4 Codes erreur API (vérité code) | Code interne | HTTP | Sens | | ------------ | ------- | ------------------------- | | `10001` | 408/500 | HTTP request timeout (ES) | | `10002` | 400 | Validation error (DTO) | | `10003` | 500 | Unexpected | Format réponse erreur : ```json theme={null} { "error": { "message": "...", "code": 10003 } } ``` ### C.5 Pas de rate limiting exposé Aucun header `X-RateLimit-*` retourné par l'API publique. Documenter neutralement : "Si vous rencontrez des throttling, contactez-nous" (sans surpromettre des limites publiques). ### C.6 Sample no-auth confirmés * `GET /v2/sample/properties` * `GET /v2/sample/adverts` * `GET /v2/sample/webhook/properties` * `GET /v2/sample/webhook/adverts` * `GET /portals` (no auth, liste portails sources réels) * `GET /version`, `GET /healthz` (no auth, util DX) ### C.7 Schémas DTO clés à cristalliser dans la doc Concepts * `FilterLocation` : 4 modes géo + 4 champs administratifs (cf §7.27) * `FilterPrice` (properties) : `initial` / `latest` / `highest` / `lowest` + `scope[]` + `currency[]` + `variation[]` + `isAuction` * `FilterAdvertPrice` : `currentPrice.value` + `currentPrice.valuePerArea` * `FilterMeta` (properties only) : `firstSeenAt` / `lastSeenAt` / `lastUpdatedAt` / `lastPublishedAt` / `isTotallyOffline` * `FilterTypeHabitation` : surface (total/habitable), roomCount, bedroomCount, bathroomCount, type T1-T5+, characteristics, climate (EPC), heat * `FilterAdvert`-only : `isOnline`, `isPro`, `isExclusive`, `isAuction`, `source`, `seller[]`, `firstSeenAt`, `lastModifiedAt`, `propertyFlxId` * Enums : `EnumPropertyOfferTypeDto` (BUY/RENT/AUCTION/LIFE\_ANNUITY\_SALE/LEASE\_BACK/BUSINESS\_TAKE\_OVER/HOLIDAYS), `EnumPropertyClassDto` (HOUSE/FLAT/PROGRAM/SHOP/PREMISES/OFFICE/LAND/BUILDING/PARKING/ROOM/OTHER), `EnumSearchRequestSortByDto` (FIRST\_SEEN\_AT/PRICE/LAST\_UPDATED\_AT/LAST\_SEEN\_AT/RELEVANCE) * Scoring `RELEVANCE` = BM25 + boosts (`title^2`, `description^3`, n-gram, fuzziness 1) → à expliquer pour éviter "pourquoi le tri RELEVANCE n'est pas par distance ?" ### C.8 Endpoints publics non encore listés * `GET /portals` (no auth) * `GET /version`, `GET /healthz` (no auth) * v1 legacy : `GET /v1/adverts/search`, `GET /v1/adverts/urls`, `GET /v1/analytics/...` → à mentionner brièvement comme legacy (sans encourager) *** ## Annexe B — Concurrents inspirations retenues | Source | À reprendre | | ------------------------------ | --------------------------------------------------------------- | | Casafari API docs | Hiérarchie claire `Concepts / Reference / FAQ`, badges visuels | | Melo `/api-reference/concepts` | Format des concepts (court, comparatifs, exemples mini) | | Melo `/api-reference/faq` | Format FAQ avec accordéons par thème | | Stripe docs | Tabs code curl/Python/Node, recipes/playbooks orientés use case | | Mintlify guidelines | Cards landing, Steps quickstart, Mermaid diagrammes | # Estimation IA expérimentale (legacy ref) Source: https://doc.fluximmo.io/api-ai-reference/estimations/estimate-the-price-of-a-property Référence legacy. Voir la page V2 BETA. Cette section `api-ai-reference` est **legacy** et sera dépréciée. Le même endpoint expérimental est désormais documenté dans la référence V2 unifiée. ## Référence canonique Consultez la page de référence active : `POST /v2/protected/experimental/estimate` — comparaison multi-modèles, contrat évolutif (BETA). L'endpoint sous-jacent est identique (`/v2/protected/experimental/estimate`). La page V2 contient la prose, l'exemple curl/Python/Node et les avertissements BETA. # Estimation immobilière lite — prix d'un bien (API V2) Source: https://doc.fluximmo.io/api-ai-reference/estimations/lite-property-estimation post /v2/protected/estimate/property/lite Estimation de prix d'un bien immobilier français à partir de sa localisation, ses caractéristiques et son type d'offre. ## À quoi ça sert `POST /v2/protected/estimate/property/lite` retourne une estimation du prix d'un bien immobilier en France (prix central + intervalle) à partir d'un payload minimal (localisation + habitation + type + offre). L'endpoint est synchrone et peut être appelé depuis votre app (UI, worker batch, scoring de portefeuille). Vous pouvez cacher côté client par hash des champs métier pour économiser du quota. ## Payload | Champ | Type | Obligatoire | Sens | | -------------------------------------- | ------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `location.locationCoordinate.location` | `[number, number]` | Oui | Coordonnées WGS84 au format `[lng, lat]`. À obtenir via le [géocodage](/api-geocoding-reference/search/recherche-par-géocodage-direct). | | `habitation.surface.total` | number | Oui | Surface habitable totale en m². | | `habitation.roomCount` | integer | Oui | Nombre de pièces (`T1` = 1, `T2` = 2, etc.). | | `habitation.bedroomCount` | integer | Oui | Nombre de chambres. | | `land.surface` | number | Si maison | Surface du terrain en m² (objet `land` racine, optionnel). | | `type` | enum | Oui | `CLASS_FLAT` ou `CLASS_HOUSE`. | | `offer.type` | enum | Oui | `OFFER_BUY` (l'endpoint lite ne supporte pas `OFFER_RENT`). | ## Réponse L'API retourne un prix central, un intervalle (`priceRange`) et un objet `metadata`. Le contrat exact est documenté plus bas par le bloc OpenAPI (`POST /v2/protected/estimate/property/lite`). Pour un appel pratique de bout en bout (géocodage + estimation + cache), suivez le [playbook Estimer un bien](/playbooks/estimer-un-bien). ## Exemple ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/estimate/property/lite \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "location": { "locationCoordinate": { "location": [2.3376, 48.8606] } }, "habitation": { "roomCount": 3, "bedroomCount": 2, "surface": { "total": 60 } }, "type": "CLASS_FLAT", "offer": { "type": "OFFER_BUY" } }' ``` ## Liens utiles * [Playbook — Estimer un bien](/playbooks/estimer-un-bien) — géocodage + estimation + cache, bout en bout. * [Géocoder une adresse en amont](/api-geocoding-reference/search/recherche-par-géocodage-direct). Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Statistiques prix par zone Source: https://doc.fluximmo.io/api-analytics-reference/market-analytics/prices-per-area-statistics post /v2/protected/analytics/market/price_per_area/statistics Statistiques prix moyens / au m² par zone géographique. # Tension immobilière par ville Source: https://doc.fluximmo.io/api-analytics-reference/market-analytics/tension-city-statistics post /v2/protected/analytics/market/tension/statistics Indicateur de tension immobilière (offre vs demande) par ville. # Compter les properties par filtres Source: https://doc.fluximmo.io/api-analytics-reference/properties-analytics/count-properties-for-search-filters post /v2/protected/analytics/property/count Comptage de properties matchant des filtres donnés (analytics légères). # Analytics generales par filtres Source: https://doc.fluximmo.io/api-analytics-reference/properties-analytics/properties-general-analytics-for-search-filters post /v2/protected/analytics/property/search Statistiques agrégées (prix moyen, distribution surface) sur des filtres. # Annuler un géocodage en cours Source: https://doc.fluximmo.io/api-geocoding-reference/batch-async/annuler-un-géocodage-en-attente-ou-en-cours post /async/projects/{projectId}/abort Annuler un géocodage batch en attente ou en cours d'exécution. # Créer un projet de géocodage batch Source: https://doc.fluximmo.io/api-geocoding-reference/batch-async/créer-un-nouveau-projet post /async/projects Créer un projet asynchrone pour géocoder un grand volume d'adresses. # Configurer les paramètres du batch Source: https://doc.fluximmo.io/api-geocoding-reference/batch-async/définir-les-paramètres-du-traitement put /async/projects/{projectId}/pipeline Définir les colonnes et options de géocodage pour le projet batch. # Démarrer le géocodage batch Source: https://doc.fluximmo.io/api-geocoding-reference/batch-async/démander-le-démarrage-du-géocodage post /async/projects/{projectId}/start Lancer l'exécution du géocodage sur le projet batch configuré. # Réinitialiser un projet batch Source: https://doc.fluximmo.io/api-geocoding-reference/batch-async/ré-initialise-un-géocodage-terminé-ou-en-erreur post /async/projects/{projectId}/reset Réinitialiser un géocodage terminé ou en erreur pour le relancer. # Détails d'un projet batch Source: https://doc.fluximmo.io/api-geocoding-reference/batch-async/récupérer-les-informations get /async/projects/{projectId} Récupérer le statut et les métadonnées d'un projet de géocodage batch. # Supprimer un projet batch Source: https://doc.fluximmo.io/api-geocoding-reference/batch-async/supprimer-un-projet delete /async/projects/{projectId} Supprimer un projet de géocodage batch et ses fichiers associés. # Télécharger le fichier résultat Source: https://doc.fluximmo.io/api-geocoding-reference/batch-async/télécharger-le-fichier-résultat-du-projet get /async/projects/{projectId}/output-file/{token} Télécharger le CSV résultat d'un projet de géocodage batch terminé. # Télécharger le fichier source Source: https://doc.fluximmo.io/api-geocoding-reference/batch-async/télécharger-le-fichier-source-du-projet get /async/projects/{projectId}/input-file/{token} Télécharger le fichier d'entrée d'un projet de géocodage batch. # Uploader le fichier source Source: https://doc.fluximmo.io/api-geocoding-reference/batch-async/uploader-un-fichier-dentrée-pour-un-projet put /async/projects/{projectId}/input-file Uploader le CSV d'entrée pour un projet de géocodage batch. # Découvrir le service de géocodage Source: https://doc.fluximmo.io/api-geocoding-reference/getcapabilities/découvrir-le-service get /getCapabilities Métadonnées et capacités du service public BAN/BANO opéré par Fluximmo. ## À quoi ça sert `GET /getCapabilities` est l'endpoint de **discovery** du service de géocodage : il retourne les métadonnées du service (versions disponibles, indices interrogeables, opérations supportées, limites). C'est utile pour : * Vérifier qu'une version d'index donnée est en ligne avant de lancer un batch. * Construire des outils internes qui s'adaptent dynamiquement aux capacités du service. * Diagnostic et monitoring. **Transparence** : ce service expose la **Base Adresse Nationale (BAN/BANO)** publique française (`api-adresse.data.gouv.fr`) opérée par Fluximmo. ## Exemple ```bash curl theme={null} curl "https://geo.fluximmo.io/getCapabilities" ``` ```python Python theme={null} import requests print(requests.get("https://geo.fluximmo.io/getCapabilities", timeout=10).json()) ``` ```javascript Node theme={null} const resp = await fetch("https://geo.fluximmo.io/getCapabilities"); console.log(await resp.json()); ``` ## Liens utiles * [Géocodage direct](/api-geocoding-reference/search/recherche-par-géocodage-direct) * [Géocodage inverse](/api-geocoding-reference/reverse/recherche-par-géocodage-inverse) # Géocodage inverse en masse (CSV) Source: https://doc.fluximmo.io/api-geocoding-reference/reverse/géocodage-inversé-en-masse-d’un-fichier-csv post /reverse/csv Géocodage inverse par fichier CSV (mode synchrone, service public BAN/BANO). ## À quoi ça sert `POST /reverse/csv` prend un fichier CSV de coordonnées GPS et retourne le CSV enrichi des adresses correspondantes (`result_label`, `result_postcode`, `result_citycode`, `result_city`). Mode **synchrone** ; pour les volumes importants, basculer sur le pipeline [batch async](/api-geocoding-reference/batch-async/créer-un-nouveau-projet). **Transparence** : repose sur la **Base Adresse Nationale (BAN/BANO)** publique opérée par Fluximmo (`api-adresse.data.gouv.fr`). **Limites pratiques** : * Quelques milliers de lignes max en synchrone, encodage UTF-8. * Le CSV doit contenir des colonnes `latitude` et `longitude` (noms configurables via les paramètres `lat` et `lon`). ## Exemple ### Reverse-geocoder un mini-CSV de points GPS ```bash curl theme={null} # points.csv contient : # id;latitude;longitude # 1;48.8606;2.3376 # 2;45.7640;4.8357 curl -X POST https://geo.fluximmo.io/reverse/csv \ -F "data=@points.csv" ``` ```python Python theme={null} import requests with open("points.csv", "rb") as fh: resp = requests.post( "https://geo.fluximmo.io/reverse/csv", files={"data": fh}, timeout=120, ) print(resp.text[:500]) ``` ```javascript Node theme={null} import fs from "node:fs"; const form = new FormData(); form.append("data", new Blob([fs.readFileSync("points.csv")]), "points.csv"); const resp = await fetch("https://geo.fluximmo.io/reverse/csv", { method: "POST", body: form, }); console.log((await resp.text()).slice(0, 500)); ``` ## Liens utiles * [Géocodage inverse unitaire](/api-geocoding-reference/reverse/recherche-par-géocodage-inverse) * [Pipeline batch asynchrone (gros volumes)](/api-geocoding-reference/batch-async/créer-un-nouveau-projet) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Géocodage inverse (coordonnées → adresse) Source: https://doc.fluximmo.io/api-geocoding-reference/reverse/recherche-par-géocodage-inverse get /reverse Service public BAN/BANO opéré par Fluximmo : coordonnées GPS → adresse. ## À quoi ça sert `GET /reverse` prend une paire `(lat, lon)` et retourne l'**adresse la plus proche** ainsi que les codes administratifs associés (code postal, code INSEE, ville, département). C'est l'inverse du géocodage direct. **Transparence** : reposes sur la **Base Adresse Nationale (BAN/BANO)** publique (`api-adresse.data.gouv.fr`), opérée et proxifiée par Fluximmo pour vous offrir un point d'entrée unifié (mêmes clés API, mêmes quotas, monitoring partagé). **Cas d'usage typiques** : * Étiqueter un point GPS issu d'une app mobile (track utilisateur, photo géolocalisée) avec une adresse humaine. * Reconstituer le code INSEE d'une commune à partir d'un point centroïde calculé. * Enrichir une base d'événements géolocalisés (visites de biens, RDV terrain) avec l'adresse postale. ## Exemple ### Centre de Paris (Trocadéro) ```bash curl theme={null} curl "https://geo.fluximmo.io/reverse?lat=48.85&lon=2.29" ``` ```python Python theme={null} import requests resp = requests.get( "https://geo.fluximmo.io/reverse", params={"lat": 48.85, "lon": 2.29}, timeout=10, ) print(resp.json()) ``` ```javascript Node theme={null} const url = new URL("https://geo.fluximmo.io/reverse"); url.searchParams.set("lat", "48.85"); url.searchParams.set("lon", "2.29"); const resp = await fetch(url); console.log(await resp.json()); ``` Retourne l'adresse la plus proche et les codes administratifs (`postcode`, `citycode`/INSEE, `city`). ## Liens utiles * [Géocodage direct (adresse → coordonnées)](/api-geocoding-reference/search/recherche-par-géocodage-direct) * [Concept · Recherche géographique](/concepts/recherche-geographique) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Géocodage direct en masse (CSV) Source: https://doc.fluximmo.io/api-geocoding-reference/search/géocodage-direct-en-masse-d’un-fichier-csv post /search/csv Géocodage direct par fichier CSV (mode synchrone, service public BAN/BANO). ## À quoi ça sert `POST /search/csv` traite un **fichier CSV** d'adresses en une seule requête multipart et retourne le CSV enrichi avec les colonnes géocodées (`latitude`, `longitude`, `result_label`, `result_score`, `result_citycode`, etc.). Mode **synchrone** : la réponse contient le CSV résultat ; à utiliser pour des batchs courts. **Transparence** : appuyé sur la **Base Adresse Nationale (BAN/BANO)** publique (`api-adresse.data.gouv.fr`) opérée par Fluximmo. Pour les fichiers volumineux ou les traitements asynchrones, voir la section [Batch async](/api-geocoding-reference/batch-async/créer-un-nouveau-projet). **Limites pratiques** : * Taille recommandée : quelques milliers de lignes en mode synchrone. Au-delà, basculez vers le pipeline batch async. * Format CSV attendu : encodage UTF-8, séparateur configurable (`;` par défaut), une ou plusieurs colonnes adresse à concaténer via le paramètre `columns`. * Latence : proportionnelle au nombre de lignes ; prévoir un timeout HTTP généreux côté client. ## Exemple ### Géocoder un mini-CSV ```bash curl theme={null} # adresses.csv contient : # id;adresse;ville # 1;1 rue de Rivoli;Paris # 2;Place Bellecour;Lyon curl -X POST https://geo.fluximmo.io/search/csv \ -F "data=@adresses.csv" \ -F "columns=adresse" \ -F "columns=ville" ``` ```python Python theme={null} import requests with open("adresses.csv", "rb") as fh: resp = requests.post( "https://geo.fluximmo.io/search/csv", files={"data": fh}, data=[("columns", "adresse"), ("columns", "ville")], timeout=120, ) print(resp.text[:500]) ``` ```javascript Node theme={null} import fs from "node:fs"; const form = new FormData(); form.append("data", new Blob([fs.readFileSync("adresses.csv")]), "adresses.csv"); form.append("columns", "adresse"); form.append("columns", "ville"); const resp = await fetch("https://geo.fluximmo.io/search/csv", { method: "POST", body: form, }); console.log((await resp.text()).slice(0, 500)); ``` ## Liens utiles * [Géocodage direct unitaire](/api-geocoding-reference/search/recherche-par-géocodage-direct) * [Pipeline batch asynchrone (gros volumes)](/api-geocoding-reference/batch-async/créer-un-nouveau-projet) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Géocodage direct (adresse → coordonnées) Source: https://doc.fluximmo.io/api-geocoding-reference/search/recherche-par-géocodage-direct get /search Service public BAN/BANO opéré par Fluximmo : adresse → coordonnées GPS. ## À quoi ça sert `GET /search` résout une chaîne d'adresse libre (rue, ville, lieu connu) en **coordonnées GPS** + métadonnées administratives : code postal, code INSEE de la commune, type de voie, score de confiance. **Transparence** : ce service expose la **Base Adresse Nationale (BAN/BANO)** publique française (`api-adresse.data.gouv.fr`), opérée et proxifiée par Fluximmo. C'est un service public ouvert ; en passant par Fluximmo vous bénéficiez d'une intégration unifiée à l'écosystème API (mêmes clés, mêmes quotas, monitoring), sans rate-limit côté gouvernement à gérer vous-même. **Cas d'usage typiques** : * Géocoder une adresse saisie par un utilisateur avant d'appeler une recherche Fluximmo (`/v2/protected/properties/search` avec filtre `location` lat/lon + radius). * Récupérer le `inseeCode` d'une commune pour filtrer par code INSEE (plus précis que le code postal qui peut couvrir plusieurs communes). * Normaliser des adresses entrées en clair dans un CRM avant export. ## Exemples ### E1 — Lieu connu : "Tour Eiffel" ```bash curl theme={null} curl "https://geo.fluximmo.io/search?q=Tour+Eiffel&limit=1" ``` ```python Python theme={null} import requests resp = requests.get( "https://geo.fluximmo.io/search", params={"q": "Tour Eiffel", "limit": 1}, timeout=10, ) print(resp.json()) ``` ```javascript Node theme={null} const url = new URL("https://geo.fluximmo.io/search"); url.searchParams.set("q", "Tour Eiffel"); url.searchParams.set("limit", "1"); const resp = await fetch(url); console.log(await resp.json()); ``` Retourne typiquement `{ lat, lon, address, postalCode: "75007", inseeCode: "75107", ... }`. ### E2 — Adresse précise pour récupérer le code INSEE ```bash curl theme={null} curl "https://geo.fluximmo.io/search?q=1+rue+de+Rivoli+Paris&limit=1" ``` ```python Python theme={null} import requests resp = requests.get( "https://geo.fluximmo.io/search", params={"q": "1 rue de Rivoli Paris", "limit": 1}, timeout=10, ) hit = resp.json()["features"][0] print(hit["properties"]["citycode"], hit["geometry"]["coordinates"]) ``` ```javascript Node theme={null} const url = new URL("https://geo.fluximmo.io/search"); url.searchParams.set("q", "1 rue de Rivoli Paris"); url.searchParams.set("limit", "1"); const resp = await fetch(url); const hit = (await resp.json()).features[0]; console.log(hit.properties.citycode, hit.geometry.coordinates); ``` Le `citycode` retourné est le code INSEE exploitable directement dans les filtres `location` Fluximmo. ## Liens utiles * [Concept · Recherche géographique](/concepts/recherche-geographique) * [Géocodage inverse (coordonnées → adresse)](/api-geocoding-reference/reverse/recherche-par-géocodage-inverse) * Playbook · *Géocoder pour Search* (à venir Phase 4) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Capabilities sur demande Source: https://doc.fluximmo.io/api-reference/capabilities-sur-demande Endpoints non exposés publiquement par défaut + sur-mesure. Activation par mail à contact@fluximmo.com. ## Pourquoi cette page Tous les endpoints documentés dans la navigation publique sont **utilisables tels quels** sur n'importe quelle clé (test ou production). En parallèle, certains endpoints existent côté plateforme mais ne sont **pas exposés publiquement par défaut** — soit parce qu'ils sont de niche, soit parce qu'ils nécessitent un calibrage spécifique au use case. Vous pouvez les ajouter à votre clé sur demande, ou demander un développement sur-mesure (devis). ## Comment activer Écrivez à [contact@fluximmo.com](mailto:contact@fluximmo.com) en précisant : 1. Votre **clé API** ou l'email du compte 2. La **capability** souhaitée ou le **besoin métier** (en 3-5 lignes — c'est ce qui oriente le devis) 3. Le **volume cible** approximatif (req/jour, taille de payload, fréquence) Une fois activée, la capability est utilisable sur votre clé sans changement d'authentification — mêmes credentials, mêmes [headers](/authentification). ## Catalogue indicatif Indicateurs de tension fine, agrégations custom par zone/typologie, séries temporelles. Au-delà du périmètre standard de la tab **Analytics**. Renvoi sur votre webhook de l'historique passé d'une alerte (créations + events) pour des dates antérieures à la création de l'alerte. Besoin d'un endpoint sur-mesure qui n'existe pas encore (filtre, agrégation, format de payload spécifique) — chiffré sur devis. Cadence rate-limit dédiée, garanties de latence, accompagnement intégration. Dimensionné selon votre profil. ## Liens utiles * [Authentification](/authentification) — vos clés fonctionnent telles quelles après activation * [Limites & rate limits](/ressources/limites-rate-limits) * [Startups & sur-mesure](/ressources/startups-et-sur-mesure) # Modèle Advert Source: https://doc.fluximmo.io/api-v2-reference/_api-models/advert Schéma complet d'une advert — annonce individuelle sur un portail (1 URL = 1 advert). Une **Advert** = annonce individuelle sur un portail. **1 URL = 1 Advert.** Plusieurs adverts décrivant le même bien physique sont rattachées à une même `Property` parente après déduplication. ```mermaid theme={null} flowchart LR A1["Advert · portail A
flxId=adv_a"] --> P[("Property
flxId=prop_X")] A2["Advert · portail B
flxId=adv_b"] --> P A3["Advert · portail C
flxId=adv_c"] --> P ``` ## Champs principaux Schéma OpenAPI complet rendu plus bas par Mintlify (`AdvertDto`). Tableau ci-dessous : champs métier + champs spécifiques advert. | Champ | Type | Description | | --------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `flxId` | `string` | Identifiant unique Fluximmo de l'advert. | | `propertyFlxId` | `string` | flxId de la `Property` parente côté Fluximmo (toujours présent en prod). **Deux adverts avec le même `propertyFlxId` sont des doublons d'un même bien physique.** | | `mainPropertyAdFlxId` | `string` | flxId de l'**Advert** primaire (premier dupliqué détecté) au sein du groupe. À distinguer de `propertyFlxId` qui pointe vers la **Property** parente. (**déprécié** — utiliser `propertyFlxId` à la place. Conservé pour rétro-compatibilité.) | | `type` | `enum` | Mêmes valeurs que [`PropertyDto.type`](/api-v2-reference/_api-models/property#champs-principaux). | | `title` | `string` | Titre de l'annonce tel que sur le portail source. | | `description` | `string` | Description telle que sur le portail source. | | `source` | `SourceDto` | `{ flxId, url, website }` — `website` = identifiant du portail/source (grands réseaux d'agences, mandataires indépendants, agences locales, etc.) + URL canonique. | | `seller` | `LightSellerDto` | `{ flxId, name, siren, type }` avec `type` ∈ `SELLER_TYPE_AGENCY`, `SELLER_TYPE_NETWORK`, `SELLER_TYPE_UNKNOWN`. | | `contactInCharge` | `ContactDto` | Contact en charge (quand disponible). | | `isOnline` | `boolean` | Annonce actuellement en ligne sur le portail. | | `isPro` | `boolean` | Vendeur professionnel. | | `isExclusive` | `boolean` | Annonce déclarée exclusive par le vendeur. | | `isAuction` | `boolean` | Annonce de type enchère. | | `isUrgent` | `boolean` | Marquée urgent. | | `firstSeenAt` | `Date` | Première fois que Fluximmo a vu cette advert (ISO 8601). | | `lastSeenAt` | `Date` | Dernière fois vue. | | `lastModifiedAt` | `Date` | Dernière modification détectée (ISO 8601). | | `currentPrice` | `AdvertPriceDto` | `{ value, valuePerArea }` — prix courant de l'advert (€). | | `currency` | `enum` | `CURRENCY_EUR` ou `CURRENCY_USD`. | | `scope` | `enum` | `PRICING_ONE_OFF` ou `PRICING_MENSUAL`. | | `charges` | `ChargesDto` | Charges récurrentes, taxes, suppléments de loyer, frais annuels. | | `warrantyDeposit` | `number` | Dépôt de garantie (location). | | `offer` | `OfferDto` | Voir [Property → offer](/api-v2-reference/_api-models/property#champs-principaux). | | `location` | `AdLocationDto` | Voir [Property → location](/api-v2-reference/_api-models/property#champs-principaux) (équivalent, sans `region`/`country` natifs — utiliser `department` et `postalCode`). | | `habitation` | `TypeHabitationDto` | Idem Property : `{ surface, roomCount, bedroomCount, climate (DPE/GES), characteristics, features, … }`. | | `land` | `TypeLandDto` | Détails terrain. | | `parking` | `TypeParkingDto` | Détails parking. | | `medias` | `MediasDto` | Photos / médias de cette advert. | | `tags` | `string[]` | Tags non structurés. | | `hasAnomaly` | `boolean` | Incohérence détectée. | > **Note** — pour les structures partagées (`OfferDto`, `TypeHabitationDto`, `MetaDto`-like), voir la page [Property](/api-v2-reference/_api-models/property). L'Advert n'a pas de bloc `meta` séparé : `firstSeenAt`, `lastSeenAt`, `lastModifiedAt`, `isOnline` sont au niveau racine de l'advert. ## Events spécifiques aux adverts L'Advert est l'objet **dynamique** (ce qui change dans le temps). Les webhooks `ALERT_MATCH_ADVERT_EVENT` couvrent : * `PRICE` — toute variation de prix (même 1 €). * `REPUBLISHED` — passage `isOnline: false` → `true`. * `UNPUBLISHED` — passage `isOnline: true` → `false`. Le check interne `CHECK` (vérification sans changement détecté) ne déclenche **pas** de webhook. Détails : [Match types & cycle alerte](/concepts/match-types-cycle-alerte). ## JSON exemple ```json theme={null} { "flxId": "adv_01HXY...", "propertyFlxId": "prop_01HXY...", "mainPropertyAdFlxId": "adv_01HXY_main", "type": "CLASS_FLAT", "title": "T3 75m² Bastille - terrasse", "source": { "website": "", "url": "https://.example/annonces/...", "flxId": "src_01HXY..." }, "seller": { "name": "Agence Bastille Immo", "type": "SELLER_TYPE_AGENCY", "siren": "812345678" }, "isOnline": true, "isPro": true, "isExclusive": false, "isAuction": false, "currency": "CURRENCY_EUR", "scope": "PRICING_ONE_OFF", "currentPrice": { "value": 435000, "valuePerArea": 5800 }, "offer": { "type": "OFFER_BUY", "isCurrentlyOccupied": false }, "location": { "postalCode": "75011", "inseeCode": "75111", "city": "Paris", "locationCoordinate": { "lat": 48.857, "lng": 2.376 } }, "habitation": { "surface": { "total": 75 }, "roomCount": 3, "bedroomCount": 2 }, "firstSeenAt": "2025-04-22T09:11:00.000Z", "lastSeenAt": "2025-04-30T18:42:00.000Z", "lastModifiedAt": "2025-04-29T07:00:00.000Z" } ``` Échantillon live : `GET /v2/sample/adverts` — voir [Sample data](/demarrer/sample-data). ## Différences clés vs Property Détails conceptuels : [Property vs Advert](/concepts/property-vs-advert). ## Déduplication côté client (`propertyFlxId`) Chaque advert porte un `propertyFlxId` qui pointe vers sa `Property` parente côté Fluximmo. **Deux adverts avec le même `propertyFlxId` sont des doublons d'un même bien physique.** Pour identifier l'advert primaire d'un groupe (le premier dupliqué détecté), utilisez `mainPropertyAdFlxId`. ```json theme={null} // Advert 1 — racine de la chaîne { "flxId": "adv_aaa", "propertyFlxId": "prop_xxx", "mainPropertyAdFlxId": "adv_aaa", "source": { "website": "" } } // Advert 2 — doublon ingéré 12 h plus tard sur un autre portail { "flxId": "adv_bbb", "propertyFlxId": "prop_xxx", "mainPropertyAdFlxId": "adv_aaa", "source": { "website": "" } } ``` Côté DB, vous savez que `adv_bbb` est doublon de `adv_aaa` (même `propertyFlxId`). Un client ADVERTS-only peut ainsi reconstruire sa propre vue agrégée à la `Property` en groupant par `propertyFlxId` — sans dépendre de l'API properties côté Fluximmo. ## Cas d'usage **Alerte chasseur temps réel.** Vous voulez chaque variation de prix, chaque republication d'annonce ? Souscrivez `ALERT_MATCH_ADVERT_EVENT` — payload **complet** dans le webhook (pas de refetch nécessaire). **Réplication BDD source-par-source.** 1 advert = 1 ligne par portail. Idéal pour reconstituer en local le flux brut multi-portail, garder la trace de chaque URL et de qui (quel `seller`) a publié quoi. **Tracking concurrentiel par agence.** `seller.flxId` + `source.website` permettent de suivre l'inventaire d'une agence donnée sur un portail donné, et de mesurer `firstSeenAt` → `UNPUBLISHED` (durée de vie de l'annonce). ## Endpoints associés * [`GET /v2/protected/adverts/{flxId}`](/api-v2-reference/adverts/get-adverts-byt-their-flx_id) — fetch direct par flxId. * [`PUT /v2/protected/adverts/search/alerts`](/api-v2-reference/adverts-alerts/create-a-new-advert-alert-for-this-search) — alerte temps réel sur les nouveaux adverts. * [Match types & cycle alerte](/concepts/match-types-cycle-alerte) — events `PRICE`, `REPUBLISHED`, `UNPUBLISHED`. # Modèle Property Source: https://doc.fluximmo.io/api-v2-reference/_api-models/property Schéma complet d'une property — bien physique unique avec déduplication multi-portails. Une **Property** = bien physique unique. Agrégation de N adverts (annonces sur N portails) après déduplication. C'est l'objet à utiliser pour l'analytique, la valorisation et la vue dédupliquée d'un bien. ```mermaid theme={null} flowchart LR A1["Advert · portail A"] --> P[("Property · flxId")] A2["Advert · portail B"] --> P A3["Advert · portail C"] --> P P --> R["1 vue unique
du bien"] ``` ## Champs principaux Le schéma OpenAPI complet est rendu plus bas par Mintlify (`PropertyDto`). Ce tableau décrit les champs porteurs de sens métier. | Champ | Type | Description | | ------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `flxId` | `string` | Identifiant unique Fluximmo de la property. | | `type` | `enum` | `CLASS_FLAT`, `CLASS_HOUSE`, `CLASS_PROGRAM`, `CLASS_LAND`, `CLASS_PARKING`, `CLASS_OFFICE`, `CLASS_SHOP`, `CLASS_BUILDING`, `CLASS_PREMISES`, `CLASS_ROOM`, `CLASS_OTHER`, `CLASS_UNKNOWN`. | | `title` | `string` | Par défaut : titre de la première advert. | | `description` | `string` | Par défaut : description de la première advert. | | `offer` | `OfferDto` | `{ type, isCurrentlyOccupied, renting }` — `type` ∈ `OFFER_BUY`, `OFFER_RENT`, `OFFER_LIFE_ANNUITY_SALE`, `OFFER_HOLIDAYS`, `OFFER_AUCTION`, `OFFER_BUSINESS_TAKE_OVER`, `OFFER_LEASE_BACK`, `OFFER_UNKNOWN`. | | `price` | `PriceDto` | `{ initial, latest, variation, currency, scope, charges, isAuction, warrantyDeposit }`. `initial` = prix de la première advert ; `latest` = prix de la dernière. `scope` ∈ `PRICING_ONE_OFF`, `PRICING_MENSUAL`. | | `location` | `LocationDto` | `{ city, postalCode, inseeCode, irisCode, department, cityCoordinate, locationCoordinate }`. Coordonnées au format `{ location: [lng, lat] }` (GeoJSON). | | `habitation` | `TypeHabitationDto` | `{ surface: { total, balconies, terraces, ... } (m²), roomCount, bedroomCount, bathroomCount, wcCount, type, characteristics, climate (DPE/GES), features, propertyCondition }`. | | `land` | `TypeLandDto` | Détails terrain (surface, etc.). | | `parking` | `TypeParkingDto` | Détails parking. | | `medias` | `MediasDto` | Photos / médias agrégés depuis les adverts. | | `meta` | `MetaDto` | `{ firstSeenAt, lastSeenAt, lastUpdatedAt, lastPublishedAt, isTotallyOffline, totallyOfflineAt }` — voir ci-dessous. | | `adverts` | `PropertyAdvertDto[]` | Liste des adverts constituantes (présent selon endpoint, peut être tronqué). | | `tags` | `string[]` | Tags non structurés. | | `hasAnomaly` | `boolean` | Une incohérence a été détectée. | | `isUrgent` | `boolean` | Property marquée urgent. | ### `meta.isTotallyOffline` (champ clé en prod) `true` ⇔ toutes les adverts associées sont offline (bien vendu, retiré, loué). Filtre standard côté search : ```json theme={null} { "meta": { "isTotallyOffline": false } } ``` → exclut les biens hors ligne. Pour suivre le moment où un bien est passé totalement offline, utilisez `meta.totallyOfflineAt`. ## JSON exemple Extrait minimal — tous les champs ne sont pas affichés. ```json theme={null} { "flxId": "prop_01HXY...", "type": "CLASS_FLAT", "title": "Appartement T3 lumineux 70m²", "offer": { "type": "OFFER_BUY", "isCurrentlyOccupied": false }, "price": { "currency": "CURRENCY_EUR", "scope": "PRICING_ONE_OFF", "initial": { "value": 450000, "valuePerArea": 6428 }, "latest": { "value": 435000, "valuePerArea": 6214 } }, "location": { "postalCode": "75011", "inseeCode": "75111", "department": "75", "city": "Paris", "locationCoordinate": { "location": [2.376, 48.857] } }, "habitation": { "surface": { "total": 70 }, "roomCount": 3, "bedroomCount": 2, "climate": { "epcEnergy": "ENERGY_CLASSIFICATION_C", "epcClimate": "GREENHOUSE_CLASSIFICATION_B" } }, "meta": { "firstSeenAt": "2025-01-12T08:14:21.000Z", "lastSeenAt": "2025-04-30T11:02:08.000Z", "isTotallyOffline": false }, "adverts": [ /* PropertyAdvertDto[] — voir endpoint dédié */ ] } ``` Pour un échantillon **live** (jeu de données réel servant de bac à sable), utilisez `GET /v2/sample/properties` — voir [Sample data](/demarrer/sample-data). ## Différences clés vs Advert Pour les détails conceptuels (déduplication, parent-enfant, choix d'endpoint) : [Property vs Advert](/concepts/property-vs-advert). ## Cas d'usage **Analytique multi-portails.** Une property = 1 ligne par bien réel. Idéal pour comptages, pricing moyen, distribution géographique sans gonfler les chiffres avec les doublons inter-portails. **Déduplication.** L'agrégation est faite par Fluximmo (matching titre/prix/géo/médias/seller). Vous lisez un bien unique, sans avoir à dédupliquer côté client. **Estimation / AVM.** `price.initial` vs `price.latest` + `price.variation` donnent la trajectoire de prix observée. Couplé à `meta.isTotallyOffline=true` + `totallyOfflineAt`, vous reconstituez les prix de transaction observables. ## Endpoints associés * [`POST /v2/protected/properties/search`](/api-v2-reference/properties-search/search-properties) — recherche dédupliquée. * [`GET /v2/protected/properties/{flxId}`](/api-v2-reference/properties/get-a-property-by-its-flx_id) — fetch d'une property par son flxId (utile après webhook). * [Match types & cycle alerte](/concepts/match-types-cycle-alerte) — events `CREATED`, `MERGED` côté property. # Réactiver une alerte advert Source: https://doc.fluximmo.io/api-v2-reference/adverts-alerts/activate-a-search-advert-alert get /v2/protected/adverts/search/alerts/{alertId}/activate Réactiver une alerte advert précédemment désactivée. `GET /v2/protected/adverts/search/alerts/{alertId}/activate` rebascule à `true` le flag `activated` d'une alerte mise en pause via [`DELETE`](/api-v2-reference/adverts-alerts/deactivate-a-search-advert-alert). Le verbe HTTP est `GET` pour des raisons historiques (legacy). Comportement : idempotent — réactiver une alerte déjà active n'a pas d'effet. **Comportement post-réactivation** : l'alerte reprend les nouveaux matches `ALERT_MATCH_CREATED` à partir de l'instant `t` de réactivation. Les events (`PRICE`, `REPUBLISHED`, `UNPUBLISHED`) reprennent uniquement sur les **adverts qui étaient déjà matchées avant la désactivation** — pas de rattrapage des events survenus pendant la pause. ## Cas d'usage * **Pause saisonnière** — désactiver pendant les congés / hors campagne, réactiver au retour. * **Rotation budgétaire** — couper temporairement les alertes les moins prioritaires lorsque le volume webhook devient trop élevé. ## Cross-links * [Désactiver une alerte](/api-v2-reference/adverts-alerts/deactivate-a-search-advert-alert) * [Match types & cycle alerte](/concepts/match-types-cycle-alerte) # Créer une alerte adverts Source: https://doc.fluximmo.io/api-v2-reference/adverts-alerts/create-a-new-advert-alert-for-this-search put /v2/protected/adverts/search/alerts Alerte temps réel sur les adverts. Webhook avec payload complet + events PRICE/REPUBLISHED/UNPUBLISHED. **Avant de créer une alerte, vérifier que ton webhook répond `200` aux données de sample disponibles sans auth (`GET /v2/sample/webhook/adverts`).** `PUT /v2/protected/adverts/search/alerts` enregistre une **alerte adverts** : à chaque advert qui matche `search_query`, Fluximmo POSTe le payload sur `webhook_url`. Le `search_query` réutilise **exactement** le schéma `FilterAdvert`, identique à celui de l'alerte adverts elle-même. **Pas de backfill à la création.** Seules les adverts ingérées **après** la création de l'alerte déclenchent un webhook. Si vous avez besoin d'historique, voir [Historique des matches](/api-v2-reference/adverts-alerts/get-history-of-match-adverts-by-its-saved-search-flxid) (utilisable uniquement après que l'alerte ait reçu des matches). ## Champs clés * `search_query` — payload `FilterAdvert`, identique au search. * `webhook_url` — endpoint HTTPS public qui recevra les `POST` de Fluximmo (cf. [concept Webhooks](/concepts/webhooks)). * `match` — tableau parmi `ALERT_MATCH_CREATED` (nouvelle advert matchée, **1×**) et `ALERT_MATCH_ADVERT_EVENT` (events `PRICE` / `REPUBLISHED` / `UNPUBLISHED` sur les adverts **historiquement matchées par cette alerte**). Voir [Match types & cycle alerte](/concepts/match-types-cycle-alerte). * `search_name` — libellé humain pour reconnaître l'alerte dans la liste. * `context` — objet libre (`{ metadata: ... }`) propagé dans chaque webhook : pratique pour relier la livraison à une campagne / un user interne / une release. ## Cas d'usage * **Chasseur immo / FlipAndGo** — découvrir chaque nouvelle annonce dans une zone et un budget. * **CRM agence** — pousser automatiquement les nouvelles annonces matchant le projet d'un client dans son fil de prospection. * **Veille événementielle** — suivre les baisses de prix et republications sur un périmètre via `ALERT_MATCH_ADVERT_EVENT`. ## Exemples ### B1 — Achat appartement département (use case standard) Nouvelles annonces uniquement (pas d'events). Idéal pour démarrer. Filtre `isOnline: true` recommandé en production pour ne recevoir que des adverts actuellement publiées. ```json theme={null} { "search_query": { "filterAd": { "location": { "department": "75" }, "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "isOnline": true } }, "webhook_url": "https://votre-domaine.com/webhooks/fluximmo", "match": ["ALERT_MATCH_CREATED"], "search_name": "Achat T2-T3 Paris" } ``` ### B2 — Avec events (suivi prix + dépublications) Combinaison `CREATED` + `ADVERT_EVENT` : vous recevez d'abord la première découverte, puis chaque event ultérieur (`PRICE`, `REPUBLISHED`, `UNPUBLISHED`) **uniquement sur les adverts déjà matchées par cette même alerte**. ```json theme={null} { "search_query": { "filterAd": { "location": { "department": "75" }, "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "currentPrice": { "value": { "max": 600000 } }, "isOnline": true } }, "webhook_url": "https://votre-domaine.com/webhooks/fluximmo", "match": ["ALERT_MATCH_CREATED", "ALERT_MATCH_ADVERT_EVENT"], "search_name": "Paris achat - veille prix" } ``` Les events ne sont **pas rétroactifs** : si vous créez l'alerte aujourd'hui, vous ne recevrez pas les `PRICE` survenus hier sur des adverts existantes. Seuls comptent les matches obtenus après la création. ### B3 — Range prix mono-département Achat appartement département 92, 100k–400k€. Côté ADVERT, `location` est un objet unique : pour cibler plusieurs départements, créez une alerte par zone. ```json theme={null} { "search_query": { "filterAd": { "location": { "department": "92" }, "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "currentPrice": { "value": { "min": 100000, "max": 400000 } }, "isOnline": true } }, "webhook_url": "https://votre-domaine.com/webhooks/fluximmo", "match": ["ALERT_MATCH_CREATED"], "search_name": "92 achat appart 100-400k" } ``` ADVERT alerts acceptent un seul objet `location` (vs PROPERTY qui supporte un array multi-zones OR). Pour couvrir plusieurs départements, créez **N alertes** distinctes — c'est plus explicite et plus simple à monitorer. ## Cross-links * [Match types & cycle alerte](/concepts/match-types-cycle-alerte) * [Webhooks](/concepts/webhooks) — format de livraison, retries, sécurité * [Modifier une alerte](/api-v2-reference/adverts-alerts/update-the-search-criteria-of-an-advert-alert) — préserve l'historique * [Historique des matches](/api-v2-reference/adverts-alerts/get-history-of-match-adverts-by-its-saved-search-flxid) # Désactiver une alerte advert Source: https://doc.fluximmo.io/api-v2-reference/adverts-alerts/deactivate-a-search-advert-alert delete /v2/protected/adverts/search/alerts/{alertId} Désactiver une alerte advert (historique consultable). ⚠️ Malgré le verbe `DELETE`, **cet appel ne supprime pas l'alerte** : il bascule simplement le flag `activated` à `false`. La configuration, les liens vers les adverts historiquement matchées et l'historique restent **consultables** et l'alerte peut être [réactivée](/api-v2-reference/adverts-alerts/activate-a-search-advert-alert) à tout moment sans perte. Tant que l'alerte est désactivée, **aucun webhook** n'est émis (ni `CREATED`, ni `ADVERT_EVENT`). ## Cas d'usage * **Pause temporaire** — couper la livraison de webhooks sans perdre l'historique. * **Migration douce** — désactiver une ancienne alerte et créer la nouvelle config à côté pour comparer le volume avant cut-over (en ayant conscience que la nouvelle alerte démarre **vide d'historique**). ## Cross-links * [Réactiver une alerte](/api-v2-reference/adverts-alerts/activate-a-search-advert-alert) * [Modifier une alerte](/api-v2-reference/adverts-alerts/update-the-search-criteria-of-an-advert-alert) * [Historique des matches](/api-v2-reference/adverts-alerts/get-history-of-match-adverts-by-its-saved-search-flxid) # Lister mes alertes adverts Source: https://doc.fluximmo.io/api-v2-reference/adverts-alerts/get-all-your-advert-alerts get /v2/protected/adverts/search/alerts/all Liste de toutes les alertes adverts actives sur la clé. `GET /v2/protected/adverts/search/alerts/all` retourne **toutes** les alertes adverts associées à votre clé API, actives **et** désactivées. Aucune pagination : la totalité du parc est renvoyée en un appel. ## Cas d'usage * **Audit interne** — inventorier les alertes en place avant un nettoyage / migration de webhook. * **UI back-office** — afficher la liste des alertes d'un client interne, avec leur statut (`activated`). * **Détection de doublons** — repérer plusieurs alertes au même `webhook_url` pointant sur des `search_query` redondantes. Croisez `flxId` avec [`GET /alerts/{alertId}`](/api-v2-reference/adverts-alerts/get-the-search-criteria-of-an-advert-alert) pour obtenir le `search_query` complet d'une alerte précise. ## Cross-links * [Voir critères d'une alerte](/api-v2-reference/adverts-alerts/get-the-search-criteria-of-an-advert-alert) * [Modifier une alerte](/api-v2-reference/adverts-alerts/update-the-search-criteria-of-an-advert-alert) * [Désactiver une alerte](/api-v2-reference/adverts-alerts/deactivate-a-search-advert-alert) # Historique des matches d'une alerte Source: https://doc.fluximmo.io/api-v2-reference/adverts-alerts/get-history-of-match-adverts-by-its-saved-search-flxid get /v2/protected/adverts/search/alerts/history/{flxId} Historique des adverts matchées par une alerte donnée. `GET /v2/protected/adverts/search/alerts/history/{flxId}` renvoie l'historique des adverts qui ont matché l'alerte identifiée par `flxId`. Utile pour rejouer un backlog après une panne de votre webhook receiver, ou pour auditer la vie d'une alerte. **Pagination offset (`skip` / `take`)** — incohérence assumée vs la pagination cursor (`searchAfterHash`) du search. Cette API a une histoire plus ancienne ; l'unification est sur la roadmap. ## Cas d'usage * **Recover after webhook outage** — votre receiver a été down 4h ? Rejouez les matches manqués via cet endpoint plutôt qu'attendre les retries. * **Audit produit** — combien de matches une alerte a-t-elle généré sur les 30 derniers jours ? Combien d'events `PRICE` ? * **Migration** — exporter l'historique d'une alerte avant un grand changement de config. ## Cross-links * [Créer une alerte](/api-v2-reference/adverts-alerts/create-a-new-advert-alert-for-this-search) * [Webhooks](/concepts/webhooks) * [Match types & cycle alerte](/concepts/match-types-cycle-alerte) # Voir les critères d'une alerte advert Source: https://doc.fluximmo.io/api-v2-reference/adverts-alerts/get-the-search-criteria-of-an-advert-alert get /v2/protected/adverts/search/alerts/{alertId} Récupère les filtres d'une alerte advert par flxId. `GET /v2/protected/adverts/search/alerts/{alertId}` retourne la configuration complète d'une alerte : `search_query`, `webhook_url`, `match`, `search_name`, `context`, `activated`. Pratique pour réinspecter une alerte avant de la modifier ou pour debug. ## Cas d'usage * **Avant un PATCH** — récupérer la config courante pour préparer une modification ciblée (un seul champ change). * **Debug livraison webhook** — confirmer que le `webhook_url` enregistré correspond à votre infra actuelle. * **Reproduction d'alerte** — copier le `search_query` pour examiner ou rejouer la même configuration de filtres dans une nouvelle alerte (`PUT`). ## Cross-links * [Modifier une alerte](/api-v2-reference/adverts-alerts/update-the-search-criteria-of-an-advert-alert) * [Lister mes alertes](/api-v2-reference/adverts-alerts/get-all-your-advert-alerts) # Modifier une alerte advert Source: https://doc.fluximmo.io/api-v2-reference/adverts-alerts/update-the-search-criteria-of-an-advert-alert patch /v2/protected/adverts/search/alerts/{alertId} Modifier les filtres d'une alerte sans perdre l'historique. `PATCH /v2/protected/adverts/search/alerts/{alertId}` modifie une alerte **en place**. Champs modifiables : `search_query`, `webhook_url`, `match`, `search_name`, `context`. ⚠️ **Toujours préférer PATCH à `delete + recreate`.** Le PATCH **conserve** l'historique des matches : les events `PRICE` / `REPUBLISHED` / `UNPUBLISHED` continueront à s'appliquer aux adverts déjà matchées. Une recréation perd définitivement ce lien. ## Cas d'usage * **Migration webhook** — changer `webhook_url` après un changement d'infra (nouveau domaine, nouveau path). * **Resserrer / élargir le filtre** — ajouter un range prix ou un nouveau département sans perdre les events historiques. * **Activer les events sur une alerte existante** — ajouter `ALERT_MATCH_ADVERT_EVENT` au tableau `match` pour commencer à recevoir les events sur les adverts déjà matchées. ## Cross-links * [Voir les critères](/api-v2-reference/adverts-alerts/get-the-search-criteria-of-an-advert-alert) * [Match types & cycle alerte](/concepts/match-types-cycle-alerte) * [Désactiver une alerte](/api-v2-reference/adverts-alerts/deactivate-a-search-advert-alert) # Rechercher des annonces (search ADVERTS) Source: https://doc.fluximmo.io/api-v2-reference/adverts-search/search-adverts post /v2/protected/adverts/search Recherche brute d'annonces avec filtres source-spécifiques (isPro, source, seller). `POST /v2/protected/adverts/search` interroge directement la couche **adverts** : chaque ligne de résultat correspond à **une annonce sur un portail**, sans déduplication entre portails. Si un même bien physique est publié sur 3 portails, vous obtiendrez 3 lignes distinctes — une par advert. Cet endpoint partage le tronc de filtres avec [`POST /v2/protected/properties/search`](/api-v2-reference/properties-search/search-properties) (cf. [Filtres communs](/concepts/filtres-communs) et [Recherche géographique](/concepts/recherche-geographique)) et y ajoute les filtres spécifiques à la couche annonce : `isOnline`, `isPro`, `isExclusive`, `isAuction`, `source`, `seller[]`, `firstSeenAt`, `lastModifiedAt`, `propertyFlxId`, `mainPropertyAdFlxId`, `scope`, `currency`. Voir [Property vs Advert](/concepts/property-vs-advert) pour choisir la bonne couche. Pagination : cursor `searchAfterHash`. Tri : `sortBy` + `orderBy` (cf. schéma OpenAPI ci-dessous). ## Cas d'usage * **Chasseur immo** — surveiller toutes les annonces (avec doublons inter-portails) dans un département pour réagir le plus vite possible. * **Agence / vérification concurrentielle** — lister toutes les annonces publiées par un `seller` donné. * **Reverse-lookup property → adverts** — récupérer toutes les annonces rattachées à une property donnée via `propertyFlxId`. * **Audit pro vs particulier** — segmenter le marché via `isPro=true|false`. Pour de l'analytique multi-portails (1 ligne = 1 bien), préférez [`/properties/search`](/api-v2-reference/properties-search/search-properties). Pour de la veille temps réel par annonce avec events `PRICE`/`UNPUBLISHED`/`REPUBLISHED`, préférez les [alertes adverts](/api-v2-reference/adverts-alerts/create-a-new-advert-alert-for-this-search). ## Exemples ### A1 — Achat appartement département (chasseur immo) Toutes les annonces d'achat d'appartement à Paris (75), 50 résultats par page. ```json theme={null} { "size": 50, "search": { "filterAd": { "location": { "department": "75" }, "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "isOnline": true } } } ``` ### A2 ★ — Rayon 5 km autour de Châtelet (geoDistance) Use case chasseur urbain : rayon géodésique autour d'un point. ```json theme={null} { "size": 100, "search": { "filterAd": { "location": { "locationCoordinate": { "location": { "geoDistance": { "pin": { "lat": 48.8584, "lon": 2.3470 }, "distanceKm": 5 } } } }, "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "isOnline": true } } } ``` ### A3 — Annonces d'un seller spécifique (agence) Lister toutes les annonces actives d'un vendeur identifié (suivi concurrentiel d'une agence). ```json theme={null} { "size": 50, "search": { "filterAd": { "seller": [{ "flxId": "" }], "isOnline": true } } } ``` La structure exacte de `seller[]` (clés `flxId`, `name`, `siren`, `type`) est documentée dans le schéma `LightSellerDto` ci-dessous. Adaptez selon votre cas (filtrer par identifiant interne, par nom commercial, par téléphone, etc.). ### A4 — Annonces d'une property donnée (`propertyFlxId`) Reverse-lookup : pour une property (bien physique dédupliqué), récupérer toutes les adverts qui y sont rattachées sur les différents portails. ```json theme={null} { "size": 50, "search": { "filterAd": { "propertyFlxId": [""] } } } ``` ## Cross-links * [Property vs Advert](/concepts/property-vs-advert) — quand choisir cet endpoint vs `/properties/search` * [Recherche géographique](/concepts/recherche-geographique) — modes `postalCode`, `department`, `geoDistance`, `geoBoundingBox` * [Filtres communs](/concepts/filtres-communs) — tronc commun `offer`, `price`, `habitation`, `type` * [Créer une alerte adverts](/api-v2-reference/adverts-alerts/create-a-new-advert-alert-for-this-search) — basculer ce payload en alerte temps réel # Récupérer une advert par flxId Source: https://doc.fluximmo.io/api-v2-reference/adverts/get-adverts-byt-their-flx_id get /v2/protected/adverts/{flxId} Refetch unitaire d'une advert (annonce sur un portail). # Signaler un problème sur une advert Source: https://doc.fluximmo.io/api-v2-reference/adverts/report-an-issue-on-an-advert post /v2/protected/adverts/{flxId}/report Signalement gratuit d'un problème sur une annonce, avec revérification en tâche de fond. `POST /v2/protected/adverts/{flxId}/report` sert à nous dire ce qui ne va pas sur une annonce : prix faux, annonce toujours en ligne alors que nous la disons hors ligne, doublon, mauvaise agence… L'appel est **gratuit (0 crédit)**, répond **200 immédiatement** et déclenche une revérification de l'annonce contre son portail en tâche de fond. Le contrat exact est documenté plus bas par le bloc OpenAPI (`POST /v2/protected/adverts/{flxId}/report`). La donnée rafraîchie **n'est pas dans la réponse**. Relisez l'annonce quelques minutes plus tard avec [`GET /v2/protected/adverts/{flxId}`](/api-v2-reference/adverts/get-adverts-byt-their-flx_id), ou recevez-la sur votre [webhook d'alerte](/concepts/webhooks) si vous êtes abonné à une alerte qui la couvre. L'annonce est revérifiée **même si Fluximmo la croit hors ligne** — c'est exactement le cas `STILL_ONLINE`. Il n'y a **aucun cooldown** : chaque appel relance une vérification, sauf si le plafond de collectes simultanées est déjà atteint (voir `skipped_count`). ## Cas d'usage * **Annonce fantôme côté Fluximmo** — vos utilisateurs voient l'annonce sur le portail alors que nous la donnons hors ligne : `STILL_ONLINE`. * **Annonce déjà retirée** — vous constatez une 404 sur le portail alors que nous la donnons en ligne : `NOT_ONLINE_ANYMORE`. * **Contrôle qualité de vos données** — un prix ou une surface incohérente détectée par vos règles métier : `WRONG_DATA`. * **Dédoublonnage** — vous retrouvez le même bien sous un autre `flx_id` : `DUPLICATE`. ## Choisir le motif `reason` est obligatoire et prend l'une de ces 7 valeurs : * **`STILL_ONLINE`** — nous la disons hors ligne, elle est visible sur le portail. * **`NOT_ONLINE_ANYMORE`** — nous la disons en ligne, elle a disparu du portail. * **`WRONG_DATA`** — prix, surface ou nombre de pièces faux. * **`WRONG_LOCATION`** — géolocalisation ou adresse fausse. * **`DUPLICATE`** — doublon déjà présent sous un autre `flx_id`. * **`WRONG_AGENCY`** — pro/particulier ou agence mal attribué. * **`OTHER`** — autre cas. `comment` est optionnel (1000 caractères maximum), mais **obligatoire quand `reason` vaut `OTHER`**. Les espaces de début et de fin sont retirés : un commentaire fait uniquement d'espaces est refusé. Les clés inconnues envoyées dans le corps sont silencieusement ignorées. **Le motif détermine la profondeur de la revérification.** `STILL_ONLINE` et `NOT_ONLINE_ANYMORE` portent sur la disponibilité : ils déclenchent un contrôle léger qui met à jour le statut en ligne — le même que [`/check`](/api-v2-reference/adverts/request-an-on-demand-check-of-an-advert). Les cinq autres motifs portent sur le contenu de l'annonce : ils déclenchent une **réextraction complète**, seule capable de corriger un prix, une surface, une adresse ou une agence. Choisir le motif juste n'est donc pas cosmétique. ## Exemples ### 1 — Annonce encore en ligne sur le portail ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/adverts/123456789/report \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "reason": "STILL_ONLINE", "comment": "Toujours visible sur leboncoin ce matin" }' ``` Réponse `200` : ```json theme={null} {"data":{"status":"accepted","entity":"advert","flx_id":"123456789","triggered":["123456789"],"triggered_count":1,"skipped_count":0,"message":"1 advert(s) queued for an on-demand re-check."}} ``` `status` vaut `accepted` ou `nothing_to_check`. `triggered` liste les `flx_id` confiés au scraper. Sur une annonce, `skipped_count` ne peut valoir que `0` ou `1` : il passe à `1` lorsque la revérification n'a pas pu être lancée, et `status` vaut alors `nothing_to_check`. Une rafale d'appels rapprochés rend ce cas plus probable — espacez-les et réessayez. Le plafond de 10 annonces et le cas « url source absente » ne concernent que les properties. ### 2 — Motif `OTHER` : le commentaire devient obligatoire ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/adverts/123456789/report \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "reason": "OTHER" }' ``` Réponse `422` — un seul message est renvoyé par appel : ```json theme={null} {"error":{"message":"comment is required when reason is OTHER","code":10002}} ``` ## Erreurs spécifiques Les réponses en succès sont enveloppées dans `{ "data": … }`, les erreurs dans `{ "error": { "message", "code" } }`. * **404** — `flx_id` inconnu : `{"error":{"message":"Advert with flx_id 123456789 not found","code":10003}}`. * **422 (validation, code `10002`)** — corps invalide, par exemple un `reason` hors énumération : `"reason must be one of the following values: STILL_ONLINE, NOT_ONLINE_ANYMORE, WRONG_DATA, WRONG_LOCATION, DUPLICATE, WRONG_AGENCY, OTHER"`. Notez le **422**, pas un 400. Un seul message est renvoyé par appel même si plusieurs champs sont invalides : branchez votre logique sur le **code**, pas sur le libellé. * **422 (code `10003`)** — l'annonce n'a pas d'url source exploitable : `"Advert with flx_id 123456789 has no source url: it cannot be re-checked."` Le signalement ne peut pas être traité. Détail des codes : [Codes d'erreur](/ressources/codes-erreur). ## Liens utiles * [Récupérer une advert par flxId](/api-v2-reference/adverts/get-adverts-byt-their-flx_id) — relire la donnée rafraîchie quelques minutes plus tard. * [Revérifier une advert](/api-v2-reference/adverts/request-an-on-demand-check-of-an-advert) — même mécanique de revérification, sans diagnostic et facturée 1 crédit. * [Webhooks](/concepts/webhooks) — recevoir la mise à jour sans repoller. * [Property vs Advert](/concepts/property-vs-advert) — l'équivalent existe sur les properties : [`POST /v2/protected/properties/{flxId}/report`](/api-v2-reference/properties/report-an-issue-on-a-property). * [Bonnes pratiques](/ressources/bonnes-pratiques) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Demander la revérification d'une advert Source: https://doc.fluximmo.io/api-v2-reference/adverts/request-an-on-demand-check-of-an-advert post /v2/protected/adverts/{flxId}/check Contrôle à la demande de la disponibilité d'une annonce sur son portail, facturé 1 crédit. `POST /v2/protected/adverts/{flxId}/check` répond à une seule question : **cette annonce est-elle toujours en ligne sur son portail ?** L'appel **n'a aucun corps de requête ni paramètre de requête** : seul le `flxId` du chemin est nécessaire. Il répond **200 immédiatement**, le contrôle est lancé en tâche de fond. Le contrat exact est documenté plus bas par le bloc OpenAPI (`POST /v2/protected/adverts/{flxId}/check`). L'appel coûte **1 crédit, facturé sur toute réponse 2xx**. Un `404` ne facture rien. Suivez votre solde avec [la consommation de crédits](/api-v2-reference/consumption/get-your-credit-consumption). **Ce contrôle porte sur la disponibilité, pas sur le contenu.** Il met à jour le statut en ligne de l'annonce ; il ne corrige ni le prix, ni la surface, ni la géolocalisation, ni l'agence. Pour faire corriger une donnée fausse, utilisez [`/report`](/api-v2-reference/adverts/report-an-issue-on-an-advert) avec le motif adéquat — c'est gratuit et plus efficace. Le résultat **n'est pas dans la réponse**. Relisez l'annonce quelques minutes plus tard avec [`GET /v2/protected/adverts/{flxId}`](/api-v2-reference/adverts/get-adverts-byt-their-flx_id), ou recevez-la sur votre [webhook d'alerte](/concepts/webhooks). L'annonce est contrôlée **même si Fluximmo la croit hors ligne** — c'est justement le cas qui permet de la faire repasser en ligne. ## `/check` ou `/report` ? * **`/check` — « est-elle toujours en ligne ? »** : vous n'avez pas de diagnostic, vous voulez confirmer la disponibilité. Payant (1 crédit), sans corps de requête. * **`/report` — nous dire ce qui ne va pas** : vous avez un diagnostic (`STILL_ONLINE`, `WRONG_DATA`, `DUPLICATE`…). Gratuit, avec motif obligatoire. **Et le motif détermine la profondeur** : un problème de disponibilité déclenche le même contrôle léger que `/check`, tandis qu'un problème de donnée déclenche une réextraction complète de l'annonce. ## Cas d'usage * **Avant d'afficher une fiche** — confirmer qu'une annonce vue il y a plusieurs jours est toujours publiée avant de la présenter à un utilisateur final. * **Confirmation de disponibilité** — vérifier qu'une annonce est toujours publiée avant de déclencher une prise de contact. * **Rattrapage ciblé** — revérifier un petit lot d'annonces sensibles sans attendre le cycle de collecte automatique. ## Exemples ### 1 — Demander la revérification ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/adverts/123456789/check \ -H "x-api-key: $FLUXIMMO_API_KEY" ``` Réponse `200` : ```json theme={null} {"data":{"status":"accepted","entity":"advert","flx_id":"123456789","triggered":["123456789"],"triggered_count":1,"skipped_count":0,"message":"1 advert(s) queued for an on-demand re-check."}} ``` `status` vaut `accepted` ou `nothing_to_check`. `triggered` liste les `flx_id` confiés au scraper. Sur une annonce, `skipped_count` ne peut valoir que `0` ou `1` : il passe à `1` lorsque la revérification n'a pas pu être lancée, et `status` vaut alors `nothing_to_check`. Une rafale d'appels rapprochés rend ce cas plus probable — espacez-les et réessayez. Le plafond de 10 annonces et le cas « url source absente » ne concernent que les properties. Le crédit est débité dans les deux cas, puisque la réponse est un 2xx. ### 2 — Revérifier, puis relire la donnée Le `200` ne contient pas l'annonce : il faut la relire quelques minutes plus tard. ```bash theme={null} # 1. déclencher la revérification (1 crédit) curl -X POST https://api.fluximmo.io/v2/protected/adverts/123456789/check \ -H "x-api-key: $FLUXIMMO_API_KEY" # 2. quelques minutes plus tard, relire l'annonce et son statut en ligne curl https://api.fluximmo.io/v2/protected/adverts/123456789 \ -H "x-api-key: $FLUXIMMO_API_KEY" ``` Si vous êtes abonné à une alerte qui couvre cette annonce, l'étape 2 est inutile : la mise à jour arrive sur votre webhook. ## Erreurs spécifiques Les réponses en succès sont enveloppées dans `{ "data": … }`, les erreurs dans `{ "error": { "message", "code" } }`. * **404** — `flx_id` inconnu : `{"error":{"message":"Advert with flx_id 123456789 not found","code":10003}}`. Aucun crédit débité. * **422 (code `10003`)** — l'annonce n'a pas d'url source exploitable : `"Advert with flx_id 123456789 has no source url: it cannot be re-checked."` Il n'y a pas d'erreur de validation de corps sur cette route, puisqu'elle n'en prend pas. Détail des codes : [Codes d'erreur](/ressources/codes-erreur). ## Liens utiles * [Récupérer une advert par flxId](/api-v2-reference/adverts/get-adverts-byt-their-flx_id) — relire l'annonce et son statut après le contrôle. * [Signaler un problème sur une advert](/api-v2-reference/adverts/report-an-issue-on-an-advert) — gratuit, avec un motif, et seul moyen de faire corriger une donnée fausse. * [Consommation de crédits](/api-v2-reference/consumption/get-your-credit-consumption) — suivre ce que coûtent vos revérifications. * [Créer une alerte adverts](/api-v2-reference/adverts-alerts/create-a-new-advert-alert-for-this-search) — recevoir les mises à jour sans repoller. * [Limites et rate limits](/ressources/limites-rate-limits) · [Bonnes pratiques](/ressources/bonnes-pratiques) * [Property vs Advert](/concepts/property-vs-advert) — l'équivalent existe sur les properties : [`POST /v2/protected/properties/{flxId}/check`](/api-v2-reference/properties/request-an-on-demand-check-of-a-property). Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Récupérer une advert par URL Source: https://doc.fluximmo.io/api-v2-reference/adverts/retrieve-advert-by-url get /v2/protected/adverts/url Retrouve une annonce à partir de son URL portail, en la collectant à la demande si nécessaire. `GET /v2/protected/adverts/url?url=…` retrouve une annonce à partir de l'URL de sa page sur un portail. Si l'annonce est déjà indexée chez Fluximmo, elle est renvoyée directement. Sinon elle est **collectée à la demande**, et la requête attend son indexation. Le paramètre `url` est obligatoire et doit être une url http(s) valide pointant vers **une** annonce, par exemple `https://www.leboncoin.fr/ad/ventes_immobilieres/3246347062`. Le contrat exact est documenté plus bas par le bloc OpenAPI (`GET /v2/protected/adverts/url`). **La requête peut bloquer jusqu'à \~45 secondes** avant de répondre, le temps de collecter puis d'indexer l'annonce. Réglez le timeout de votre client HTTP **au-dessus de 45 s** : la valeur par défaut de la plupart des librairies est plus basse et coupera la requête avant la réponse. **1 crédit est facturé uniquement quand un document est renvoyé (`200`).** Les réponses `202` (indexation en cours) et `422` (rejetée par l'ingestion) ne sont pas facturées. ## Les trois issues possibles * **`200` — l'annonce est disponible.** Le corps est l'objet advert, enveloppé : `{ "data": { … } }`. 1 crédit. * **`202` — collectée, pas encore indexée.** Réessayez la **même** url après `retry_after_s` secondes. Ce délai est dans le corps ; il n'y a **pas** d'en-tête HTTP `Retry-After`. ```json theme={null} {"data":{"status":"pending","website_ref":"leboncoin.fr_3246347062","retry_after_s":120,"message":"The ad was collected and is being indexed. Retry the same url in a couple of minutes."}} ``` * **`422` — collectée mais rejetée par l'ingestion** (données incohérentes ou manquantes, hors périmètre). **Cette annonce ne deviendra jamais disponible : ne réessayez pas.** ```json theme={null} {"data":{"status":"rejected_by_ingestion","website_ref":"leboncoin.fr_3246347062","message":"This ad was collected but could not be ingested (incoherent or missing data, or out of scope). It will not become available."}} ``` Ces deux corps sont enveloppés dans `data` (ce sont des issues métier, pas des erreurs) et portent un champ `status` lisible par une machine, ainsi que `website_ref` pour corréler un retry ultérieur. Ne traitez pas `website_ref` comme garanti : il peut valoir `null` lorsque l'annonce n'a pas de référence canonique. **Aucune déduplication côté serveur.** Deux appels concurrents sur la même url déclenchent **deux** collectes — et **deux** crédits si les deux répondent `200`. Sérialisez ou dédupliquez vos appels sur une même url. ## Cas d'usage * **Import d'une annonce repérée à la main** — un utilisateur colle l'url d'une annonce, vous la rapatriez dans votre base. * **Enrichissement d'un lien existant** — retrouver le `flx_id` Fluximmo derrière une url portail déjà stockée chez vous. * **Vérification ponctuelle** — obtenir la fiche complète (prix, surface, vendeur) d'une annonce vue sur un portail. ## Exemples ### 1 — Récupérer une annonce par son url Notez le `--max-time 60`, supérieur aux \~45 s d'attente maximale. ```bash theme={null} curl --max-time 60 -G https://api.fluximmo.io/v2/protected/adverts/url \ -H "x-api-key: $FLUXIMMO_API_KEY" \ --data-urlencode "url=https://www.leboncoin.fr/ad/ventes_immobilieres/3246347062" ``` ### 2 — Gérer le `202` en réessayant après `retry_after_s` `202` et `422` se distinguent par le code HTTP : on réessaie le premier, jamais le second. ```python theme={null} import os, time, requests API = "https://api.fluximmo.io/v2/protected/adverts/url" headers = {"x-api-key": os.environ["FLUXIMMO_API_KEY"]} params = {"url": "https://www.leboncoin.fr/ad/ventes_immobilieres/3246347062"} for _ in range(5): # bornez les tentatives : une annonce peut rester pending resp = requests.get(API, headers=headers, params=params, timeout=60) if resp.status_code == 200: print(resp.json()["data"]) # annonce disponible — 1 crédit débité break if resp.status_code == 202: time.sleep(resp.json()["data"]["retry_after_s"]) # indexation en cours continue print(resp.status_code, resp.json()) # 422 rejetée, 404 — on n'insiste pas break ``` ## Erreurs Les erreurs, elles, ne sont pas enveloppées dans `data` mais dans `{ "error": { "message", "code" } }`. * **404** — portail non couvert, url invalide, ou url ne désignant pas une annonce unique (page de recherche, url tronquée). * **422 (validation, code `10002`)** — paramètre `url` absent, vide ou mal formé. Les trois cas renvoient le même message, `"Url query parameter must be a valid http(s) url"` ; branchez tout de même sur le code `10002` plutôt que sur le libellé. À ne pas confondre avec le `422` de rejet d'ingestion ci-dessus, qui est un succès métier enveloppé dans `data` et porte un `status`. Détail des codes : [Codes d'erreur](/ressources/codes-erreur). ## Liens utiles * [Récupérer une advert par flxId](/api-v2-reference/adverts/get-adverts-byt-their-flx_id) — une fois le `flx_id` connu, c'est la voie la plus directe. * [Property vs Advert](/concepts/property-vs-advert) — une url portail désigne toujours une advert ; l'équivalent existe sur les properties : [`GET /v2/protected/properties/url`](/api-v2-reference/properties/retrieve-a-property-by-url). * [Consommation de crédits](/api-v2-reference/consumption/get-your-credit-consumption) · [Limites et rate limits](/ressources/limites-rate-limits) * [Bonnes pratiques](/ressources/bonnes-pratiques) — timeouts, retries et gestion des statuts métier. Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Consommation crédits Source: https://doc.fluximmo.io/api-v2-reference/consumption/get-your-credit-consumption get /v2/protected/admin/consumption Voir la consommation crédits de la clé courante. # Match DPE à une annonce (V2 stable) Source: https://doc.fluximmo.io/api-v2-reference/dpe-match/match-dpe-à-une-annonce-immobilière post /v2/protected/opendata/dpe/match Retourne les DPE ADEME les plus similaires à une annonce immobilière, classés par score de similarité avec un niveau de confiance. ## À quoi ça sert `POST /v2/protected/opendata/dpe/match` recherche, parmi les DPE de la base ADEME, les candidats les plus proches d'une annonce immobilière donnée. Contrairement à [DPE Search](/api-v2-reference/dpe-search/recherche-dpe) qui renvoie une liste paginée et filtrée, Match renvoie les meilleurs candidats **classés par similarité** et accompagnés d'un score. Le payload sépare deux notions à ne pas confondre : * **`filters` (pré-filtre dur)** — exclut les candidats qui ne correspondent pas (géographie, plage de surface, type de bâtiment, plage de dates, codes ADEME/BAN). Un candidat hors de ces bornes n'apparaît jamais dans la réponse. * **`scoring_targets` (ancres douces)** — n'excluent rien. Elles influencent uniquement le classement : un candidat proche d'une ancre remonte dans le ranking, un candidat éloigné reste présent mais plus bas. Chaque résultat porte un `score` normalisé de 0 à 100 et un `confidence` (`HIGH`, `MEDIUM`, `LOW`, `REJECT`). L'objet `meta` indique le nombre total de candidats trouvés et un drapeau `ambiguous`, vrai lorsque les deux premiers scores diffèrent de 5 points ou moins — utile pour signaler un match incertain à arbitrer manuellement. Cas d'usage typiques : retrouver le DPE exact d'une annonce, rapprocher un lot d'annonces de leurs DPE pour enrichir un portefeuille, ou estimer l'étiquette énergétique probable d'un bien à partir de ses caractéristiques. ## Payload Le corps de requête est un objet JSON. | Champ | Type | Obligatoire | Sens | | ----------------- | ------- | ----------- | ------------------------------------------------- | | `filters` | object | Non | Pré-filtre dur. Voir le détail ci-dessous. | | `size` | integer | Non | Nombre de candidats retournés (1–20, défaut `5`). | | `scoring_targets` | object | Non | Ancres de scoring. Voir le détail ci-dessous. | ### `filters` (pré-filtre dur) | Champ | Type | Sens | | ---------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------ | | `code_departement_ban` | string | Code département BAN (2–3 caractères, ex. `75`, `2A`, `974`). | | `code_insee_ban` | string | Code INSEE de la commune (5 caractères). | | `code_postal_ban` | string | Code postal (5 chiffres). | | `numero_dpe` | string | Numéro unique du DPE attribué par l'ADEME. Recherche par identifiant. | | `type_batiment` | string | `appartement`, `maison` ou `immeuble`. | | `surface_habitable_logement` | object | Plage de surface habitable en m² : `{ "min": number, "max": number }`. | | `date_etablissement_dpe` | object | Plage de dates d'établissement : `{ "from": "YYYY-MM-DD", "to": "YYYY-MM-DD" }`. | | `location` | object | Filtre géographique : `{ "lat": number, "lon": number, "distance": "10km" }`. Unités acceptées : `m`, `km`, `mi`, `ft`, `yd`, `nmi`. | ### `scoring_targets` (ancres douces) | Champ | Type | Sens | | ------------------------------ | ------ | ---------------------------------------------------------------------------- | | `conso_5_usages_par_m2_ep` | number | Consommation cible en kWh/m²/an. Rapproche les candidats à conso proche. | | `emission_ges_5_usages_par_m2` | number | Émissions GES cibles en kgCO₂/m²/an. | | `etiquette_dpe` | string | Étiquette énergie cible (`A`–`G`). Bonus de score si correspondance. | | `etiquette_ges` | string | Étiquette GES cible (`A`–`G`). Bonus de score si correspondance. | | `nombre_niveau_immeuble` | number | Nombre de niveaux de l'immeuble (0–200). | | `numero_etage_appartement` | number | Étage de l'appartement (−10 à 200 ; valeur négative = sous-sol). | | `periode_construction` | string | Période de construction cible (ex. `avant 1948`, `1948-1974`, `après 2021`). | | `nombre_niveau_logement` | number | Nombre de niveaux du logement (0–200). | ## Réponse L'API retourne un objet `{ data, meta }`. `data` est un tableau de candidats classés par `score` décroissant. Chaque entrée contient : | Champ | Type | Sens | | ------------ | ------ | ---------------------------------------------------------- | | `score` | number | Score de similarité normalisé (0–100). | | `confidence` | string | Niveau de confiance : `HIGH`, `MEDIUM`, `LOW` ou `REJECT`. | | `dpe` | object | Champs clés du DPE retenu (projection Match). | L'objet `dpe` expose notamment `numero_dpe`, `adresse_ban`, `location` (`{ lat, lon }`), `etiquette_dpe`, `etiquette_ges`, `conso_5_usages_par_m2_ep`, `emission_ges_5_usages_par_m2`, `surface_habitable_logement`, `date_etablissement_dpe` et `date_fin_validite_dpe`. `meta` contient : | Champ | Type | Sens | | ----------- | ------- | ------------------------------------------------------------------ | | `total` | number | Nombre total de candidats trouvés (au-delà de `size`). | | `ambiguous` | boolean | `true` si les deux premiers scores diffèrent de 5 points ou moins. | Le contrat exact est documenté plus bas par le bloc OpenAPI (`POST /v2/protected/opendata/dpe/match`). ## Exemple ### Recherche par numéro de DPE Lookup direct par identifiant ADEME : la recherche court-circuite le scoring et retourne le DPE correspondant. ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/opendata/dpe/match \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": { "code_departement_ban": "75", "code_postal_ban": "75009", "numero_dpe": "2675E0698907D" } }' ``` ### Match multi-critères avec ancres de scoring Pré-filtre dur sur la zone, le type et la plage de surface, puis classement affiné par quatre ancres douces. ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/opendata/dpe/match \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": { "code_departement_ban": "75", "code_postal_ban": "75011", "type_batiment": "appartement", "surface_habitable_logement": { "min": 45, "max": 60 }, "location": { "lat": 48.8592, "lon": 2.3789, "distance": "2km" } }, "scoring_targets": { "etiquette_dpe": "D", "etiquette_ges": "C", "conso_5_usages_par_m2_ep": 210, "nombre_niveau_immeuble": 6 }, "size": 5 }' ``` ## Liens utiles * [DPE Search — recherche paginée](/api-v2-reference/dpe-search/recherche-dpe) — liste filtrée de DPE, sans scoring. * [DVF vs Fluximmo](/concepts/dvf-vs-fluximmo) — comprendre les sources de données open data. Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Recherche DPE — diagnostics énergétiques ADEME (API V2) Source: https://doc.fluximmo.io/api-v2-reference/dpe-search/recherche-dpe post /v2/protected/opendata/dpe/search Recherche filtrée et paginée de Diagnostics de Performance Énergétique (DPE) issus de l'open-data ADEME, enrichis du géocodage BAN. ## À quoi ça sert `POST /v2/protected/opendata/dpe/search` interroge le jeu de DPE (Diagnostic de Performance Énergétique) de l'open-data ADEME, enrichi du géocodage BAN et d'enrichissements Fluximmo. Vous passez un objet `filters`, un tri et une pagination, l'endpoint retourne la liste des DPE correspondants. Les filtres couvrent l'identité (`numero_dpe`, `flx_id`), la géographie (département, commune, code postal, recherche par rayon), le type de bâtiment, la période de construction, les surfaces, les étiquettes énergie et GES, la qualité d'isolation, la zone climatique et plusieurs plages de dates. Contrairement à [`/dpe/match`](/api-v2-reference/dpe-match/match-dpe-à-une-annonce-immobilière), cet endpoint n'effectue **aucun scoring** ni rapprochement avec une annonce : il renvoie simplement les DPE qui satisfont vos critères, triés et paginés. ## Payload Le corps de la requête est un objet `{ filters, sortBy, sortOrder, size, cursor }`. | Champ | Type | Obligatoire | Sens | | ----------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `filters` | object | Non | Critères de recherche (voir ci-dessous). Vide ⇒ aucun filtre. | | `sortBy` | enum | Non | Champ de tri : `numero_dpe`, `date_etablissement_dpe`, `type_batiment`, `surface_habitable_logement`, `etiquette_dpe`, `conso_5_usages_par_m2_ep`, `emission_ges_5_usages`, `emission_ges_5_usages_par_m2`, `zone_climatique`. | | `sortOrder` | enum | Non | `asc` ou `desc`. | | `size` | integer | Non | Taille de page, entre `1` et `100`. Défaut `20`. | | `cursor` | string | Non | Curseur de pagination renvoyé par la réponse précédente. | ### Principaux filtres (`filters`) | Filtre | Type | Sens | | ------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `code_departement_ban` | string | Code département BAN (ex : `75`, `2A`, `974`). | | `code_insee_ban` | string | Code INSEE de la commune (5 caractères). | | `code_postal_ban` | string | Code postal BAN (5 chiffres). | | `location` | object | Recherche par rayon : `{ lat, lon, distance }`, `distance` au format `10km`, `500m`, `2mi`… (les trois champs sont requis). | | `type_batiment` | string\[] | Valeurs : `appartement`, `maison`, `immeuble`. | | `periode_construction` | string\[] | Tranches : `avant 1948`, `1948-1974`, `1975-1977`, `1978-1982`, `1983-1988`, `1989-2000`, `2001-2005`, `2006-2012`, `2013-2021`, `après 2021`. | | `etiquette_dpe` | string\[] | Étiquette énergie : `A` à `G`. | | `etiquette_ges` | string\[] | Étiquette GES : `A` à `G`. | | `surface_habitable_logement` | object | Plage `{ min, max }` en m². | | `qualite_isolation_enveloppe` | string\[] | `insuffisante`, `moyenne`, `bonne`, `très bonne`. | | `qualite_isolation_menuiseries` | string\[] | `insuffisante`, `moyenne`, `bonne`, `très bonne`. | | `zone_climatique` | string\[] | `H1a`, `H1b`, `H1c`, `H2b`, `H2c`, `H2d`, `H3`. | | `date_etablissement_dpe` | object | Plage de dates `{ from, to }` (format ISO). | Les filtres numériques (`flx_id`, `surface_habitable_logement`, `conso_5_usages_*`, `emission_ges_*`, `nombre_*`…) acceptent un objet `{ min, max }` ; les filtres de date (`date_etablissement_dpe`, `date_visite_diagnostiqueur`, `date_reception_dpe`, `date_fin_validite_dpe`…) acceptent un objet `{ from, to }`. Le contrat complet est documenté par le bloc OpenAPI ci-dessous. ## Réponse L'API retourne une enveloppe paginée : | Champ | Type | Sens | | --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------- | | `data` | object\[] | Liste des DPE correspondants (`numero_dpe`, `flx_id`, `date_etablissement_dpe`, `etiquette_dpe`, `etiquette_ges`, `location`…). | | `total` | integer | Nombre total de DPE correspondant aux filtres. | | `size` | integer | Taille de page effective. | | `hasMore` | boolean | `true` s'il reste des pages à parcourir. | | `cursor` | string | Curseur à renvoyer dans la requête suivante pour obtenir la page suivante. Présent uniquement quand `hasMore` vaut `true`. | Pour une pagination déterministe, triez sur un champ unique (`numero_dpe`) et propagez le `cursor` de page en page. ## Exemple ### Recherche simple — DPE d'un département ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/opendata/dpe/search \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": { "code_departement_ban": "75" } }' ``` ### Recherche multi-critères — appartements parisiens étiquettes C/D ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/opendata/dpe/search \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": { "code_departement_ban": "75", "type_batiment": ["appartement"], "etiquette_dpe": ["C", "D"], "etiquette_ges": ["C"], "surface_habitable_logement": { "min": 40, "max": 90 }, "date_etablissement_dpe": { "from": "2023-01-01" } }, "sortBy": "date_etablissement_dpe", "sortOrder": "desc", "size": 50 }' ``` ### Recherche par rayon — autour d'un point ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/opendata/dpe/search \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": { "location": { "lat": 48.8592, "lon": 2.3789, "distance": "2km" }, "type_batiment": ["appartement"] }, "sortBy": "numero_dpe", "sortOrder": "asc", "size": 20 }' ``` ## Liens utiles * [Match DPE à une annonce immobilière](/api-v2-reference/dpe-match/match-dpe-à-une-annonce-immobilière) — rapprochement avec scoring entre un DPE et une annonce. * [Filtres communs](/concepts/filtres-communs) — conventions des filtres de plage et d'enum. * [Recherche géographique](/concepts/recherche-geographique) — détail du filtre par rayon (`lat`, `lon`, `distance`). * [DVF vs Fluximmo](/concepts/dvf-vs-fluximmo) — périmètre des sources open-data. Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Match DVF à un bien (V2 stable) Source: https://doc.fluximmo.io/api-v2-reference/dvf-match/match-dvf-à-un-bien-immobilier post /v2/protected/opendata/dvf/match Retourne les transactions DVF les plus similaires à un bien immobilier connu, classées par score de similarité. ## À quoi ça sert `POST /v2/protected/opendata/dvf/match` recherche, parmi les transactions DVF (Demandes de Valeurs Foncières), celles qui ressemblent le plus à un bien que vous connaissez déjà. Le résultat est une liste de candidats classés par similarité décroissante. Là où la [recherche DVF](/api-v2-reference/dvf-search/recherche-dvf) renvoie des transactions paginées sans ordre de pertinence, le Match attribue à chaque candidat un `score` et un niveau de `confidence` pour vous dire à quel point il correspond au bien cible. Le payload combine deux mécanismes distincts : * **`filters` (pré-filtre dur)** — exclut les transactions qui ne respectent pas le critère. Un candidat hors du département, hors de la bande de surface ou hors de la zone géographique n'apparaît jamais dans la réponse. * **`scoring_targets` (ancres douces)** — n'exclut rien. Ces ancres influencent uniquement le classement : un candidat proche du prix cible, avec le même nombre de pièces ou la même nature de transaction, remonte dans la liste. La réponse contient pour chaque entrée : * `score` — similarité normalisée de `0` à `100`. * `confidence` — `HIGH`, `MEDIUM`, `LOW` ou `REJECT`. * `dvf` — la transaction DVF correspondante (champs essentiels). Le bloc `meta.ambiguous` vaut `true` lorsque les deux premiers résultats ont des scores proches (écart ≤ 5 points) : c'est un signal qu'aucun candidat ne se détache nettement et qu'une vérification manuelle peut être utile. ## Payload | Champ | Type | Obligatoire | Sens | | ---------------------------------- | ------------------------------ | ----------- | ---------------------------------------------------------------------------------------------------------------- | | `filters` | objet | Non | Pré-filtre dur. Tout candidat ne respectant pas un filtre est exclu de la réponse. | | `filters.departement_code` | string | Non | Code du département (2-3 caractères, ex : `75`, `2A`, `974`). | | `filters.insee_code` | string | Non | Code INSEE de la commune (5 caractères). | | `filters.postal_code` | string | Non | Code postal (5 chiffres). | | `filters.id` | string | Non | Identifiant unique d'une mutation DVF. Permet un lookup exact d'une transaction connue. | | `filters.property_type` | enum | Non | Type de bien : `Appartement`, `Maison`, `Autre`, `Terrain`, `Tertiaire`, `Dépendance`, `Volume`. | | `filters.surface_m2` | objet `{ min, max }` | Non | Bande de surface habitable en m². | | `filters.transaction_date` | objet `{ from, to }` | Non | Fenêtre de dates de transaction (format `YYYY-MM-DD`). | | `filters.location` | objet `{ lat, lon, distance }` | Non | Zone géographique. `distance` au format `10km`, `500m`, etc. | | `size` | integer | Non | Nombre de candidats retournés. Entre `1` et `20`, défaut `5`. | | `scoring_targets` | objet | Non | Ancres de scoring. Influencent le classement sans exclure de candidats. | | `scoring_targets.price_eur` | number | Non | Prix cible en euros (décroissance gaussienne : plus le candidat est proche, plus il remonte). | | `scoring_targets.main_rooms` | number | Non | Nombre de pièces cible. Les candidats au même `main_rooms` reçoivent un bonus. | | `scoring_targets.is_new_build` | boolean | Non | Bien neuf cible. Les candidats au même `is_new_build` reçoivent un bonus. | | `scoring_targets.transaction_type` | enum | Non | Nature de transaction cible : `Vente`, `Vente en l'etat futur d'achevement`, `Vente terrain a batir`, `Echange`. | Les résultats sont triés par `score` décroissant. ## Réponse L'API retourne un objet `{ data, meta }`. ```json theme={null} { "data": [ { "score": 92.4, "confidence": "HIGH", "dvf": { "id": "2014-871319", "transaction_date": "2014-06-12T00:00:00.000Z", "transaction_type": "Vente", "price_eur": 265788, "price_per_m2_living_eur": 558, "postal_code": "83000", "insee_code": "83137", "property_type": "Appartement", "surface_m2": 476, "main_rooms": 3, "is_new_build": false, "location": { "lat": 43.12, "lon": 5.93 } } } ], "meta": { "total": 2, "ambiguous": false } } ``` * `data[].score` — similarité de `0` à `100`. * `data[].confidence` — `HIGH` / `MEDIUM` / `LOW` / `REJECT`. * `data[].dvf` — sous-ensemble projeté de la transaction DVF (`id`, `transaction_date`, `transaction_type`, `price_eur`, `surface_m2`, `main_rooms`, `is_new_build`, `location`, etc.). * `meta.total` — nombre total de candidats trouvés après pré-filtrage. * `meta.ambiguous` — `true` si l'écart de score entre les deux premiers résultats est ≤ 5 points. Le contrat complet champ par champ est décrit par le bloc OpenAPI plus bas (`POST /v2/protected/opendata/dvf/match`). ## Exemple ### Lookup exact par `id` Lorsque vous connaissez déjà l'identifiant de mutation, le pré-filtre `id` cible une transaction précise. Le résultat est unique avec un `score` de `100`. ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/opendata/dvf/match \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": { "departement_code": "83", "postal_code": "83000", "id": "2014-871319" } }' ``` ### Match multi-critères avec ancres de scoring Cas typique : vous décrivez un bien (zone, type, bande de surface) via `filters`, puis vous affinez le classement avec `scoring_targets` (prix, pièces, nature de transaction). ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/opendata/dvf/match \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": { "departement_code": "83", "postal_code": "83000", "property_type": "Appartement", "surface_m2": { "min": 450, "max": 500 }, "location": { "lat": 43.1242, "lon": 5.928, "distance": "5km" } }, "scoring_targets": { "price_eur": 265000, "main_rooms": 3, "is_new_build": false, "transaction_type": "Vente" }, "size": 5 }' ``` ### Match d'un bien neuf (VEFA) L'ancre `is_new_build: true` combinée à la nature `Vente en l'etat futur d'achevement` (l'enum contient des apostrophes, à échapper en shell) favorise les transactions de logements neufs. ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/opendata/dvf/match \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": { "departement_code": "83", "postal_code": "83000", "property_type": "Maison", "surface_m2": { "min": 100, "max": 140 } }, "scoring_targets": { "price_eur": 450000, "is_new_build": true, "transaction_type": "Vente en l'\''etat futur d'\''achevement" }, "size": 3 }' ``` ## Liens utiles * [Recherche DVF](/api-v2-reference/dvf-search/recherche-dvf) — lister les transactions DVF par filtres, sans classement. * [DVF vs Fluximmo](/concepts/dvf-vs-fluximmo) — différence entre données DVF (transactions officielles) et annonces Fluximmo. Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Recherche DVF — transactions immobilières notariales Source: https://doc.fluximmo.io/api-v2-reference/dvf-search/recherche-dvf post /v2/protected/opendata/dvf/search Recherche filtrée et paginée des transactions immobilières DVF (prix de vente notariaux) en France. ## À quoi ça sert `POST /v2/protected/opendata/dvf/search` interroge la base DVF (Demandes de Valeurs Foncières), qui recense les transactions immobilières issues des actes notariés. Vous fournissez des `filters` (localisation, type de bien, type de mutation, fourchettes de prix et de surface, date) et l'endpoint retourne les mutations correspondantes, triées et paginées. C'est un endpoint de **recherche brute** : il renvoie les transactions telles qu'elles existent dans DVF, sans scoring ni pondération. Si vous cherchez les ventes les plus comparables à un bien donné (avec un score de similarité), utilisez plutôt [`/dvf/match`](/api-v2-reference/dvf-match/match-dvf-à-un-bien-immobilier). La pagination est par curseur : reprenez le champ `cursor` de la réponse pour obtenir la page suivante. ## Payload | Champ | Type | Obligatoire | Sens | | ----------- | ------- | ----------- | ----------------------------------------------------------------------------------------------------------------------- | | `filters` | objet | Non | Critères de recherche (voir ci-dessous). Sans `filters`, la recherche porte sur l'ensemble des transactions. | | `sortBy` | enum | Non | Champ de tri : `id`, `transaction_date`, `price_eur`, `price_per_m2_living_eur`, `price_per_m2_land_eur`, `surface_m2`. | | `sortOrder` | enum | Non | `asc` ou `desc`. | | `size` | integer | Non | Taille de page, entre `1` et `100` (défaut `20`). | | `cursor` | string | Non | Curseur de pagination renvoyé par la réponse précédente. | ### Champs de `filters` | Champ | Type | Sens | | ------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | | `id` | string | Identifiant unique d'une mutation DVF. | | `transaction_date` | `{ from, to }` | Plage de dates (ISO). `from` = date minimale, `to` = date maximale ; les deux sont optionnelles. | | `transaction_type` | string\[] | Nature de la mutation. Valeurs : `Vente`, `Vente en l'etat futur d'achevement`, `Vente terrain a batir`, `Echange`. | | `price_eur` | `{ min, max }` | Fourchette du prix de transaction en euros. | | `price_per_m2_living_eur` | `{ min, max }` | Fourchette du prix au m² de surface habitable. | | `price_per_m2_land_eur` | `{ min, max }` | Fourchette du prix au m² de terrain. | | `location` | `{ lat, lon, distance }` | Recherche géographique autour d'un point. `distance` au format `10km`, `500m`, `2mi`, etc. Les trois champs sont requis ensemble. | | `departement_code` | string | Code département (2-3 caractères, ex : `75`, `2A`, `974`). | | `insee_code` | string | Code INSEE de la commune (5 caractères). | | `postal_code` | string | Code postal (5 chiffres). | | `iris_code` | string | Code IRIS (9 chiffres). | | `iris_type` | string\[] | Type d'IRIS : `H` (habitat), `Z` (zone), `A` (activité), `D` (divers). | | `property_type` | string\[] | Type de bien : `Appartement`, `Maison`, `Autre`, `Terrain`, `Tertiaire`, `Dépendance`, `Volume`. | | `surface_m2` | `{ min, max }` | Fourchette de surface habitable principale en m². | | `main_rooms` | `{ min, max }` | Fourchette du nombre de pièces principales. | | `land_surface_m2` | `{ min, max }` | Fourchette de surface de terrain en m². | | `is_new_build` | boolean | Logement neuf (VEFA ou première vente). | | `is_open_market` | boolean | Transaction sur le marché libre (hors adjudication/expropriation). | | `carrez_surface_m2` | `{ min, max }` | Fourchette de surface loi Carrez en m². | | `soil_surface_m2` | `{ min, max }` | Fourchette de surface au sol du bâti en m². | | `garden_surface_m2` | `{ min, max }` | Fourchette de surface de jardin en m². | ## Réponse L'API retourne une enveloppe paginée : | Champ | Type | Sens | | --------- | -------- | ----------------------------------------------------------------------------- | | `data` | objet\[] | Liste des mutations DVF correspondant aux filtres. | | `total` | integer | Nombre total de transactions correspondant à la recherche. | | `size` | integer | Taille de page effective. | | `hasMore` | boolean | `true` s'il reste des résultats à paginer. | | `cursor` | string | Curseur à réinjecter dans le champ `cursor` du payload pour la page suivante. | Chaque élément de `data` contient notamment `id`, `transaction_date`, `transaction_type`, `price_eur`, `price_per_m2_living_eur`, `property_type`, `surface_m2`, `main_rooms`, `departement_code`, `insee_code`, `postal_code`, `location` et `is_new_build`. Le contrat exact est documenté plus bas par le bloc OpenAPI. ## Exemple ### Appartements vendus à Toulon, du plus cher au moins cher ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/opendata/dvf/search \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": { "departement_code": "83", "postal_code": "83000", "property_type": ["Appartement"], "transaction_type": ["Vente"], "transaction_date": { "from": "2023-01-01", "to": "2024-12-31" } }, "sortBy": "price_eur", "sortOrder": "desc", "size": 5 }' ``` ### Appartements neufs en VEFA, triés par prix au m² habitable ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/opendata/dvf/search \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": { "departement_code": "83", "transaction_type": ["Vente en l'\''etat futur d'\''achevement"], "is_new_build": true, "property_type": ["Appartement"] }, "sortBy": "price_per_m2_living_eur", "sortOrder": "asc", "size": 20 }' ``` La valeur `Vente en l'etat futur d'achevement` contient deux apostrophes. En shell, échappez-les comme dans l'exemple (`'\''`) ou utilisez un fichier JSON via `-d @payload.json`. ### Recherche géographique autour d'un point ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/opendata/dvf/search \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": { "location": { "lat": 43.1242, "lon": 5.928, "distance": "5km" }, "property_type": ["Maison"], "surface_m2": { "min": 80, "max": 200 }, "price_eur": { "min": 200000, "max": 600000 } }, "sortBy": "transaction_date", "sortOrder": "desc", "size": 10 }' ``` ## Liens utiles * [Match DVF à un bien immobilier](/api-v2-reference/dvf-match/match-dvf-à-un-bien-immobilier) — pour obtenir les transactions les plus comparables à un bien, avec score de similarité. * [DVF vs Fluximmo](/concepts/dvf-vs-fluximmo) — différences entre les transactions notariées DVF et les annonces du marché Fluximmo. Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Estimation expérimentale Source: https://doc.fluximmo.io/api-v2-reference/estimations/estimate-the-price-of-a-property post /v2/protected/experimental/estimate Estimation avec modèle expérimental (comparaison de modèles externes). ## À quoi ça sert `POST /v2/protected/experimental/estimate` est l'endpoint **expérimental** qui retourne, en plus de l'estimation centrale Fluximmo, une comparaison avec d'autres modèles externes ou variantes en cours d'évaluation. Il est destiné aux équipes qui veulent benchmarker leur prix de référence ou comprendre le scoring multi-modèles avant qu'une variante ne passe en production. L'output inclut des champs supplémentaires (résultats par modèle, scoring relatif) qui ne sont pas figés : **le contrat peut évoluer** au fil des itérations. **BETA** : la structure de la réponse peut changer sans préavis majeur. N'intégrez pas cet endpoint dans un flux critique de production. Pour un usage stable, utilisez [`/estimate/property/lite`](/api-v2-reference/estimations/lite-property-estimation). ## Exemple ### Appartement Paris — comparaison multi-modèles ```bash curl theme={null} curl -X POST https://api.fluximmo.io/v2/protected/experimental/estimate \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "property": { "lat": 48.8589, "lon": 2.3789, "propertyType": "FLAT", "surfaceArea": 45, "roomsCount": 2, "bedroomsCount": 1 } }' ``` ```python Python theme={null} import os, requests resp = requests.post( "https://api.fluximmo.io/v2/protected/experimental/estimate", headers={"x-api-key": os.environ['FLUXIMMO_API_KEY']}, json={ "property": { "lat": 48.8589, "lon": 2.3789, "propertyType": "FLAT", "surfaceArea": 45, "roomsCount": 2, "bedroomsCount": 1, } }, timeout=30, ) print(resp.json()) ``` ```javascript Node theme={null} const resp = await fetch("https://api.fluximmo.io/v2/protected/experimental/estimate", { method: "POST", headers: { "x-api-key": process.env.FLUXIMMO_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ property: { lat: 48.8589, lon: 2.3789, propertyType: "FLAT", surfaceArea: 45, roomsCount: 2, bedroomsCount: 1, }, }), }); console.log(await resp.json()); ``` ## Liens utiles * [Estimation lite (STABLE)](/api-v2-reference/estimations/lite-property-estimation) * [Concept · Property vs Advert](/concepts/property-vs-advert) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Estimation lite (V2) Source: https://doc.fluximmo.io/api-v2-reference/estimations/lite-property-estimation post /v2/protected/estimate/property/lite Estimation rapide du prix d'un bien (modèle V2 production). ## À quoi ça sert `POST /v2/protected/estimate/property/lite` retourne une estimation AVM (*Automated Valuation Model*) du prix d'un bien immobilier en France, en quelques centaines de millisecondes. C'est le modèle V2 de production de Fluximmo, entraîné sur l'historique d'annonces et de transactions agrégé par la plateforme. L'endpoint accepte un payload synthétique : **localisation** (`location.locationCoordinate.location` au format `[lng, lat]`), **caractéristiques du bien** (`habitation.surface.total`, `habitation.roomCount`, `habitation.bedroomCount`), **type de bien** (`CLASS_FLAT` ou `CLASS_HOUSE`) et **type d'offre** (`OFFER_BUY`). Il retourne un prix central (`data.price`), un intervalle (`data.priceRange.lower` / `upper`) et des métadonnées optionnelles indiquant la qualité de l'estimation pour la zone considérée. **Cas d'usage typiques** : pricing automatique côté agent, peer comparison (comparer un bien à son marché local), pré-qualification d'opportunité, alimentation d'une UI d'estimation grand public, scoring de portefeuille. ## Exemples ### E1 — Appartement Paris (75001) ```bash curl theme={null} curl -X POST https://api.fluximmo.io/v2/protected/estimate/property/lite \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "location": { "locationCoordinate": { "location": [2.3376, 48.8606] } }, "habitation": { "surface": { "total": 60 }, "roomCount": 3, "bedroomCount": 2 }, "type": "CLASS_FLAT", "offer": { "type": "OFFER_BUY" } }' ``` ```python Python theme={null} import os, requests resp = requests.post( "https://api.fluximmo.io/v2/protected/estimate/property/lite", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, json={ "location": {"locationCoordinate": {"location": [2.3376, 48.8606]}}, "habitation": {"surface": {"total": 60}, "roomCount": 3, "bedroomCount": 2}, "type": "CLASS_FLAT", "offer": {"type": "OFFER_BUY"}, }, timeout=30, ) print(resp.json()) ``` ```javascript Node theme={null} const resp = await fetch("https://api.fluximmo.io/v2/protected/estimate/property/lite", { method: "POST", headers: { "x-api-key": process.env.FLUXIMMO_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ location: { locationCoordinate: { location: [2.3376, 48.8606] } }, habitation: { surface: { total: 60 }, roomCount: 3, bedroomCount: 2 }, type: "CLASS_FLAT", offer: { type: "OFFER_BUY" }, }), }); console.log(await resp.json()); ``` ### E2 — Maison Bretagne (110m², 4 pièces) ```bash curl theme={null} curl -X POST https://api.fluximmo.io/v2/protected/estimate/property/lite \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "location": { "locationCoordinate": { "location": [-4.4861, 48.3904] } }, "habitation": { "surface": { "total": 110 }, "roomCount": 4, "bedroomCount": 3 }, "land": { "surface": 350 }, "type": "CLASS_HOUSE", "offer": { "type": "OFFER_BUY" } }' ``` ```python Python theme={null} import os, requests resp = requests.post( "https://api.fluximmo.io/v2/protected/estimate/property/lite", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, json={ "location": {"locationCoordinate": {"location": [-4.4861, 48.3904]}}, "habitation": {"surface": {"total": 110}, "roomCount": 4, "bedroomCount": 3}, "land": {"surface": 350}, "type": "CLASS_HOUSE", "offer": {"type": "OFFER_BUY"}, }, timeout=30, ) print(resp.json()) ``` ```javascript Node theme={null} const resp = await fetch("https://api.fluximmo.io/v2/protected/estimate/property/lite", { method: "POST", headers: { "x-api-key": process.env.FLUXIMMO_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ location: { locationCoordinate: { location: [-4.4861, 48.3904] } }, habitation: { surface: { total: 110 }, roomCount: 4, bedroomCount: 3 }, land: { surface: 350 }, type: "CLASS_HOUSE", offer: { type: "OFFER_BUY" }, }), }); console.log(await resp.json()); ``` ## Liens utiles * [Concept · Property vs Advert](/concepts/property-vs-advert) * [Géocoder une adresse avant estimation](/api-geocoding-reference/search/recherche-par-géocodage-direct) * [Playbook · Estimer un bien](/playbooks/estimer-un-bien) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Statistiques prix par zone Source: https://doc.fluximmo.io/api-v2-reference/market-analytics/prices-per-area-statistics post /v2/protected/analytics/market/price_per_area/statistics Statistiques prix moyens / au m² par zone géographique. # Tension immobilière par ville Source: https://doc.fluximmo.io/api-v2-reference/market-analytics/tension-city-statistics post /v2/protected/analytics/market/tension/statistics Indicateur de tension immobilière (offre vs demande) par ville. # Réactiver une alerte properties Source: https://doc.fluximmo.io/api-v2-reference/properties-alerts/activate-a-property-search-alert get /v2/protected/properties/search/alerts/{alertId}/activate Réactiver une alerte properties précédemment désactivée. `GET /v2/protected/properties/search/alerts/{alertId}/activate` rebascule à `true` le flag `activated` d'une alerte mise en pause via [`DELETE`](/api-v2-reference/properties-alerts/deactivate-a-property-search-alert). Le verbe HTTP est `GET` pour des raisons historiques (legacy). Idempotent — réactiver une alerte déjà active n'a pas d'effet. **Comportement post-réactivation** : l'alerte reprend les nouveaux matches `ALERT_MATCH_CREATED` à partir de l'instant `t` de réactivation. Si `ALERT_MATCH_MERGED` est dans le tableau `match`, les fusions reprennent uniquement sur les **properties qui étaient déjà matchées avant la désactivation** — pas de rattrapage des fusions survenues pendant la pause. ## Cas d'usage * **Pause saisonnière** — désactiver hors campagne, réactiver pour reprendre la collecte. * **Bascule budgétaire** — couper les alertes les moins prioritaires en pic de volume. ## Cross-links * [Désactiver une alerte](/api-v2-reference/properties-alerts/deactivate-a-property-search-alert) * [Match types & cycle alerte](/concepts/match-types-cycle-alerte) # Créer une alerte properties Source: https://doc.fluximmo.io/api-v2-reference/properties-alerts/create-a-new-alert-for-this-property-search put /v2/protected/properties/search/alerts Alerte temps réel sur les properties matchant des filtres. Webhook IDs only. **Avant de créer une alerte, vérifier que ton webhook répond `200` aux données de sample disponibles sans auth (`GET /v2/sample/webhook/properties`).** `PUT /v2/protected/properties/search/alerts` crée une **alerte properties** : à chaque property qui matche `search_query`, Fluximmo POSTe sur `webhook_url` un payload **IDs only** que vous refetchez ensuite via [`GET /v2/protected/properties/{flxId}`](/api-v2-reference/properties/get-a-property-by-its-flx_id) ou en bulk via [`/properties/bulk`](/api-v2-reference/properties/retreive-properties-by-their-flx_ids). Le `search_query` réutilise le schéma de [`POST /v2/protected/properties/search`](/api-v2-reference/properties-search/search-properties). **Pas de backfill à la création.** Seules les properties créées / mises à jour **après** la création de l'alerte déclenchent un webhook. ## Champs clés * `search_query` — payload `FilterProperty`, identique au search. * `webhook_url` — endpoint HTTPS public (cf. [Webhooks](/concepts/webhooks)). * `match` — tableau parmi `ALERT_MATCH_CREATED` (nouvelle property qualifiée) et `ALERT_MATCH_MERGED` (uniquement quand une nouvelle advert vient d'être fusionnée dans une property **déjà matchée** — pas à la création). Voir [Match types & cycle alerte](/concepts/match-types-cycle-alerte). * `search_name` — libellé humain. * `context` — objet `QueryContextDto { metadata: object }` propagé tel quel dans chaque webhook. Exemple : `"context": {"metadata": {}}` (objet, **pas** une string sérialisée). **Combo standard recommandé : `match: ["ALERT_MATCH_CREATED"]` seul.** L'ajout de `ALERT_MATCH_MERGED` est un mode avancé qui multiplie le volume webhook (chaque enrichissement / fusion d'une property déjà matchée déclenche un nouvel appel). À réserver aux cas où vous voulez aussi recevoir les fusions a posteriori (analytics historique, scoring continu). ## Cas d'usage * **Marketplace / réplication BDD** — synchroniser un référentiel de biens dédupliqués. * **Analytics fintech** — capter le flux de nouvelles properties d'un département pour stats temps réel. * **AVM** — alimenter un modèle d'estimation au fil de l'eau avec des biens nouvellement vus. ## Exemples ### C1 ★★ — Combo standard (default `match=[CREATED]`) Achat appartement Paris 1er, exclusion des biens totalement offline. Volume webhook minimal. ```json theme={null} { "search_query": { "filterProperty": { "location": [{ "postalCode": "75001" }], "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "meta": { "isTotallyOffline": false } } }, "webhook_url": "https://votre-domaine.com/webhooks/fluximmo-properties", "match": ["ALERT_MATCH_CREATED"], "search_name": "Properties Paris achat appartement" } ``` ### C2 — Département + `isTotallyOffline=false` (analytics fintech) Toutes les nouvelles properties d'achat dans un département, exclusion offline. ```json theme={null} { "search_query": { "filterProperty": { "location": [{ "department": "13" }], "type": ["CLASS_FLAT", "CLASS_HOUSE"], "offer": [{ "type": "OFFER_BUY" }], "meta": { "isTotallyOffline": false } } }, "webhook_url": "https://analytics.example.com/webhooks/fluximmo", "match": ["ALERT_MATCH_CREATED"], "search_name": "Bouches-du-Rhône achat - analytics" } ``` ### C3 — `match=[CREATED, MERGED]` AVANCÉ Recevoir aussi les fusions (= une property déjà matchée a été enrichie : nouvel advert rattaché, prix recalculé, etc.). ```json theme={null} { "search_query": { "filterProperty": { "location": [{ "postalCode": "75009" }, { "postalCode": "75017" }], "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "price": { "latest": { "value": { "min": 50000, "max": 358000 } } }, "meta": { "isTotallyOffline": false } } }, "webhook_url": "https://votre-domaine.com/webhooks/fluximmo", "match": ["ALERT_MATCH_CREATED", "ALERT_MATCH_MERGED"], "search_name": "Paris 9/17 - veille enrichie" } ``` Ce mode génère plus de webhooks **uniquement quand une nouvelle advert est fusionnée** dans une property déjà matchée — pas à chaque check de fraîcheur. Sur des filtres larges et un volume d'ingestion élevé, mesurez votre capacité receiver avant d'activer `ALERT_MATCH_MERGED`. ## Cross-links * [Match types & cycle alerte](/concepts/match-types-cycle-alerte) * [Property vs Advert](/concepts/property-vs-advert) * [Webhooks](/concepts/webhooks) * [Search properties](/api-v2-reference/properties-search/search-properties) # Créer plusieurs alertes properties Source: https://doc.fluximmo.io/api-v2-reference/properties-alerts/create-many-new-alert-for-property-search put /v2/protected/properties/search/alerts/many Création bulk d'alertes properties (multi-territoires). `PUT /v2/protected/properties/search/alerts/many` crée **N alertes en un seul appel**. Pratique pour onboarder un client avec 50 zones distinctes (un département par alerte, ou une ville par alerte) sans saturer la latence. La limite max et le comportement exact en cas d'erreur partielle (atomicité tout-ou-rien vs best-effort) sont définis côté DTO `CreatePropertySearchAlertsManyDto`. Référez-vous au schéma OpenAPI ci-dessous pour les contraintes en vigueur. En cas de doute, faites un pilote à 5 alertes avant de pousser à 50. Chaque alerte du tableau respecte la même grille de champs que l'endpoint unitaire : `search_query`, `webhook_url`, `match`, `search_name`, `context`. Voir [Créer une alerte properties](/api-v2-reference/properties-alerts/create-a-new-alert-for-this-property-search) pour le détail. ## Cas d'usage * **Multi-territoires** — un client B2B couvre 30 départements. Plutôt que 30 PUT séquentiels, un seul appel bulk. * **Onboarding template** — appliquer un *preset* d'alertes (un par segment de portefeuille) à chaque nouveau client interne. * **Reset périodique** — recréer en bulk un parc d'alertes après une migration majeure. Recréer en bulk **n'apporte pas l'historique** des anciennes alertes. Si vous voulez conserver les events sur les biens déjà matchés, préférez [PATCH](/api-v2-reference/properties-alerts/update-the-search-criteria-of-a-property-alert) sur les alertes existantes plutôt que delete + bulk recreate. ## Cross-links * [Créer une alerte properties (unitaire)](/api-v2-reference/properties-alerts/create-a-new-alert-for-this-property-search) * [Lister mes alertes](/api-v2-reference/properties-alerts/get-all-your-property-alerts) * [Match types & cycle alerte](/concepts/match-types-cycle-alerte) # Désactiver une alerte properties Source: https://doc.fluximmo.io/api-v2-reference/properties-alerts/deactivate-a-property-search-alert delete /v2/protected/properties/search/alerts/{alertId} Désactiver une alerte properties (l'historique reste consultable). ⚠️ Malgré le verbe `DELETE`, **cet appel ne supprime pas l'alerte** : il bascule simplement le flag `activated` à `false`. La configuration et les liens vers les properties historiquement matchées restent **consultables** ; l'alerte peut être [réactivée](/api-v2-reference/properties-alerts/activate-a-property-search-alert) à tout moment sans perte. Tant que l'alerte est désactivée, **aucun webhook** n'est émis (ni `CREATED`, ni `MERGED`). ## Cas d'usage * **Pause temporaire** — couper la livraison de webhooks sans perdre l'historique. * **Migration douce** — désactiver une ancienne alerte et lancer la nouvelle config en parallèle pour comparer les volumes (en sachant que la nouvelle alerte démarre **vide d'historique**). ## Cross-links * [Réactiver une alerte](/api-v2-reference/properties-alerts/activate-a-property-search-alert) * [Modifier une alerte](/api-v2-reference/properties-alerts/update-the-search-criteria-of-a-property-alert) * [Historique des matches](/api-v2-reference/properties-alerts/get-history-of-match-properties-by-its-saved-search-flxid) # Lister mes alertes properties Source: https://doc.fluximmo.io/api-v2-reference/properties-alerts/get-all-your-property-alerts get /v2/protected/properties/search/alerts/all Liste de toutes les alertes properties actives sur la clé. `GET /v2/protected/properties/search/alerts/all` retourne **toutes** les alertes properties associées à votre clé API, actives **et** désactivées. Pas de pagination. ## Cas d'usage * **Audit / inventaire** — lister le parc d'alertes avant nettoyage ou rotation. * **Back-office** — afficher dans une UI interne les alertes par client / par campagne (via `context`). * **Détection de redondance** — repérer plusieurs alertes au même `webhook_url` couvrant des `search_query` qui se chevauchent. ## Cross-links * [Voir critères d'une alerte](/api-v2-reference/properties-alerts/get-the-search-criteria-of-a-propertyalert) * [Modifier une alerte](/api-v2-reference/properties-alerts/update-the-search-criteria-of-a-property-alert) * [Désactiver une alerte](/api-v2-reference/properties-alerts/deactivate-a-property-search-alert) # Historique des matches d'une alerte Source: https://doc.fluximmo.io/api-v2-reference/properties-alerts/get-history-of-match-properties-by-its-saved-search-flxid get /v2/protected/properties/search/alerts/history/{flxId} Historique des properties matchées par une alerte donnée. `GET /v2/protected/properties/search/alerts/history/{flxId}` retourne l'historique des properties matchées par l'alerte identifiée par `flxId`. Réponse : array de `PropertyDto` complets, pas besoin de refetch. **Pagination offset (`skip` / `take`)** — incohérence assumée vs la pagination cursor (`searchAfterHash`) du search. Cette API a une histoire plus ancienne ; l'unification est sur la roadmap. ## Cas d'usage * **Recover after webhook outage** — votre receiver a été down ? Rejouez les matches manqués via cet endpoint plutôt qu'attendre les retries. * **Audit produit** — combien de matches `CREATED` vs `MERGED` une alerte a-t-elle généré sur 30 jours ? * **Bootstrap / backfill initial** — après création d'une alerte, l'historique se peuple au fil du temps. Cet endpoint vous permet de rejouer ce flux pour réhydrater une BDD client perdue. ## Cross-links * [Créer une alerte properties](/api-v2-reference/properties-alerts/create-a-new-alert-for-this-property-search) * [Webhooks](/concepts/webhooks) * [Match types & cycle alerte](/concepts/match-types-cycle-alerte) # Voir les critères d'une alerte Source: https://doc.fluximmo.io/api-v2-reference/properties-alerts/get-the-search-criteria-of-a-propertyalert get /v2/protected/properties/search/alerts/{alertId} Récupère les filtres d'une alerte properties par flxId. `GET /v2/protected/properties/search/alerts/{alertId}` retourne la configuration complète d'une alerte properties : `search_query`, `webhook_url`, `match`, `search_name`, `context`, `activated`. ## Cas d'usage * **Avant un PATCH** — récupérer la config courante pour préparer une modification ciblée. * **Reproduire en search synchrone** — copier `search_query` et le rejouer via [`POST /v2/protected/properties/search`](/api-v2-reference/properties-search/search-properties) pour comparer ce que matche l'alerte aujourd'hui. * **Debug** — confirmer le `webhook_url` et les `match` types réellement enregistrés. ## Cross-links * [Modifier une alerte](/api-v2-reference/properties-alerts/update-the-search-criteria-of-a-property-alert) * [Search properties](/api-v2-reference/properties-search/search-properties) * [Lister mes alertes](/api-v2-reference/properties-alerts/get-all-your-property-alerts) # Modifier une alerte properties Source: https://doc.fluximmo.io/api-v2-reference/properties-alerts/update-the-search-criteria-of-a-property-alert patch /v2/protected/properties/search/alerts/{alertId} Modifier les filtres d'une alerte sans perdre l'historique des matches. `PATCH /v2/protected/properties/search/alerts/{alertId}` modifie une alerte properties **en place**. Champs modifiables : `search_query`, `webhook_url`, `match`, `search_name`, `context`. ⚠️ **Toujours préférer PATCH à `delete + recreate`.** Le PATCH **conserve** le lien vers les properties déjà matchées : si vous activez ultérieurement `ALERT_MATCH_MERGED`, vous recevrez les fusions des biens déjà capturés. Une recréation perd ce lien. ## Cas d'usage * **Migration webhook** — pivoter `webhook_url` après un changement d'infra sans perdre l'historique. * **Activer les fusions sur une alerte existante** — passer `match` de `["ALERT_MATCH_CREATED"]` à `["ALERT_MATCH_CREATED", "ALERT_MATCH_MERGED"]` pour commencer à recevoir les enrichissements des properties déjà capturées. * **Resserrer un filtre** — ajouter un range prix ou un type sans casser l'historique. ## Cross-links * [Match types & cycle alerte](/concepts/match-types-cycle-alerte) * [Voir les critères](/api-v2-reference/properties-alerts/get-the-search-criteria-of-a-propertyalert) * [Désactiver une alerte](/api-v2-reference/properties-alerts/deactivate-a-property-search-alert) # Compter les properties par filtres Source: https://doc.fluximmo.io/api-v2-reference/properties-analytics/count-properties-for-search-filters post /v2/protected/analytics/property/count Comptage de properties matchant des filtres donnés (analytics légères). # Analytics generales par filtres Source: https://doc.fluximmo.io/api-v2-reference/properties-analytics/properties-general-analytics-for-search-filters post /v2/protected/analytics/property/search Statistiques agrégées (prix moyen, distribution surface) sur des filtres. # Recherche allégée (lite) Source: https://doc.fluximmo.io/api-v2-reference/properties-search/lite-search-properties post /v2/protected/properties/search/lite Variante allégée pour UI temps réel, mobile, autocomplete. Schéma de réponse réduit. Variante allégée du [`POST /v2/protected/properties/search`](/api-v2-reference/properties-search/search-properties) : **100% identique au search full sur le plan fonctionnel**, mais avec un **schéma de réponse réduit** (champs essentiels uniquement), une **surface de filtres réduite** (variantes allégées) et une **doc plus courte / plus simple à lire**. Aucun gain de latence promis — c'est avant tout une variante simplifiée pour intégrations qui n'ont pas besoin de la richesse complète du modèle property. À privilégier pour : auto-complétion d'une UI mobile, prévisualisation de carte, suggestions instantanées, prefetch côté front. À éviter quand vous avez besoin de toute la richesse du modèle property (analytics, AVM, peer comparison) — utilisez la variante full dans ce cas. **Différences vs full** : les filtres suivants ne sont **pas disponibles** dans la version lite : * `adverts[]` (filtres nested sur les annonces source) * `parking` * `process` * `tags` * `hasAnomaly` * `isUrgent` * `cityCoordinate` (utiliser `locationCoordinate` à la place) Tous les autres filtres (`location` administratif, `locationCoordinate` pour les modes géo, `offer`, `type`, `price`, `habitation`, `land`, `meta`) sont disponibles, mais en **variantes allégées** : par exemple `price` ne supporte que `latest` (pas `initial`), et `habitation` est une variante restreinte de la version full. Voir le schéma `FilterPropertyLiteDto` pour le détail exact. ## Cas d'usage * Auto-complétion d'adresse → preview des biens disponibles à mesure que l'utilisateur tape * App mobile : liste paginée rapide, scroll infini * Carte interactive avec markers (peu de champs nécessaires par marker) * Prefetch / cache côté front pour limiter la taille des payloads * Widgets embarqués (extensions, plugins tiers) ## Filtres clés Identiques à la version full (voir [Filtres communs](/concepts/filtres-communs)) à l'exception des filtres listés en intro. Pour la géo, voir [Recherche géographique](/concepts/recherche-geographique). Le filtre `location[].city` est **ignoré** côté moteur de recherche. Utilisez `postalCode`, `inseeCode` ou `department` pour filtrer par zone administrative — sinon le résultat couvre toute la France. ## Exemples ### Exemple 1 — Combo standard adapté lite (Paris achat appartement) Même cas que l'exemple #1 du `search` full, ajusté pour la variante lite (suppression du filtre nested `adverts[]` qui n'est pas supporté). ```json theme={null} { "size": 25, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [{ "postalCode": "75001" }], "type": ["CLASS_FLAT", "CLASS_HOUSE", "CLASS_PROGRAM"], "offer": [{ "type": "OFFER_BUY" }], "price": { "latest": { "value": { "min": 100000, "max": 350000 } } }, "habitation": { "surface": { "total": { "min": 30, "max": 110 } }, "bedroomCount": { "min": 1, "max": 3 } }, "meta": { "isTotallyOffline": false, "firstSeenAt": { "min": "2025-01-01T00:00:00.000Z" } } } } } ``` ```bash curl theme={null} curl -X POST https://api.fluximmo.io/v2/protected/properties/search/lite \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d @payload.json ``` ```python Python theme={null} import requests, os resp = requests.post( "https://api.fluximmo.io/v2/protected/properties/search/lite", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, json={ "size": 25, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [{"postalCode": "75001"}], "type": ["CLASS_FLAT", "CLASS_HOUSE", "CLASS_PROGRAM"], "offer": [{"type": "OFFER_BUY"}], "price": {"latest": {"value": {"min": 100000, "max": 350000}}}, "habitation": { "surface": {"total": {"min": 30, "max": 110}}, "bedroomCount": {"min": 1, "max": 3}, }, "meta": {"isTotallyOffline": False}, } }, }, ) print(resp.json()) ``` ```javascript Node theme={null} const resp = await fetch("https://api.fluximmo.io/v2/protected/properties/search/lite", { method: "POST", headers: { "x-api-key": process.env.FLUXIMMO_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ size: 25, sortBy: "FIRST_SEEN_AT", orderBy: "DESC", search: { filterProperty: { location: [{ postalCode: "75001" }], type: ["CLASS_FLAT", "CLASS_HOUSE", "CLASS_PROGRAM"], offer: [{ type: "OFFER_BUY" }], price: { latest: { value: { min: 100000, max: 350000 } } }, habitation: { surface: { total: { min: 30, max: 110 } }, bedroomCount: { min: 1, max: 3 }, }, meta: { isTotallyOffline: false }, }, }, }), }); console.log(await resp.json()); ``` ### Exemple 2 — Auto-complétion mobile (Paris flat, top 10) Cas UI mobile : filtres minimaux, `size` réduite à 10, tri canonique. Payload de retour minimal pour limiter le coût d'affichage côté front. ```json theme={null} { "size": 10, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [{ "postalCode": "75001" }], "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "meta": { "isTotallyOffline": false } } } } ``` ```bash curl theme={null} curl -X POST https://api.fluximmo.io/v2/protected/properties/search/lite \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d @payload.json ``` ```python Python theme={null} import requests, os resp = requests.post( "https://api.fluximmo.io/v2/protected/properties/search/lite", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, json={ "size": 10, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [{"postalCode": "75001"}], "type": ["CLASS_FLAT"], "offer": [{"type": "OFFER_BUY"}], "meta": {"isTotallyOffline": False}, } }, }, ) print(resp.json()) ``` ```javascript Node theme={null} const resp = await fetch("https://api.fluximmo.io/v2/protected/properties/search/lite", { method: "POST", headers: { "x-api-key": process.env.FLUXIMMO_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ size: 10, sortBy: "FIRST_SEEN_AT", orderBy: "DESC", search: { filterProperty: { location: [{ postalCode: "75001" }], type: ["CLASS_FLAT"], offer: [{ type: "OFFER_BUY" }], meta: { isTotallyOffline: false }, }, }, }), }); console.log(await resp.json()); ``` ## Pagination Identique à [`search` full](/api-v2-reference/properties-search/search-properties#pagination) : cursor `searchAfterHash` retourné dans la réponse, à renvoyer dans le payload suivant. `size` 1-25 recommandé, `sortBy` / `orderBy` à figer entre deux pages pour préserver la stabilité du cursor. # Biens similaires (lite) Source: https://doc.fluximmo.io/api-v2-reference/properties-search/lite-similar-search-properties post /v2/protected/properties/search/lite/similar Trouver des biens similaires à un flxId ou un payload de référence. **Endpoint en BETA.** L'algorithme de scoring de similarité (boosts, distance) est susceptible d'évoluer. Le contrat d'entrée/sortie reste stable. Signalez-nous tout résultat incohérent — nous ajustons les boosts en continu. Recherche de biens similaires à une property de référence (par `flxId` ou par payload). Pratique pour les cas **peer comparison / AVM** : à partir d'un bien donné, trouver les N biens les plus proches en termes de localisation, surface, prix, type. Le scoring repose sur des boosts de pertinence, passés via le champ optionnel `boosts`. À privilégier pour : modules "biens similaires" sur fiche bien, AVM peer-based, recommandations utilisateur, alimentation d'un comparateur. ## Cas d'usage * Module "biens similaires" sur une fiche détaillée (suggestions contextualisées) * AVM (Automated Valuation Model) : prix médian / quartile sur peers proches * Comparateur : trouver 5-10 biens comparables à présenter à un acheteur * Détection d'anomalie de prix : un bien dont les peers sont 30% plus chers * Réassurance vendeur : "voici les biens vendus / en vente comparables" ## Filtres clés Mêmes filtres que [`search/lite`](/api-v2-reference/properties-search/lite-search-properties) (pas d'`adverts[]` nested, pas de `parking`, pas de `process`, pas de `tags`, pas de `hasAnomaly`, pas de `isUrgent`). **Champ additionnel** : `boosts` (objet optionnel) — map `nom de champ → coefficient` pour ajuster le poids de chaque dimension de similarité (ex: pondérer plus fort la distance géo vs la surface). Laissez vide pour utiliser les boosts par défaut Fluximmo. Le filtre `location[].city` est **ignoré** côté moteur de recherche. Utilisez `postalCode`, `inseeCode` ou `department` pour filtrer par zone administrative — sinon le résultat couvre toute la France. ## Exemples ### Exemple 1 — Peer comparison à partir d'un quartier + critères de référence Cas AVM peer-based : on cherche des biens similaires à un T3 \~70 m² à vendre dans le 11e arrondissement de Paris, dans un rayon de 1.5 km autour d'un point de référence, budget proche, même type. Le moteur retourne les biens les plus similaires triés par score. Pour une similarité **à partir d'un `flxId` existant**, fetchez d'abord la property cible via [`GET /v2/protected/properties/{flxId}`](/api-v2-reference/properties/get-a-property-by-its-flx_id) pour récupérer ses caractéristiques, puis utilisez-les comme filtres dans le payload ci-dessous. Cet endpoint n'accepte pas `flxId` directement comme entrée. ```json theme={null} { "size": 10, "sortBy": "RELEVANCE", "orderBy": "DESC", "search": { "filterProperty": { "location": [ { "locationCoordinate": { "location": { "geoDistance": { "pin": { "lat": 48.8566, "lon": 2.3776 }, "distanceKm": 1.5 } } } } ], "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "price": { "latest": { "value": { "min": 450000, "max": 750000 } } }, "habitation": { "surface": { "total": { "min": 55, "max": 90 } }, "bedroomCount": { "min": 2, "max": 3 } }, "meta": { "isTotallyOffline": false } } }, "boosts": { "habitation.surface.total": 2.0, "price.latest.value": 1.5 } } ``` ```bash curl theme={null} curl -X POST https://api.fluximmo.io/v2/protected/properties/search/lite/similar \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d @payload.json ``` ```python Python theme={null} import requests, os resp = requests.post( "https://api.fluximmo.io/v2/protected/properties/search/lite/similar", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, json={ "size": 10, "sortBy": "RELEVANCE", "orderBy": "DESC", "search": { "filterProperty": { "location": [ { "locationCoordinate": { "location": { "geoDistance": { "pin": {"lat": 48.8566, "lon": 2.3776}, "distanceKm": 1.5, } } } } ], "type": ["CLASS_FLAT"], "offer": [{"type": "OFFER_BUY"}], "price": {"latest": {"value": {"min": 450000, "max": 750000}}}, "habitation": { "surface": {"total": {"min": 55, "max": 90}}, "bedroomCount": {"min": 2, "max": 3}, }, "meta": {"isTotallyOffline": False}, } }, "boosts": { "habitation.surface.total": 2.0, "price.latest.value": 1.5, }, }, ) print(resp.json()) ``` ```javascript Node theme={null} const resp = await fetch("https://api.fluximmo.io/v2/protected/properties/search/lite/similar", { method: "POST", headers: { "x-api-key": process.env.FLUXIMMO_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ size: 10, sortBy: "RELEVANCE", orderBy: "DESC", search: { filterProperty: { location: [ { locationCoordinate: { location: { geoDistance: { pin: { lat: 48.8566, lon: 2.3776 }, distanceKm: 1.5, }, }, }, }, ], type: ["CLASS_FLAT"], offer: [{ type: "OFFER_BUY" }], price: { latest: { value: { min: 450000, max: 750000 } } }, habitation: { surface: { total: { min: 55, max: 90 } }, bedroomCount: { min: 2, max: 3 }, }, meta: { isTotallyOffline: false }, }, }, boosts: { "habitation.surface.total": 2.0, "price.latest.value": 1.5, }, }), }); console.log(await resp.json()); ``` ## Pagination Cursor `searchAfterHash` identique à [`search` full](/api-v2-reference/properties-search/search-properties#pagination). `size` accepte jusqu'à 100 sur cet endpoint (à confirmer selon votre quota). `sortBy=RELEVANCE` est le tri naturel ici (score de similarité), mais vous pouvez forcer un autre critère (`PRICE`, `FIRST_SEEN_AT`) si besoin. # Rechercher des biens (search PROPERTIES) Source: https://doc.fluximmo.io/api-v2-reference/properties-search/search-properties post /v2/protected/properties/search Recherche analytique avec déduplication multi-portails. Filtres riches, géo avancée, pagination cursor. Cet endpoint est l'**outil principal** pour interroger le catalogue Fluximmo dédupliqué. Une ligne de réponse = un bien physique unique (`flxId`), agrégé à partir d'une ou plusieurs annonces source. Utilisez-le dès que vous avez besoin d'un état du marché propre, sans doublons inter-portails — idéal pour la recherche multi-portail BAAS. Côté ADVERTS, il n'existe pas d'endpoint de search public : les annonces brutes (1 URL = 1 advert) sont accessibles uniquement via une alerte webhook ADVERT. Le payload de filtres d'une alerte advert est identique à celui d'un search property côté schéma. Voir [Property vs Advert](/concepts/property-vs-advert) pour la décision. Variante allégée : [`POST /v2/protected/properties/search/lite`](/api-v2-reference/properties-search/lite-search-properties) — schéma de réponse réduit, perf supérieure, filtres restreints. Utilisez-la pour UI temps réel, mobile, autocomplete. ## Cas d'usage * Cartographier l'offre d'un secteur (zone géographique × type × budget) * Construire un AVM / peer comparison à partir d'un combo prix + surface + chambres * Alimenter un dashboard analytique (suivi mensuel, séries temporelles via `meta.firstSeenAt`) * Sourcer des biens pour un chasseur / mandataire (sortBy `FIRST_SEEN_AT DESC`) * Backtester une thèse d'investissement (rendement m², rotation de stock) ## Filtres clés **Champs déclarés par les annonceurs** — plus tu rajoutes de filtres spécifiques (ex. `numberOfToilets`, `floor`, etc.), plus tu risques de louper des annonces : peu d'annonceurs renseignent les champs très détaillés. En production, démarre avec un set minimal (`location` + `type` + `offer` + `price`) puis affine si besoin. Le payload se compose de `search.filterProperty` (les filtres) + `size`, `sortBy`, `orderBy`, `searchAfterHash` (pagination & tri) + `search.fullTexts` / `search.keywords` (recherche textuelle, max 20 items chacun). | Filtre | Rôle | Voir | | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------- | | `location[]` | Champs administratifs + `geoBoundingBox` + `geoDistance` (OR multi-zones) | [Recherche géographique](/concepts/recherche-geographique) | | `offer[].type` | `OFFER_BUY`, `OFFER_RENT`, etc. | [Filtres communs](/concepts/filtres-communs) | | `type[]` | Classes de biens : `CLASS_FLAT`, `CLASS_HOUSE`, `CLASS_PROGRAM`, etc. | [Filtres communs](/concepts/filtres-communs) | | `price.{initial,latest}` + `price.{currency,scope,variation,isAuction,warrantyDeposit}` | Prix valeur (`value`), par m² (`valuePerArea`), variation, scope, currency | [Filtres communs](/concepts/filtres-communs) | | `habitation` | Surface, roomCount, bedroomCount, bathroomCount, EPC, heat | [Filtres communs](/concepts/filtres-communs) | | `meta.isTotallyOffline` | Exclure les biens offline (best practice prod : `false`) | [Filtres communs](/concepts/filtres-communs) | | `meta.{firstSeenAt,lastSeenAt,lastUpdatedAt}` | Filtres temporels | [Filtres communs](/concepts/filtres-communs) | | `adverts[]` (nested) | Filtres sur les annonces source (`isOnline`, `isPro`, `source`...) | [Filtres communs](/concepts/filtres-communs) | | `land`, `parking`, `tags`, `process`, `hasAnomaly`, `isUrgent` | Filtres avancés / niches | DTO référence | Le filtre `location[].city` est **ignoré** côté moteur de recherche. Utilisez `postalCode`, `inseeCode` ou `department` pour filtrer par zone administrative — sinon le résultat couvre toute la France. ## Exemples Tous les payloads ci-dessous utilisent le wrapper officiel `{ size, sortBy, orderBy, search: { filterProperty: { ... } } }` conformément au DTO `SearchPropertyPayloadDto`. ### Exemple 1 — Combo standard dominant (Paris achat appartement) Le combo prod le plus fréquent : Paris 1er, achat appartement/maison/programme neuf, budget 100-350 k€, surface 30-110 m², 1-3 chambres, biens offline exclus, recherche limitée aux annonces vues à partir du 1er janvier 2025, tri du plus récent au plus ancien. ```json theme={null} { "size": 25, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [{ "postalCode": "75001" }], "type": ["CLASS_FLAT", "CLASS_HOUSE", "CLASS_PROGRAM"], "offer": [{ "type": "OFFER_BUY" }], "price": { "initial": { "value": { "min": 100000, "max": 350000 } } }, "habitation": { "surface": { "total": { "min": 30, "max": 110 } }, "bedroomCount": { "min": 1, "max": 3 } }, "meta": { "isTotallyOffline": false, "firstSeenAt": { "min": "2025-01-01T00:00:00.000Z" } } } } } ``` ```bash curl theme={null} curl -X POST https://api.fluximmo.io/v2/protected/properties/search \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d @payload.json ``` ```python Python theme={null} import requests, os resp = requests.post( "https://api.fluximmo.io/v2/protected/properties/search", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, json={ "size": 25, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [{"postalCode": "75001"}], "type": ["CLASS_FLAT", "CLASS_HOUSE", "CLASS_PROGRAM"], "offer": [{"type": "OFFER_BUY"}], "price": {"initial": {"value": {"min": 100000, "max": 350000}}}, "habitation": { "surface": {"total": {"min": 30, "max": 110}}, "bedroomCount": {"min": 1, "max": 3}, }, "meta": { "isTotallyOffline": False, "firstSeenAt": {"min": "2025-01-01T00:00:00.000Z"}, }, } }, }, ) print(resp.json()) ``` ```javascript Node theme={null} const resp = await fetch("https://api.fluximmo.io/v2/protected/properties/search", { method: "POST", headers: { "x-api-key": process.env.FLUXIMMO_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ size: 25, sortBy: "FIRST_SEEN_AT", orderBy: "DESC", search: { filterProperty: { location: [{ postalCode: "75001" }], type: ["CLASS_FLAT", "CLASS_HOUSE", "CLASS_PROGRAM"], offer: [{ type: "OFFER_BUY" }], price: { initial: { value: { min: 100000, max: 350000 } } }, habitation: { surface: { total: { min: 30, max: 110 } }, bedroomCount: { min: 1, max: 3 }, }, meta: { isTotallyOffline: false, firstSeenAt: { min: "2025-01-01T00:00:00.000Z" }, }, }, }, }), }); console.log(await resp.json()); ``` ### Exemple 2 — Recherche par département (location maison) Cas exhaustivité : tout un département en location, maisons uniquement, surface ≥ 80 m² et 3+ chambres. `department` est un champ administratif indexé (code à 2 chiffres), idéal quand `postalCode` est trop fin. ```json theme={null} { "size": 25, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [{ "department": "13" }], "type": ["CLASS_HOUSE"], "offer": [{ "type": "OFFER_RENT" }], "habitation": { "surface": { "total": { "min": 80 } }, "bedroomCount": { "min": 3 } }, "meta": { "isTotallyOffline": false } } } } ``` ```bash curl theme={null} curl -X POST https://api.fluximmo.io/v2/protected/properties/search \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d @payload.json ``` ```python Python theme={null} import requests, os resp = requests.post( "https://api.fluximmo.io/v2/protected/properties/search", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, json={ "size": 25, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [{"department": "13"}], "type": ["CLASS_HOUSE"], "offer": [{"type": "OFFER_RENT"}], "habitation": { "surface": {"total": {"min": 80}}, "bedroomCount": {"min": 3}, }, "meta": {"isTotallyOffline": False}, } }, }, ) print(resp.json()) ``` ```javascript Node theme={null} const resp = await fetch("https://api.fluximmo.io/v2/protected/properties/search", { method: "POST", headers: { "x-api-key": process.env.FLUXIMMO_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ size: 25, sortBy: "FIRST_SEEN_AT", orderBy: "DESC", search: { filterProperty: { location: [{ department: "13" }], type: ["CLASS_HOUSE"], offer: [{ type: "OFFER_RENT" }], habitation: { surface: { total: { min: 80 } }, bedroomCount: { min: 3 } }, meta: { isTotallyOffline: false }, }, }, }), }); console.log(await resp.json()); ``` ### Exemple 3 — geoDistance rayon 20 km autour de la Tour Eiffel Cas chasseur urbain : rayon centré sur un point géocodé. Le pin est en `{lat, lon}` (WGS84). Calcul euclidien (DistanceType `Plane`) — fiable jusqu'à \~50 km, au-delà préférer `geoBoundingBox`. ```json theme={null} { "size": 25, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [ { "locationCoordinate": { "location": { "geoDistance": { "pin": { "lat": 48.858370, "lon": 2.294481 }, "distanceKm": 20 } } } } ], "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "meta": { "isTotallyOffline": false } } } } ``` ```bash curl theme={null} curl -X POST https://api.fluximmo.io/v2/protected/properties/search \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d @payload.json ``` ```python Python theme={null} import requests, os resp = requests.post( "https://api.fluximmo.io/v2/protected/properties/search", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, json={ "size": 25, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [ { "locationCoordinate": { "location": { "geoDistance": { "pin": {"lat": 48.858370, "lon": 2.294481}, "distanceKm": 20, } } } } ], "type": ["CLASS_FLAT"], "offer": [{"type": "OFFER_BUY"}], "meta": {"isTotallyOffline": False}, } }, }, ) print(resp.json()) ``` ```javascript Node theme={null} const resp = await fetch("https://api.fluximmo.io/v2/protected/properties/search", { method: "POST", headers: { "x-api-key": process.env.FLUXIMMO_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ size: 25, sortBy: "FIRST_SEEN_AT", orderBy: "DESC", search: { filterProperty: { location: [ { locationCoordinate: { location: { geoDistance: { pin: { lat: 48.85837, lon: 2.294481 }, distanceKm: 20, }, }, }, }, ], type: ["CLASS_FLAT"], offer: [{ type: "OFFER_BUY" }], meta: { isTotallyOffline: false }, }, }, }), }); console.log(await resp.json()); ``` ### Exemple 4 — geoBoundingBox Paris (carte interactive) Cas type : "search in this map area" sur une UI de cartographie. `topLeft.lat` doit être supérieur à `bottomRight.lat` (latitudes décroissantes du nord vers le sud). ```json theme={null} { "size": 25, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [ { "locationCoordinate": { "location": { "geoBoundingBox": { "topLeft": { "lat": 48.9021, "lon": 2.2241 }, "bottomRight": { "lat": 48.8156, "lon": 2.4699 } } } } } ], "offer": [{ "type": "OFFER_BUY" }], "meta": { "isTotallyOffline": false } } } } ``` ```bash curl theme={null} curl -X POST https://api.fluximmo.io/v2/protected/properties/search \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d @payload.json ``` ```python Python theme={null} import requests, os resp = requests.post( "https://api.fluximmo.io/v2/protected/properties/search", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, json={ "size": 25, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [ { "locationCoordinate": { "location": { "geoBoundingBox": { "topLeft": {"lat": 48.9021, "lon": 2.2241}, "bottomRight": {"lat": 48.8156, "lon": 2.4699}, } } } } ], "offer": [{"type": "OFFER_BUY"}], "meta": {"isTotallyOffline": False}, } }, }, ) print(resp.json()) ``` ```javascript Node theme={null} const resp = await fetch("https://api.fluximmo.io/v2/protected/properties/search", { method: "POST", headers: { "x-api-key": process.env.FLUXIMMO_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ size: 25, sortBy: "FIRST_SEEN_AT", orderBy: "DESC", search: { filterProperty: { location: [ { locationCoordinate: { location: { geoBoundingBox: { topLeft: { lat: 48.9021, lon: 2.2241 }, bottomRight: { lat: 48.8156, lon: 2.4699 }, }, }, }, }, ], offer: [{ type: "OFFER_BUY" }], meta: { isTotallyOffline: false }, }, }, }), }); console.log(await resp.json()); ``` ### Exemple 5 — Multi-zones OR (Paris + petite couronne) Plusieurs entrées dans `location[]` sont combinées en **OR** : au moins une zone doit correspondre. Vous pouvez mixer libellés administratifs (`postalCode`, `department`, `inseeCode`) et modes géo (`geoDistance`, `geoBoundingBox`) dans la même requête — utile pour une agence multi-territoires. ```json theme={null} { "size": 25, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [ { "department": "75" }, { "department": "92" }, { "department": "93" }, { "department": "94" } ], "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "meta": { "isTotallyOffline": false } } } } ``` ```bash curl theme={null} curl -X POST https://api.fluximmo.io/v2/protected/properties/search \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d @payload.json ``` ```python Python theme={null} import requests, os resp = requests.post( "https://api.fluximmo.io/v2/protected/properties/search", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, json={ "size": 25, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [ {"department": "75"}, {"department": "92"}, {"department": "93"}, {"department": "94"}, ], "type": ["CLASS_FLAT"], "offer": [{"type": "OFFER_BUY"}], "meta": {"isTotallyOffline": False}, } }, }, ) print(resp.json()) ``` ```javascript Node theme={null} const resp = await fetch("https://api.fluximmo.io/v2/protected/properties/search", { method: "POST", headers: { "x-api-key": process.env.FLUXIMMO_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ size: 25, sortBy: "FIRST_SEEN_AT", orderBy: "DESC", search: { filterProperty: { location: [ { department: "75" }, { department: "92" }, { department: "93" }, { department: "94" }, ], type: ["CLASS_FLAT"], offer: [{ type: "OFFER_BUY" }], meta: { isTotallyOffline: false }, }, }, }), }); console.log(await resp.json()); ``` ## Pagination L'API utilise une **pagination cursor** stable plutôt qu'un `offset`. C'est la méthode recommandée pour itérer sur de gros volumes : insertions et suppressions concurrentes ne décalent pas les pages. **Workflow** : 1. Premier appel : envoyer le payload sans `searchAfterHash`. 2. La réponse contient un champ `searchAfterHash` (string opaque). Conservez-le. 3. Pour la page suivante : renvoyer **le même payload** (mêmes filtres, même `sortBy`/`orderBy`), en ajoutant `searchAfterHash: ""`. 4. Quand `searchAfterHash` est absent / null dans la réponse, vous avez atteint la fin des résultats. **Paramètres** : * `size` : 1 à 100 (recommandé : **25** pour `search` full ; voir DTO). Plus grand = plus de coût et de latence. * `sortBy` ∈ `FIRST_SEEN_AT`, `LAST_UPDATED_AT`, `LAST_SEEN_AT`, `PRICE`, `RELEVANCE`. Le tri canonique en prod est `FIRST_SEEN_AT DESC`. * `orderBy` ∈ `ASC`, `DESC`. * ⚠️ **Ne changez pas `sortBy`/`orderBy` entre deux pages** — le cursor devient invalide. ```python Pagination Python theme={null} import requests, os payload = { "size": 25, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": {"filterProperty": {"location": [{"department": "75"}], "offer": [{"type": "OFFER_BUY"}]}}, } cursor = None while True: if cursor: payload["searchAfterHash"] = cursor resp = requests.post( "https://api.fluximmo.io/v2/protected/properties/search", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, json=payload, ).json() data = resp.get("data", {}) or {} for hit in data.get("items", []): print(hit["flxId"]) cursor = data.get("searchAfterHash") if not cursor: break ``` # Récupérer une property par flxId Source: https://doc.fluximmo.io/api-v2-reference/properties/get-a-property-by-its-flx_id get /v2/protected/properties/{flxId} Refetch unitaire d'une property après réception d'un webhook. # Voir les signalements d'une property Source: https://doc.fluximmo.io/api-v2-reference/properties/getissue--get-raised-issues-for-a-given-property get /v2/browse/property/{flxId}/_issues Lister les issues remontées sur une property donnée. # Time-series des signalements Source: https://doc.fluximmo.io/api-v2-reference/properties/issuetimeseries--obtain-time-series-analysis-over-issues-for-all-the-properties post /v2/browse/property/issues/_timeseries Analyse temporelle des signalements sur les properties. # Signaler un problème sur une property Source: https://doc.fluximmo.io/api-v2-reference/properties/report-an-issue-on-a-property post /v2/protected/properties/{flxId}/report Nous remonter une donnée fausse ou un statut erroné et déclencher une revérification gratuite. ## À quoi ça sert `POST /v2/protected/properties/{flxId}/report` sert à nous dire ce qui ne va pas sur un bien : prix faux, annonce toujours en ligne alors que nous la donnons disparue, doublon, mauvaise agence. L'appel est **gratuit (0 crédit)**, répond **200 immédiatement** et lance en tâche de fond une revérification des annonces auprès de leurs portails. Rappel de vocabulaire : une **property** est un bien physique dédupliqué qui agrège N **adverts**, les annonces publiées sur des portails différents (voir [Property vs Advert](/concepts/property-vs-advert)). Ce sont donc **les annonces** de la property qui repartent en collecte, pas la property elle-même. La donnée rafraîchie **n'est pas dans la réponse**. Relisez la property quelques minutes plus tard avec [`GET /v2/protected/properties/{flxId}`](/api-v2-reference/properties/get-a-property-by-its-flx_id), ou recevez-la sur votre [webhook d'alerte](/concepts/webhooks) si vous y êtes abonné. ## Ce qui est réellement revérifié * **Seules les annonces en ligne** — celles déjà hors ligne sont ignorées. * **10 annonces au maximum**, les plus récemment vues ; l'excédent est compté dans `skipped_count`. * Une annonce **sans url source exploitable** est également comptée dans `skipped_count`. * `skipped_count` peut aussi être non nul parce que certaines revérifications n'ont pas pu être lancées. Une rafale d'appels rapprochés rend ce cas plus probable. Ce n'est pas une erreur : espacez vos appels et réessayez. * Si **aucune** annonce en ligne n'est revérifiable, la réponse vaut `status: "nothing_to_check"` avec `triggered_count: 0`, en **HTTP 200** — là encore, ce n'est pas une erreur. Le tableau `triggered` liste les flx\_id **des annonces** confiées au scraper, jamais celui de la property. **Le motif détermine la profondeur de la revérification.** `STILL_ONLINE` et `NOT_ONLINE_ANYMORE` portent sur la disponibilité : ils déclenchent un contrôle léger qui met à jour le statut en ligne — le même que [`/check`](/api-v2-reference/properties/request-an-on-demand-check-of-a-property). Les cinq autres motifs portent sur le contenu des annonces : ils déclenchent une **réextraction complète**, seule capable de corriger un prix, une surface, une adresse ou une agence. Choisir le motif juste n'est donc pas cosmétique. ## Cas d'usage * **Corriger une donnée fausse** — `WRONG_DATA` lorsque prix, surface ou nombre de pièces ne correspondent pas au portail. * **Rétablir un statut** — `STILL_ONLINE` si nous disons le bien hors ligne alors qu'il est visible, `NOT_ONLINE_ANYMORE` dans le cas inverse. * **Signaler un doublon** — `DUPLICATE` quand le même bien existe déjà sous un autre flx\_id. * **Corriger la localisation ou le vendeur** — `WRONG_LOCATION` pour une géoloc ou une adresse fausse, `WRONG_AGENCY` pour un pro/particulier ou une agence mal attribués. * **Tout le reste** — `OTHER`, qui rend alors le champ `comment` obligatoire (1000 caractères maximum). Le contrat exact est documenté plus bas par le bloc OpenAPI (`POST /v2/protected/properties/{flxId}/report`). ## Exemples ### 1 — Signaler un prix erroné ```bash curl theme={null} curl -X POST https://api.fluximmo.io/v2/protected/properties/123456789/report \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"reason": "WRONG_DATA", "comment": "Le portail affiche 450000 €, vous avez 425000 €."}' ``` ```python Python theme={null} import os, requests resp = requests.post( "https://api.fluximmo.io/v2/protected/properties/123456789/report", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, json={"reason": "WRONG_DATA", "comment": "Le portail affiche 450000 €, vous avez 425000 €."}, timeout=30, ) body = resp.json() # `data` en 2xx, `error` en 404 / 422. print(resp.status_code, body.get("data", body.get("error"))) ``` Deux annonces en ligne sont reparties en collecte, aucune n'a été écartée : ```json Réponse 200 theme={null} {"data":{"status":"accepted","entity":"property","flx_id":"123456789","triggered":["987654321","987654322"],"triggered_count":2,"skipped_count":0,"message":"2 advert(s) queued for an on-demand re-check."}} ``` Quand des annonces sont écartées, `message` le dit : le suffixe ` 3 advert(s) skipped.` est ajouté au message nominal, et si **rien** n'a pu partir alors que des annonces étaient éligibles, le message devient `No advert queued: 3 advert(s) skipped (per-request cap, missing url, or too many scrapes already running).` ### 2 — Motif libre, sur une property dont les annonces sont toutes hors ligne ```bash curl theme={null} curl -X POST https://api.fluximmo.io/v2/protected/properties/123456789/report \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"reason": "OTHER", "comment": "Les photos ne correspondent pas au bien décrit."}' ``` Le signalement est bien enregistré, mais il n'y avait rien à revérifier — d'où le `200` avec `nothing_to_check` : ```json Réponse 200 theme={null} {"data":{"status":"nothing_to_check","entity":"property","flx_id":"123456789","triggered":[],"triggered_count":0,"skipped_count":0,"message":"No online advert to re-check."}} ``` ## Erreurs spécifiques * **404** — le flx\_id n'existe pas : `{"error":{"message":"Property with flx_id 123456789 not found","code":10003}}`. * **422** — corps invalide (et non 400), code `10002`, avec un seul message : `"comment is required when reason is OTHER"` ou `"reason must be one of the following values: STILL_ONLINE, NOT_ONLINE_ANYMORE, WRONG_DATA, WRONG_LOCATION, DUPLICATE, WRONG_AGENCY, OTHER"`. Un seul message est renvoyé par appel même si plusieurs champs sont invalides : branchez votre logique sur le **code**, pas sur le libellé. Les espaces de début et de fin du commentaire sont retirés : un commentaire composé uniquement d'espaces est donc refusé. Les clés inconnues envoyées dans le corps sont **silencieusement ignorées**. La liste complète des codes est sur [Codes d'erreur](/ressources/codes-erreur). Il n'y a **aucun cooldown** : chaque appel relance des vérifications. Signalez sans hésiter, c'est gratuit et cela améliore la base pour tout le monde. L'équivalent existe sur les annonces : [`POST /v2/protected/adverts/{flxId}/report`](/api-v2-reference/adverts/report-an-issue-on-an-advert). ## Liens utiles * [Concept · Property vs Advert](/concepts/property-vs-advert) * [Relire la property après signalement](/api-v2-reference/properties/get-a-property-by-its-flx_id) * [Revérifier une property](/api-v2-reference/properties/request-an-on-demand-check-of-a-property) — même mécanique, sans diagnostic, facturée 1 crédit. * [Concept · Webhooks](/concepts/webhooks) * [Ressources · Codes d'erreur](/ressources/codes-erreur) * [Ressources · Bonnes pratiques](/ressources/bonnes-pratiques) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Demander une revérification d'une property Source: https://doc.fluximmo.io/api-v2-reference/properties/request-an-on-demand-check-of-a-property post /v2/protected/properties/{flxId}/check Contrôler à la demande la disponibilité des annonces en ligne d'un bien, pour 1 crédit forfaitaire. ## À quoi ça sert `POST /v2/protected/properties/{flxId}/check` répond à une seule question, pour chacune des annonces en ligne du bien : **est-elle toujours publiée sur son portail ?** La route **n'attend aucun corps de requête** ni paramètre : seul le `flxId` du chemin compte. Elle répond **200 tout de suite**, les contrôles tournent en tâche de fond. Rappel de vocabulaire : une **property** est un bien physique dédupliqué qui agrège N **adverts**, les annonces publiées sur des portails différents (voir [Property vs Advert](/concepts/property-vs-advert)). Ce sont donc **les annonces en ligne** qui repartent en collecte, pas la property elle-même. L'appel coûte **1 crédit, facturé sur toute réponse 2xx — y compris quand elle vaut `nothing_to_check`**. Un `404`, lui, ne facture rien. Suivez votre consommation sur [Consommation de crédits](/api-v2-reference/consumption/get-your-credit-consumption). Le crédit est **forfaitaire** : un seul appel peut déclencher jusqu'à 10 collectes pour ce même crédit. Sur un bien largement multi-diffusé, c'est l'appel le plus rentable de l'API. ## `/check` ou `/report` ? * **`/check`** — **« ces annonces sont-elles toujours en ligne ? »** : pas de diagnostic à fournir, pas de corps, 1 crédit. Met à jour le statut en ligne, **et lui seul**. * **[`/report`](/api-v2-reference/properties/report-an-issue-on-a-property)** — vous voulez **nous dire ce qui ne va pas** (prix faux, annonce disparue, doublon) : un motif est obligatoire, et c'est **gratuit**. En pratique : si vous savez ce qui cloche, signalez-le — c'est gratuit, et **c'est le seul moyen de faire corriger un prix, une surface ou une géolocalisation**. Si vous voulez seulement confirmer que le bien est toujours disponible, utilisez `/check`. L'équivalent existe sur les annonces : [`POST /v2/protected/adverts/{flxId}/check`](/api-v2-reference/adverts/request-an-on-demand-check-of-an-advert). ## Ce qui est réellement revérifié * **Seules les annonces en ligne** — celles déjà hors ligne sont ignorées. * **10 annonces au maximum**, les plus récemment vues ; l'excédent est compté dans `skipped_count`. * Une annonce **sans url source exploitable** est également comptée dans `skipped_count`. * `skipped_count` peut aussi être non nul parce que certaines revérifications n'ont pas pu être lancées. Une rafale d'appels rapprochés rend ce cas plus probable. Ce n'est pas une erreur : espacez vos appels et réessayez. * Si **aucune** annonce en ligne n'est revérifiable, la réponse vaut `status: "nothing_to_check"` avec `triggered_count: 0`, en **HTTP 200** — et le crédit est tout de même décompté. Le tableau `triggered` liste les flx\_id **des annonces** confiées au scraper, jamais celui de la property. Le contrat exact est documenté plus bas par le bloc OpenAPI (`POST /v2/protected/properties/{flxId}/check`). ## Cas d'usage * **Vérifier avant d'appeler** — s'assurer qu'un bien est toujours disponible juste avant de contacter le vendeur. * **Rafraîchir une short-list** — remettre à jour les quelques biens qu'un utilisateur a mis en favori, à la demande. * **Confirmer une disparition** — un bien absent depuis plusieurs jours de vos alertes : un `check` tranche. Le résultat **n'est pas dans la réponse**. Relisez la property quelques minutes plus tard avec [`GET /v2/protected/properties/{flxId}`](/api-v2-reference/properties/get-a-property-by-its-flx_id), ou recevez-la sur votre [webhook d'alerte](/concepts/webhooks) si vous y êtes abonné. Ce qui aura changé, c'est le statut en ligne des annonces contrôlées — pas leur contenu. ## Exemples ### 1 — Forcer la revérification d'un bien ```bash curl theme={null} curl -X POST https://api.fluximmo.io/v2/protected/properties/123456789/check \ -H "x-api-key: $FLUXIMMO_API_KEY" ``` ```python Python theme={null} import os, requests resp = requests.post( "https://api.fluximmo.io/v2/protected/properties/123456789/check", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, timeout=30, ) body = resp.json() # `data` en 2xx, `error` en 404. print(resp.status_code, body.get("data", body.get("error"))) ``` ```json Réponse 200 theme={null} {"data":{"status":"accepted","entity":"property","flx_id":"123456789","triggered":["987654321","987654322"],"triggered_count":2,"skipped_count":0,"message":"2 advert(s) queued for an on-demand re-check."}} ``` Quand des annonces sont écartées, `message` le dit : le suffixe ` 3 advert(s) skipped.` est ajouté au message nominal, et si **rien** n'a pu partir alors que des annonces étaient éligibles, le message devient `No advert queued: 3 advert(s) skipped (per-request cap, missing url, or too many scrapes already running).` ### 2 — Rien à revérifier (le crédit reste dû) ```json Réponse 200 theme={null} {"data":{"status":"nothing_to_check","entity":"property","flx_id":"123456789","triggered":[],"triggered_count":0,"skipped_count":0,"message":"No online advert to re-check."}} ``` Toutes les annonces de ce bien sont hors ligne : il n'y a rien à collecter, et il n'y aura pas de mise à jour à relire. Testez `triggered_count > 0` avant de programmer une relecture. ## Erreurs spécifiques * **404** — le flx\_id n'existe pas : `{"error":{"message":"Property with flx_id 123456789 not found","code":10003}}`. Non facturé. Il n'y a pas d'erreur de validation `422` sur cette route, puisqu'elle n'accepte aucun corps. La liste complète des codes est sur [Codes d'erreur](/ressources/codes-erreur). ## Liens utiles * [Relire la property après le check](/api-v2-reference/properties/get-a-property-by-its-flx_id) * [Signaler un problème sur une property](/api-v2-reference/properties/report-an-issue-on-a-property) — gratuit, avec un motif, et seul moyen de faire corriger une donnée fausse. * [Concept · Property vs Advert](/concepts/property-vs-advert) * [Consommation de crédits](/api-v2-reference/consumption/get-your-credit-consumption) * [Ressources · Limites et rate limits](/ressources/limites-rate-limits) * [Ressources · Codes d'erreur](/ressources/codes-erreur) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Récupérer plusieurs properties par flxIds Source: https://doc.fluximmo.io/api-v2-reference/properties/retreive-properties-by-their-flx_ids post /v2/protected/properties/_many Refetch en lot — recommandé après webhook properties (IDs only). # Récupérer une property par advert flxId Source: https://doc.fluximmo.io/api-v2-reference/properties/retrieve-a-property-by-advert-flxid get /v2/protected/properties/advert/{flxId} Récupérer la property parente d'une advert donnée. # Récupérer une property par URL Source: https://doc.fluximmo.io/api-v2-reference/properties/retrieve-a-property-by-url get /v2/protected/properties/url Retrouver un bien à partir de l'URL d'une de ses annonces, avec collecte à la demande si besoin. ## À quoi ça sert `GET /v2/protected/properties/url?url=…` prend l'URL d'**une annonce portail** et retourne la **property** qui l'agrège. Une property est un bien physique dédupliqué qui regroupe N **adverts**, les annonces publiées sur des portails différents (voir [Property vs Advert](/concepts/property-vs-advert)) : vous partez donc d'un lien leboncoin, seloger ou autre, et vous récupérez le bien complet côté Fluximmo. Si l'annonce est déjà indexée **et déjà rattachée à une property**, celle-ci part immédiatement. Sinon — annonce inconnue, ou connue mais pas encore rattachée — elle est **collectée à la demande** et la requête **attend** que la chaîne annonce → property soit complète. Cette route peut **bloquer jusqu'à \~45 secondes** avant de répondre, le temps de la collecte. Réglez le timeout de votre client HTTP **au-dessus de cette valeur** — un timeout à 10 ou 30 s vous fera manquer des réponses valides. Ne parallélisez pas des dizaines d'appels : voir [Limites et rate limits](/ressources/limites-rate-limits). ## Facturation **1 crédit, uniquement quand un document est renvoyé (200).** Les réponses `202` et `422` ne sont **pas** facturées : une URL qui n'aboutit pas ne vous coûte rien. **Aucune déduplication côté serveur.** Deux appels concurrents sur la même url déclenchent **deux** collectes, et **deux** crédits si les deux répondent `200`. Dédupliquez vos appels sur une même url avant de les émettre. ## Cas d'usage * **Enrichir un lien collé par un utilisateur** — votre utilisateur colle une URL d'annonce, vous affichez le bien normalisé, géolocalisé et dédupliqué. * **Rattacher votre base à Fluximmo** — retrouver le flx\_id de biens que vous suivez déjà par leur URL portail. * **Amorcer un suivi** — récupérer la property puis surveiller ses évolutions via une [alerte](/api-v2-reference/properties-alerts/create-a-new-alert-for-this-property-search) ou un [webhook](/concepts/webhooks). Le contrat exact est documenté plus bas par le bloc OpenAPI (`GET /v2/protected/properties/url`). ## Les trois issues possibles Le corps contient la property complète, enveloppée : `{"data": { … }}`. 1 crédit décompté. L'annonce a été collectée mais la chaîne annonce → property n'est pas encore complète. Réessayez **la même url** après `retry_after_s` secondes. Cette valeur est **dans le corps** : il n'y a **pas** d'en-tête HTTP `Retry-After`. L'annonce a été collectée mais refusée (données incohérentes ou manquantes, hors périmètre). Elle **ne deviendra jamais disponible** : ne réessayez pas, retirez l'url de votre file. Deux réponses portent le code **422** sur cette route, et elles n'ont rien à voir. Le **422 de validation** est une vraie erreur, non enveloppée : `{"error":{"message":"Url query parameter must be a valid http(s) url","code":10002}}` — même message que le paramètre soit absent, vide ou mal formé ; branchez tout de même sur le **code `10002`**, pas sur le libellé. Le **422 de rejet d'ingestion** est une réponse métier, enveloppée dans `data` et porteuse d'un `status`. Fiez-vous à la clé racine : `error` contre `data`. ## Exemples ### 1 — Résoudre une URL leboncoin ```bash curl theme={null} curl -G https://api.fluximmo.io/v2/protected/properties/url \ -H "x-api-key: $FLUXIMMO_API_KEY" \ --data-urlencode "url=https://www.leboncoin.fr/ad/ventes_immobilieres/3246347062" \ --max-time 60 ``` ```python Python theme={null} import os, requests resp = requests.get( "https://api.fluximmo.io/v2/protected/properties/url", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, params={"url": "https://www.leboncoin.fr/ad/ventes_immobilieres/3246347062"}, timeout=60, # la requête peut bloquer ~45 s ) body = resp.json() # `data` sur 200/202/422 d'ingestion, `error` sur 404 et 422 de validation. print(resp.status_code, body.get("data", body.get("error"))) ``` En `200`, le corps est la property elle-même sous `data`. Notez le `--max-time 60` / `timeout=60` : c'est le point à ne pas rater. ### 2 — Distinguer un `202` d'un `422` d'ingestion ```json Réponse 202 — à réessayer dans 120 s theme={null} {"data":{"status":"pending","website_ref":"leboncoin.fr_3246347062","retry_after_s":120,"message":"The ad was collected and is being indexed. Retry the same url in a couple of minutes."}} ``` ```json Réponse 422 — définitif, ne pas réessayer theme={null} {"data":{"status":"rejected_by_ingestion","website_ref":"leboncoin.fr_3246347062","message":"This ad was collected but could not be ingested (incoherent or missing data, or out of scope). It will not become available."}} ``` Un client correct branche sur `status` : `pending` → replanifier le même appel après `retry_after_s`, `rejected_by_ingestion` → abandonner définitivement cette url. `website_ref` sert à corréler un retry, mais peut valoir `null` : ne le prenez pas comme clé obligatoire. ## Erreurs spécifiques * **404** — portail non couvert, url invalide, ou url ne désignant pas une annonce unique (page de recherche, url tronquée). Enveloppe d'erreur classique. La liste complète des codes est sur [Codes d'erreur](/ressources/codes-erreur). ## Liens utiles * [Concept · Property vs Advert](/concepts/property-vs-advert) * [Récupérer une property par flxId](/api-v2-reference/properties/get-a-property-by-its-flx_id) * [Récupérer une property par le flxId d'une annonce](/api-v2-reference/properties/retrieve-a-property-by-advert-flxid) * [Récupérer une advert par URL](/api-v2-reference/adverts/retrieve-advert-by-url) — la même mécanique, mais qui rend l'annonce plutôt que le bien. * [Ressources · Limites et rate limits](/ressources/limites-rate-limits) * [Ressources · Bonnes pratiques](/ressources/bonnes-pratiques) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Properties similaires Source: https://doc.fluximmo.io/api-v2-reference/properties/similar-properties post /v2/protected/properties/similars Trouver des properties similaires à une property donnée. # Détails d'une agence par identifiant officiel Source: https://doc.fluximmo.io/api-v2-reference/realestate-companies/retrieve-a-realestate-company-from-its-official-identifier get /v2/protected/companies/{siren} Informations d'une agence immobilière à partir de son SIREN/SIRET. # Échantillon d'adverts (no-auth) Source: https://doc.fluximmo.io/api-v2-reference/sample-adverts/get-a-sample-of-ads get /v2/sample/adverts 10 adverts d'exemple sans authentification, pour découvrir le format. ## ★ Endpoint sans authentification `GET /v2/sample/adverts` retourne **10 adverts d'exemple** (annonces canoniques, format strict identique à la production) **sans aucune clé API requise**. Parallèle à `sample/properties` mais pour le modèle `Advert` : utile pour distinguer concrètement Property vs Advert. Le bouton **"Try it"** ci-contre fonctionne directement, sans configurer de clé. **Cas d'usage typiques** : * **Découvrir la structure** d'un objet `Advert` (annonce brute du marché, agrégée par Fluximmo). * **Tester votre parser** côté client. * **Fixtures de tests** pour vos workflows adverts (matching, filtering). * **Comprendre la différence Property vs Advert** : comparez ce sample avec `sample/properties` pour voir les divergences (un `Advert` correspond à une publication d'annonce ; une `Property` est le bien sous-jacent dédupliqué). ## Exemple ```bash curl theme={null} curl https://api.fluximmo.io/v2/sample/adverts ``` ```python Python theme={null} import requests resp = requests.get("https://api.fluximmo.io/v2/sample/adverts", timeout=10).json() adverts = resp["data"] print(f"{len(adverts)} adverts") print(adverts[0].keys()) ``` ```javascript Node theme={null} const res = await (await fetch("https://api.fluximmo.io/v2/sample/adverts")).json(); const adverts = res.data; console.log(`${adverts.length} adverts`); console.log(Object.keys(adverts[0])); ``` ## Liens utiles * [Concept · Property vs Advert](/concepts/property-vs-advert) * [Démarrer · Sample data](/demarrer/sample-data) * [Sample webhook adverts](/api-v2-reference/sample-adverts/sample-advert-webhook-body) * [Sample properties (no-auth)](/api-v2-reference/sample-properties/get-a-sample-of-properties) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Exemple de webhook adverts Source: https://doc.fluximmo.io/api-v2-reference/sample-adverts/sample-advert-webhook-body get /v2/sample/webhook/adverts Payload de webhook adverts type, pour tester votre endpoint. ## À quoi ça sert `GET /v2/sample/webhook/adverts` retourne **le payload exact** que Fluximmo POSTerait à votre endpoint webhook lorsqu'un `Advert` matche une alerte. À utiliser pour : * **Tester votre handler webhook** sans devoir attendre un vrai event. * **Valider** votre déserialisation et votre persistance. * **Fixtures CI/CD** pour les tests d'intégration. * **Documenter** votre pipeline de réception interne. Aucune authentification requise. ## Exemple ```bash curl theme={null} curl https://api.fluximmo.io/v2/sample/webhook/adverts ``` ```python Python theme={null} import requests, json payload = requests.get("https://api.fluximmo.io/v2/sample/webhook/adverts", timeout=10).json() print(json.dumps(payload, indent=2)[:500]) ``` ```javascript Node theme={null} const payload = await (await fetch("https://api.fluximmo.io/v2/sample/webhook/adverts")).json(); console.log(payload); ``` ## Liens utiles * [Concept · Webhooks](/concepts/webhooks) * [Sample adverts (no-auth)](/api-v2-reference/sample-adverts/get-a-sample-of-ads) * [Sample webhook properties](/api-v2-reference/sample-properties/sample-properties-webhook-body) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Échantillon de properties (no-auth) Source: https://doc.fluximmo.io/api-v2-reference/sample-properties/get-a-sample-of-properties get /v2/sample/properties 10 properties d'exemple sans authentification, pour découvrir le format. ## ★ Endpoint sans authentification `GET /v2/sample/properties` retourne **10 properties d'exemple** (biens canoniques, format strict identique à la production) **sans aucune clé API requise**. C'est l'endpoint le plus rapide pour découvrir Fluximmo : zéro inscription, zéro friction. Le bouton **"Try it"** ci-contre fonctionne directement, sans configurer de clé. Cliquez et exécutez. **Cas d'usage typiques** : * **Découvrir la structure** d'un objet `Property` avant de demander une clé d'API. * **Tester votre parser** : alimentez vos types TypeScript / classes Pydantic / structs Go avec ce sample, vérifiez le mapping. * **Fixtures de tests** : importez ce JSON tel quel dans vos tests d'intégration ou snapshots. * **Debug local** : reproduire un bug sans consommer votre quota API. ## Exemple ```bash curl theme={null} curl https://api.fluximmo.io/v2/sample/properties ``` ```python Python theme={null} import requests resp = requests.get("https://api.fluximmo.io/v2/sample/properties", timeout=10) properties = resp.json()["data"] print(f"{len(properties)} properties") print(properties[0].keys()) ``` ```javascript Node theme={null} const resp = await fetch("https://api.fluximmo.io/v2/sample/properties"); const { data: properties } = await resp.json(); console.log(`${properties.length} properties`); console.log(Object.keys(properties[0])); ``` Aucun header `Authorization` n'est nécessaire. ## Liens utiles * [Concept · Property vs Advert](/concepts/property-vs-advert) * [Démarrer · Sample data](/demarrer/sample-data) * [Sample webhook properties](/api-v2-reference/sample-properties/sample-properties-webhook-body) * [Sample adverts (no-auth)](/api-v2-reference/sample-adverts/get-a-sample-of-ads) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Exemple de webhook properties Source: https://doc.fluximmo.io/api-v2-reference/sample-properties/sample-properties-webhook-body get /v2/sample/webhook/properties Payload de webhook properties type, pour tester votre endpoint. ## À quoi ça sert `GET /v2/sample/webhook/properties` retourne **le payload exact** que Fluximmo POSTerait à votre endpoint webhook lorsqu'une `Property` matche une alerte. Parfait pour : * **Tester votre handler webhook** sans devoir attendre un vrai event en production. * **Valider votre parser** côté serveur (déserialisation, validation de schéma, persistance). * **Documenter** votre intégration interne avec un payload de référence stable. * **CI/CD** : utiliser ce payload comme fixture dans les tests d'intégration de votre worker webhook. L'endpoint ne nécessite aucune authentification (c'est un endpoint d'exemple en lecture). ## Exemple ```bash curl theme={null} curl https://api.fluximmo.io/v2/sample/webhook/properties ``` ```python Python theme={null} import requests, json payload = requests.get("https://api.fluximmo.io/v2/sample/webhook/properties", timeout=10).json() # Replay vers votre webhook local pour tester : # requests.post("http://localhost:3000/webhooks/properties", json=payload) print(json.dumps(payload, indent=2)[:500]) ``` ```javascript Node theme={null} const payload = await (await fetch("https://api.fluximmo.io/v2/sample/webhook/properties")).json(); // Replay vers votre webhook local pour tester : // await fetch("http://localhost:3000/webhooks/properties", { method: "POST", body: JSON.stringify(payload), headers: { "Content-Type": "application/json" } }); console.log(payload); ``` ## Liens utiles * [Concept · Webhooks](/concepts/webhooks) * [Sample properties (no-auth)](/api-v2-reference/sample-properties/get-a-sample-of-properties) * [Sample webhook adverts](/api-v2-reference/sample-adverts/sample-advert-webhook-body) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Liste des portails immobiliers Source: https://doc.fluximmo.io/api-v2-reference/system/get-the-list-of-real-estate-platforms get /portals Liste des portails immobiliers couverts par Fluximmo. # Version de l'API Source: https://doc.fluximmo.io/api-v2-reference/system/getversion get /version Version courante de l'API Fluximmo. # Healthcheck Source: https://doc.fluximmo.io/api-v2-reference/system/healthz get /healthz Endpoint de healthcheck. # Authentification API Fluximmo — clé x-api-key Source: https://doc.fluximmo.io/authentification Authentification par header `x-api-key`. Activation et gestion des clés via my.fluximmo.io. Toutes les requêtes vers les endpoints `/v2/protected/*` requièrent une clé API transmise via le header HTTP `x-api-key`. Les endpoints `/v2/sample/*` sont publics et n'exigent aucun header. ## Header `x-api-key` Ajoutez votre clé dans chaque requête authentifiée : ```bash curl theme={null} curl --location 'https://api.fluximmo.io/v2/protected/admin/consumption?from_date=2024-01-01&to_date=2024-02-01' \ --header 'x-api-key: VOTRE_CLE' ``` ```python Python theme={null} import requests response = requests.get( "https://api.fluximmo.io/v2/protected/admin/consumption", headers={"x-api-key": "VOTRE_CLE"}, params={"from_date": "2024-01-01", "to_date": "2024-02-01"}, ) ``` ```javascript Node theme={null} const response = await fetch( "https://api.fluximmo.io/v2/protected/admin/consumption?from_date=2024-01-01&to_date=2024-02-01", { headers: { "x-api-key": "VOTRE_CLE" } } ); ``` Si la clé est absente, invalide ou expirée, l'API renvoie un `401 Unauthorized` (clé manquante / inconnue) ou `403 Forbidden` (clé valide mais sans accès au périmètre demandé). Détail des codes : [Codes d'erreur](/ressources/codes-erreur). ## Clé test gratuite (1 semaine) Onglet **API** → **Générer une clé test**. **Limites de la clé test :** * Durée : **7 jours** à compter de la génération * Volume : quotas réduits (\~5 req/s — suffisants pour valider une intégration de bout en bout) * Périmètre : accès complet aux modules **Search, Refetch, Analytics, IA, Géocodage** * Seule restriction : **création d'alertes webhook sur demande** par mail à [contact@fluximmo.com](mailto:contact@fluximmo.com) Pour découvrir l'API sans même créer de compte, utilisez d'abord les [endpoints sample no-auth](/demarrer/sample-data). ## Clé production Activation **depuis votre espace client** [my.fluximmo.io](https://my.fluximmo.io) — onglet **API**. Vous pilotez tout depuis votre compte avant ou après activation : génération de clé, suivi de consommation, paramétrage facturation. * Pas d'engagement initial, pas de paiement avant que votre intégration soit validée * Pricing au volume — barème détaillé sur l'espace client * Pour des besoins spécifiques (gros volume, endpoints custom, devis sur-mesure), écrivez à [contact@fluximmo.com](mailto:contact@fluximmo.com) avec votre `clientId` **Vous êtes une startup early-stage ?** Fluximmo accompagne les jeunes équipes avec des conditions adaptées au cas par cas. [Nous contacter](mailto:contact@fluximmo.com). Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # DVF vs Fluximmo — quand utiliser lequel Source: https://doc.fluximmo.io/concepts/dvf-vs-fluximmo Comparatif honnête entre DVF (prix de vente notariaux gouvernementaux) et Fluximmo (annonces temps réel) — usage combiné gagnant. ## Pourquoi cette page DVF (Demande de Valeurs Foncières) est l'open data immobilier le plus cité par les startups qui évaluent leurs options. C'est gratuit, gouvernemental, et dans certains cas suffisant. Mais DVF et Fluximmo répondent à des besoins très différents. Cette page clarifie quand utiliser lequel — et pourquoi, pour la plupart des produits sérieux, **les deux ensemble** sont complémentaires. ## DVF — forces * **Prix de vente réels** (pas prix annonce) — donnée notariale issue des actes authentiques * **Géolocalisation parcellaire** précise (parcelle cadastrale) * **Gratuit** et open data * **Historique long** sur le foncier transacté ## DVF — limites * **Aucun temps réel** — publication trimestrielle avec un lag de l'ordre de 6 mois minimum après transaction * **Pas de caractéristiques détaillées** : pas de photos, pas de description, pas de filtres surface / chambres / équipements précis * **Pas de webhooks** — pas de push, pas de notification, pas de cycle de vie * **Pas de cycle de vie de l'annonce** : aucun event de type baisse de prix, republication, retrait * **Couverture transaction uniquement** — aucune visibilité sur le marché qui ne s'est pas vendu (biens en vente, biens retirés) ## Fluximmo — forces * **Temps réel** via webhooks `adverts` et `properties` — concepts produit (nouvelle annonce, baisse de prix, republication, dépublication) à dériver côté client à partir de la structure technique réelle du payload : `data.created` (nouvelles entités) et `data.updated` (évolutions, avec champs `currentPrice` / `isOnline`) * **Caractéristiques riches** : habitation détaillée, climat, équipements, photos, full-text * **Filtres avancés** : surface, pièces, prix, équipements, état, énergie * **Géo riche** : bbox, distance, multi-zones (OR géographique) * **Multi-portails dédupliqués** côté `Property` (un bien = une entité, plusieurs annonces sources) ## Fluximmo — limites * **Prix = prix annonce** (pas prix de vente notarial — c'est ce que le vendeur demande, pas ce qui a été signé) * **Couverture marketplace** : ce qui est publié sur les portails uniquement (pas les ventes off-market sans annonce) ## Tableau de décision | Besoin | DVF | Fluximmo | | ----------------------------------------------------------- | --- | --------- | | Prix de vente réel historique | Oui | Non | | Catalogue temps réel des biens en vente | Non | Oui | | Détails techniques d'un bien (surface, pièces, équipements) | Non | Oui | | Photos, description, full-text | Non | Oui | | Webhooks / push | Non | Oui | | Cycle de vie d'annonce (baisse de prix, retrait) | Non | Oui | | Compliance / scoring AVM avec valeurs notariales | Oui | Combiné | | Alertes chasseur immobilier | Non | Oui | | Replication BDD adverts | Non | Oui | | Géolocalisation parcellaire cadastrale | Oui | Partielle | | Filtres surface / pièces / équipements | Non | Oui | ## Combinaison gagnante Pour la plupart des produits immobiliers sérieux, **les deux sources sont complémentaires** : * **Fluximmo** fournit le marché courant (qui est en vente, à quel prix annonce, avec quelles caractéristiques) en temps réel. * **DVF** fournit le marché passé (qui s'est vendu, à quel prix réel) avec lag. Combiner les deux donne un AVM ou un produit data complet : la valeur demandée aujourd'hui, croisée avec la dernière transaction signée sur la même zone ou la même parcelle. DVF et DPE sont accessibles directement via l'API Fluximmo avec une clé API standard : un seul fournisseur, un seul contrat, pas de double intégration à maintenir. ```mermaid theme={null} flowchart LR subgraph TR["Temps réel"] F[Fluximmo
adverts / properties] end subgraph H["Historique notarial"] D[DVF
prix de vente] end F -->|prix annonce + caractéristiques| AVM[AVM / scoring / data product] D -->|prix de vente réels lag 6+ mois| AVM AVM --> C[Client final] ``` ## Cas d'usage concrets ### 1. AVM proptech Un moteur d'estimation AVM utilise Fluximmo pour récupérer les comparables courants (mêmes caractéristiques, même zone géographique, biens actuellement en vente), puis cross-check avec les transactions DVF des 6 à 12 derniers mois sur la même parcelle ou la même rue via [Recherche DVF](/api-v2-reference/dvf-search/recherche-dvf) ou [Match DVF](/api-v2-reference/dvf-match/match-dvf-à-un-bien-immobilier). Résultat : une fourchette d'estimation calibrée sur le marché vivant **et** sur les prix réellement signés. ### 2. Compliance / scoring KYC immobilier Une fintech combine l'annonce courante (Fluximmo) avec la dernière transaction notariale DVF de la même parcelle pour vérifier la cohérence prix annonce vs prix de vente historique — utile en compliance, anti-fraude, scoring crédit. L'accès DVF se fait directement via l'API avec une clé standard ([Match DVF](/api-v2-reference/dvf-match/match-dvf-à-un-bien-immobilier)). ## Accéder à DVF et DPE via l'API DVF et DPE sont exposés comme endpoints requêtables, accessibles avec une clé API standard. Chaque jeu de données propose deux modes : * **Search** — renvoie une liste filtrée et paginée d'entrées (par zone géographique, période, caractéristiques). Adapté à l'exploration et à la constitution de jeux de comparables. * **Match** — à partir d'un bien décrit (localisation, surface, caractéristiques), renvoie les entrées les plus proches classées par score de similarité. Adapté à l'enrichissement d'une annonce ou d'un bien précis. | Jeu de données | Endpoint | Fiche | | ------------------------------ | -------- | ------------------------------------------------------------------------------------------------------ | | DVF (valeurs foncières) | Search | [Recherche DVF](/api-v2-reference/dvf-search/recherche-dvf) | | DVF (valeurs foncières) | Match | [Match DVF à un bien immobilier](/api-v2-reference/dvf-match/match-dvf-à-un-bien-immobilier) | | DPE (diagnostics énergétiques) | Search | [Recherche DPE](/api-v2-reference/dpe-search/recherche-dpe) | | DPE (diagnostics énergétiques) | Match | [Match DPE à une annonce immobilière](/api-v2-reference/dpe-match/match-dpe-à-une-annonce-immobilière) | ## Liens utiles * [Estimer un bien](/playbooks/estimer-un-bien) — playbook AVM * [Property vs Advert](/concepts/property-vs-advert) — choisir la bonne entité côté Fluximmo # Filtres communs de recherche immobilière API Source: https://doc.fluximmo.io/concepts/filtres-communs Référence des filtres communs : search Properties + alertes Properties + alertes Adverts. Location, offer, price, habitation, meta, type. Cette page liste les filtres communs aux endpoints **search Properties** et **alertes Properties / Adverts**. Le payload de filtres d'une alerte ADVERT est très proche de celui d'un search property — la structure d'une zone est identique, mais la **cardinalité diffère** : côté Property, `location` est un **tableau** (multi-zones combinées en `OR`) ; côté Advert, `location` est un **objet unique**. D'autres clés racine peuvent également différer. Il n'existe pas d'endpoint search ADVERT public ; les annonces brutes passent toujours par une alerte webhook. Chaque sous-objet est optionnel ; les filtres absents ne contraignent pas le résultat. Tous les filtres top-level sont combinés en `AND` logique. Quand un filtre prend une **array d'objets** (ex. côté Property : `location[]`, `offer[]`), les éléments de l'array sont combinés en `OR`. Côté Advert, `location` est un **objet unique** → pas de `OR` possible sur les zones. **Complétude des annonces variable.** Selon les annonceurs, certains champs sont fréquemment renseignés (prix, surface, ville, type), d'autres rarement (nombre de toilettes, étage, exposition, équipements rares). **Plus vous filtrez sur des champs rares, plus vous risquez d'écarter des biens pertinents.** Privilégiez d'abord les filtres essentiels et n'affinez qu'au besoin. ## `location[]` — zones géographiques `location` est un **tableau** : plusieurs zones sont combinées en `OR` (ex. Paris OU 92 OU 93). Chaque entrée accepte exactement **un** des modes ci-dessous. | Mode | Type | Exemple | | ----------------------------------- | ---------------- | --------------------------- | | `postalCode` | string | `"75011"` | | `inseeCode` | string | `"75056"` | | `department` | string (2 chars) | `"75"` | | `city` | string | ⚠️ **ignoré** par le moteur | | `locationCoordinate.geoBoundingBox` | objet | bbox carte | | `locationCoordinate.geoDistance` | objet | rayon autour d'un point | Détails complets, exemples par mode et logique multi-zones : [Recherche géographique](/concepts/recherche-geographique). ```json theme={null} { "location": [{ "postalCode": "75011" }, { "department": "92" }] } ``` ## `offer[].type` — type d'offre `offer` est un tableau. Le champ `type` est un enum : | Valeur | Sens | | -------------------------- | ---------------------------- | | `OFFER_BUY` | Vente | | `OFFER_RENT` | Location | | `OFFER_LIFE_ANNUITY_SALE` | Viager | | `OFFER_HOLIDAYS` | Location vacances | | `OFFER_AUCTION` | Vente aux enchères | | `OFFER_BUSINESS_TAKE_OVER` | Reprise de fonds de commerce | | `OFFER_LEASE_BACK` | Lease-back | | `OFFER_UNKNOWN` | Inconnu (à éviter) | ```json theme={null} { "offer": [{ "type": "OFFER_BUY" }] } ``` ## `price` — prix | Champ | Type | Sens | | ----------------------------------------- | ---- | -------------------------------------------------- | | `price.initial.value.min` / `.max` | int | Range du prix initial (à la première publication). | | `price.latest.value.min` / `.max` | int | Range du prix courant (le plus récent). | | `price.initial.valuePerArea.min` / `.max` | int | Prix au m² initial. | | `price.latest.valuePerArea.min` / `.max` | int | Prix au m² courant. | ```json theme={null} { "price": { "latest": { "value": { "min": 200000, "max": 450000 } } } } ``` ## `habitation` — caractéristiques du logement | Champ | Type | | --------------------------------------- | -------- | | `habitation.surface.total.min` / `.max` | int (m²) | | `habitation.bedroomCount.min` / `.max` | int | | `habitation.roomCount.min` / `.max` | int | | `habitation.bathroomCount.min` / `.max` | int | | `habitation.wcCount.min` / `.max` | int | | `habitation.propertyCondition[]` | array | Note : `habitation.floor` n'est pas un champ direct du DTO — il fait partie de `habitation.characteristics` ou `habitation.features` selon le modèle. Utilisez `surface`, `bedroomCount`, `roomCount` pour les filtres usuels. ```json theme={null} { "habitation": { "surface": { "total": { "min": 40, "max": 80 } }, "bedroomCount": { "min": 2 }, "roomCount": { "min": 3 } } } ``` ## `type[]` — classe de bien Tableau d'enum `EnumPropertyClassDto` : | Valeur | Sens | | ---------------- | ------------------------- | | `CLASS_FLAT` | Appartement | | `CLASS_HOUSE` | Maison | | `CLASS_PROGRAM` | Programme neuf | | `CLASS_LAND` | Terrain | | `CLASS_PARKING` | Parking / box | | `CLASS_OFFICE` | Bureau | | `CLASS_SHOP` | Local commercial | | `CLASS_PREMISES` | Local pro / professionnel | | `CLASS_BUILDING` | Immeuble | | `CLASS_ROOM` | Chambre | | `CLASS_OTHER` | Autre | | `CLASS_UNKNOWN` | Inconnu (à éviter) | ```json theme={null} { "type": ["CLASS_FLAT", "CLASS_HOUSE"] } ``` ## `meta` — métadonnées de cycle de vie | Champ | Type | Sens | | ----------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------- | | `meta.isTotallyOffline` | boolean | `false` = exclure les biens dont **toutes** les adverts sont offline. Très utile pour ne voir que des biens encore actifs. | | `meta.firstSeenAt` | range date | Première fois que Fluximmo a vu le bien (`min` / `max` ISO 8601). | | `meta.lastSeenAt` | range date | Dernière fois que Fluximmo a vu le bien. | ```json theme={null} { "meta": { "isTotallyOffline": false, "firstSeenAt": { "min": "2025-01-01T00:00:00Z" } } } ``` ## Filtres spécifiques Adverts (alerte) Au-delà des filtres communs ci-dessus, les alertes ADVERTS exposent : `isOnline` (équivalent advert de `meta.isTotallyOffline` côté property), `isPro`, `isExclusive`, `source` (portail d'origine), `seller[]`, `firstSeenAt`. Voir [Advert — modèle complet](/api-v2-reference/_api-models/advert). ## Pour aller plus loin * [Recherche géographique](/concepts/recherche-geographique) — détail des modes de zone et multi-zones OR. * [Property — modèle complet](/api-v2-reference/_api-models/property) * [Advert — modèle complet](/api-v2-reference/_api-models/advert) * [Search Properties](/api-v2-reference/properties-search/search-properties) # Match types & cycle de vie d'une alerte Source: https://doc.fluximmo.io/concepts/match-types-cycle-alerte Comprendre les types de matches (CREATED, MERGED, ADVERT_EVENT) et pourquoi recréer une alerte fait perdre l'historique. Un **match** est l'événement métier qu'émet Fluximmo lorsqu'une advert ou une property satisfait les critères d'une alerte. Chaque match est livré au webhook de l'alerte. Le **type** de match indique la nature de l'événement : nouveau bien découvert, fusion, ou événement sur un bien déjà matché. ## Match types — Adverts | Type (souscription `match[]`) | Quand | Branche du webhook canonique | | ----------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ALERT_MATCH_CREATED` | Une nouvelle advert vient d'être ingérée et qualifiée par l'alerte. Émis **une seule fois** par advert. | `data.created[]` — `AdvertDto` complet. | | `ALERT_MATCH_ADVERT_EVENT` | Une advert **historiquement matchée par cette alerte** a évolué (prix, status). | `data.updated[]` — DTO réduit (`flxId`, `currentPrice`, `isOnline`). Type d'event (PRICE / REPUBLISHED / UNPUBLISHED) **dérivé côté client** par diff vs état local. | ## Match types — Properties | Type | Quand | Payload (webhook) | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | | `ALERT_MATCH_CREATED` | Une property vient d'être créée et qualifiée par l'alerte. | IDs uniquement → refetch. | | `ALERT_MATCH_MERGED` | Une nouvelle advert vient d'être fusionnée dans une property **déjà matchée**. Émis uniquement à la fusion (pas à la création de la property elle-même). | IDs uniquement → refetch. | ## Comparatif Adverts vs Properties | | Adverts | Properties | | ------------------------------------ | --------------------------------------------------------------------------------------------------------- | ---------------------------------------- | | `CREATED` | Oui | Oui | | `MERGED` | Non par nature (chaque advert reste distincte) — mais `propertyFlxId` permet la déduplication côté client | **Oui** — fusion = nouveau matching | | Events post-création (`PRICE`, etc.) | **Oui** via `ADVERT_EVENT` | **Non** — pas d'event natif sur Property | Si vous avez besoin d'événements granulaires (variation de prix, online/offline), **utilisez les alertes Adverts**. Les alertes Properties ne livrent que des notifications de découverte/fusion. ## Events ADVERT — à dériver côté client Le webhook ADVERT pousse les advert evolved dans `data.updated[]` avec un DTO minimal (`flxId`, `currentPrice {value, valuePerArea}`, `isOnline`). Il n'y a **pas de champ `event_type`** côté serveur — vous reconstituez l'événement par comparaison avec votre état stocké local : | Event dérivé | Règle de détection (côté client) | Note | | ------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `PRICE` | `advert.currentPrice.value` ≠ valeur précédemment stockée | Toute variation, **même 1 €**. Pas de seuil serveur. Filtrez côté client si vous voulez ignorer les micro-variations. | | `REPUBLISHED` | `advert.isOnline` passe de `false` à `true` | Annonce remise en ligne. | | `UNPUBLISHED` | `advert.isOnline` passe de `true` à `false` | Annonce retirée. | **Pré-requis** : maintenez localement (`flxId` → `{ price, isOnline }`) à jour à chaque webhook (`data.created` ET `data.updated`) pour pouvoir comparer au prochain. Sans état local, impossible de dériver les events. **`CHECK` non émis.** Les vérifications périodiques sans changement réel ne déclenchent aucun webhook — pas de bruit. ## Pas de backfill à la création Une alerte ne match **que** les annonces ingérées **après** sa création (`createdAt`). Les annonces déjà présentes dans le catalogue qui rempliraient les critères ne déclencheront pas de match. ```mermaid theme={null} sequenceDiagram participant Fluximmo participant Alerte participant Webhook Note over Fluximmo: t = -2 j
advert A1 ingerée (qualifiante) Note over Alerte: t = 0
création de l'alerte Note right of Alerte: A1 jamais matchée
(antérieure) Fluximmo->>Webhook: t = 1 → A2 ingérée → ALERT_MATCH_CREATED Fluximmo->>Webhook: t = 2 → A2 changement de prix → ALERT_MATCH_ADVERT_EVENT (PRICE) Fluximmo->>Webhook: t = 3 → A2 passe offline → ALERT_MATCH_ADVERT_EVENT (UNPUBLISHED) Note over Alerte: t = 4
DELETE + recréation alerte Note right of Alerte: ⚠️ historique A2 perdu
plus aucun event sur A2 ``` **Pattern recommandé pour couvrir passé + futur** : 1. **Côté PROPERTIES** : `POST /v2/protected/properties/search` one-shot avec vos critères pour récupérer les biens déjà ingérés ; en parallèle, créez une alerte property pour le flux continu. 2. **Côté ADVERTS** : créez l'alerte ADVERT pour le flux continu, et **demandez par mail** à [contact@fluximmo.com](mailto:contact@fluximmo.com) la réception du backfill historique sur votre webhook (volume + dates précisés). 3. **Dédupliquez** côté client : un même `flxId` peut réapparaître si ingéré à nouveau (rare). Utilisez `flxId` comme clé d'idempotence. ## Pour aller plus loin * [Webhooks — architecture, retry, sécurité](/concepts/webhooks) * [Property vs Advert](/concepts/property-vs-advert) * Endpoints alertes : `PUT /v2/protected/properties/search/alerts`, `PUT /v2/protected/adverts/search/alerts`, `PATCH /v2/protected/{adverts,properties}/search/alerts/{flxId}`. # Property vs Advert — bien dédupliqué ou annonce portail Source: https://doc.fluximmo.io/concepts/property-vs-advert Comprendre la différence entre Property (bien physique dédupliqué) et Advert (annonce portail) — décision matrix, propertyFlxId pour reconstituer la dédup côté client, anti-patterns. Fluximmo expose **deux entités** parce que le marché immobilier français est structurellement bruité : un même bien physique apparaît typiquement sur 3 à 8 portails (grands réseaux d'agences, mandataires indépendants, agences locales, etc.) avec des descriptions, des prix et des photos qui divergent. Compter les annonces brutes mène à de la double-comptabilité ; ne regarder que le bien dédupliqué fait perdre la trace fine des publications. * **Advert** : 1 annonce sur 1 portail = 1 advert. Vue brute, exhaustive, source-par-source. Accessible **uniquement via alerte webhook** (pas de search public). * **Property** : 1 bien physique unique = N adverts agrégés côté Fluximmo. Vue dédupliquée, accessible via `POST /v2/protected/properties/search` (BAAS API à la demande) + alerte webhook qui livre des IDs. ## Quand choisir lequel — version courte **Par défaut → PROPERTY.** Si votre besoin est "rechercher des biens sur plusieurs portails sans les compter en double" (BAAS API, marketplace, analytics, scoring, AVM), Property est la primitive simple : une requête, un résultat dédupliqué, payez à l'usage. C'est ce que 90 % des intégrations veulent. **ADVERTS** est nécessaire si vous voulez : * répliquer la donnée Fluximmo dans votre propre DB (le seul moyen — il n'y a pas d'export bulk côté property) ; * recevoir tous les events de cycle de vie en temps réel avec le payload complet self-contained (`PRICE`, `REPUBLISHED`, `UNPUBLISHED`) ; * tracker l'inventaire d'une agence ou d'un seller portail-par-portail. ## Décision rapide ## Modèle d'agrégation ```mermaid theme={null} flowchart LR A1["Advert · portail A
flxId=adv_aaa
propertyFlxId=adv_aaa"] --> P["Property
flxId=prop_aaa"] A2["Advert · portail B
flxId=adv_bbb
propertyFlxId=adv_aaa"] --> P A3["Advert · portail C
flxId=adv_ccc
propertyFlxId=adv_aaa"] --> P P --> O["1 bien physique
3 publications portail"] ``` Une `Property` est créée (ou enrichie via `MERGED`) dès qu'un advert ingéré est rattaché à un bien physique déjà connu. ## Reconstituer la dédup côté client (propertyFlxId) Chaque advert porte un champ `propertyFlxId`. **Deux adverts avec le même `propertyFlxId` sont des doublons d'un même bien physique.** La règle d'attribution : * La première advert non-dupliquée d'une chaîne de dédup a `flxId == propertyFlxId` (l'advert est sa propre racine). * Chaque advert ingérée ensuite et reconnue comme doublon hérite du `propertyFlxId` de la racine. Concrètement, si vous recevez deux adverts en webhook : ```json theme={null} // Advert 1 — racine de la chaîne { "flxId": "adv_aaa", "propertyFlxId": "adv_aaa", "source": { "website": "" } } // Advert 2 — doublon ingéré 12 h plus tard { "flxId": "adv_bbb", "propertyFlxId": "adv_aaa", "source": { "website": "" } } ``` Vous savez côté DB que `adv_bbb` est un doublon de `adv_aaa`. Un client ADVERTS-only peut ainsi reconstruire sa propre vue agrégée à la `Property` en groupant par `propertyFlxId` — sans avoir à appeler le côté property de Fluximmo. ## Reproduire la `Property` côté client Les adverts contiennent **toute l'information** nécessaire pour reconstruire le concept `Property` côté client : il suffit de grouper les adverts par `propertyFlxId` et de maintenir l'agrégat à jour via les événements webhook (`data.created` pour nouveaux adverts, `data.updated` pour les changements de prix/statut). Voir le playbook [Réplication BDD adverts](/playbooks/replicate-bdd-adverts). ## Cas d'usage **Property** — Cas d'usage **principal** côté API : `POST /v2/protected/properties/search` retourne directement les biens dédupliqués multi-portails. Une requête, un résultat propre, facturé à l'appel. **Property** — Affichez un bien unique avec ses N sources en bas de fiche. Évitez les doublons visuels qui dégradent l'UX. **Property** — Comptez des biens, pas des publications. Les KPIs (stock, durée moyenne en ligne, prix médian) doivent être basés sur la déduplication serveur. **Property** — Estimer la valeur d'un bien suppose un objet unique. Utilisez `Property` + `estimatedPrice` pour la valorisation. **Advert** — Seule option pour avoir un stream complet de la donnée Fluximmo en local. Le webhook livre le payload entier et tous les events de cycle de vie. **Advert** — `seller.flxId` + `source.website` permettent de suivre l'inventaire d'un acteur sur un portail donné, et de mesurer la durée de vie de chaque annonce. ## Coût / perf | Axe | ADVERTS (alerte webhook) | PROPERTIES (search API + alerte) | | ---------------------- | ------------------------------------------------------------- | ------------------------------------------------------- | | Mode | Push continu, payload complet, doublons inclus | Pull à la demande (search) + push allégé (alerte = IDs) | | Volume webhook | Élevé : toutes les annonces matching, doublons inter-portails | Faible côté alerte (IDs uniquement, refetch optionnel) | | Ownership de la donnée | Fort — vous pouvez tout répliquer en local | Moindre — pas de stream continu côté client | | Quand c'est gagnant | Réplication DB, market intel temps réel, analytics interne | Recherche BAAS, scoring à la demande, UI temps réel | **Volume webhook ADVERTS** — c'est un mode haut débit. Sur des filtres larges (un département, plusieurs régions), prévoyez la capacité receiver en conséquence avant d'activer l'alerte. Côté PROPERTIES, l'alerte `MERGED` ne se déclenche **que** quand une nouvelle advert est fusionnée dans une property déjà matchée — pas à chaque check de fraîcheur. ## Anti-patterns **Stocker en DB les résultats d'un search PROPERTIES — KO.** L'API properties n'est pas conçue pour ce cas. Aucun event `UNPUBLISHED` n'est émis côté property : si un bien est retiré du marché, votre cache local va dériver sans signal. Pour répliquer Fluximmo en local, utilisez **ADVERTS** (alerte webhook avec payload complet + events `UNPUBLISHED`). **Faire de l'analytics agrégé sur Adverts sans grouper par `propertyFlxId` — KO.** Un même bien physique compté 5 fois (une fois par portail) gonfle le stock, fausse les médianes prix/m² et biaise les durées en ligne. Si vous répliquez les adverts en DB, groupez par `propertyFlxId` au moment du calcul KPI pour retrouver la vue dédupliquée — ou utilisez directement l'API PROPERTIES côté Fluximmo. **Recréer une advert pour "rafraîchir" une property — KO.** Les adverts sont fusionnées automatiquement par le moteur de matching. Ne cherchez pas à recréer ou dupliquer manuellement : la déduplication est un service de Fluximmo. ## Pour aller plus loin * [Match types & cycle de vie d'une alerte](/concepts/match-types-cycle-alerte) — quand vous recevez `CREATED`, `MERGED`, `ADVERT_EVENT`. * [Property — modèle complet](/api-v2-reference/_api-models/property) * [Advert — modèle complet](/api-v2-reference/_api-models/advert) * [Search Properties](/api-v2-reference/properties-search/search-properties) # Recherche géographique immobilière : zones, bbox, distance Source: https://doc.fluximmo.io/concepts/recherche-geographique Filtrer par zone : 4 modes administratifs + 2 modes géométriques (bbox, distance) + multi-zones OR. Le filtre `location[]` est un **tableau** : chaque entrée définit **une** zone, et les zones sont combinées en `OR` côté moteur. Vous disposez de 6 modes : 4 administratifs (codes) et 2 géométriques (lat/lng). ## Vue d'ensemble | Catégorie | Mode | Champ | Quand l'utiliser | | ------------- | ------------ | -------------------------------------------- | ------------------------------------ | | Administratif | Code postal | `postalCode` | Zone connue par CP | | Administratif | Code INSEE | `inseeCode` | Identifiant commune unique (5 chars) | | Administratif | Département | `department` | Zone large (ex. `"75"`) | | Administratif | Ville | `city` | ⚠️ **ignoré** | | Géométrique | Bounding box | `locationCoordinate.location.geoBoundingBox` | Carte interactive (viewport) | | Géométrique | Rayon | `locationCoordinate.location.geoDistance` | "Tout dans X km autour de Y" | ## Modes administratifs ### Code postal ```json theme={null} { "location": [{ "postalCode": "75011" }] } ``` ### Code INSEE ```json theme={null} { "location": [{ "inseeCode": "75056" }] } ``` ### Département ```json theme={null} { "location": [{ "department": "75" }] } ``` ### Ville (⚠️ ignoré) ## Modes géométriques ### `geoBoundingBox` — rectangle latitude/longitude ```json theme={null} { "location": [{ "locationCoordinate": { "location": { "geoBoundingBox": { "topLeft": { "lat": 48.9020, "lon": 2.2240 }, "bottomRight": { "lat": 48.8155, "lon": 2.4699 } } } } }] } ``` **Contraintes** : * `topLeft.lat > bottomRight.lat` (latitude croît vers le nord). * `topLeft.lon < bottomRight.lon` (longitude croît vers l'est). Une bbox inversée ne lève pas d'erreur côté moteur mais retourne **0 résultat**. ### `geoDistance` — rayon autour d'un point ```json theme={null} { "location": [{ "locationCoordinate": { "location": { "geoDistance": { "pin": { "lat": 48.8584, "lon": 2.2945 }, "distanceKm": 20 } } } }] } ``` ## Multi-zones — logique OR `location` étant un array, **chaque entrée est combinée en OR**. Mélangez librement les modes : ```json theme={null} { "location": [ { "postalCode": "75011" }, { "department": "92" }, { "department": "93" }, { "locationCoordinate": { "location": { "geoDistance": { "pin": { "lat": 48.8584, "lon": 2.2945 }, "distanceKm": 5 } } } } ] } ``` → Résultats : Paris 11e **OU** 92 **OU** 93 **OU** 5 km autour de la Tour Eiffel. **Différence PROPERTY vs ADVERT** : côté `/properties/search` et `/properties/search/alerts`, `location` est un **array** (multi-zones OR). Côté `/adverts/search` et `/adverts/search/alerts`, `location` est un **objet unique** — pour cibler plusieurs zones côté advert, créez une alerte par zone. Cf [Property vs Advert](/concepts/property-vs-advert). ## Quel mode choisir ? ```mermaid theme={null} flowchart TD Start["Quelle est ma zone ?"] --> Q1{"Connue par
code admin ?"} Q1 -- "1 commune" --> CP["postalCode
ou inseeCode"] Q1 -- "1 département" --> Dep["department"] Q1 -- "Non" --> Q2{"Forme
géométrique ?"} Q2 -- "Rectangle / viewport carte" --> BB["geoBoundingBox"] Q2 -- "Rayon autour d'un point" --> Dist["geoDistance"] ``` ## Anti-patterns * **`distanceKm` = 0 ou négatif** → erreur ou 0 résultat selon le mode. * **Bbox inversée** (`topLeft.lat < bottomRight.lat`) → 0 résultat. * **Mixer `postalCode` et `city` dans la même entrée** → `city` est ignoré, donc le `postalCode` prend le dessus, mais c'est ambigu. Choisissez **un** mode par entrée. ## Pour aller plus loin * [Filtres communs](/concepts/filtres-communs) — l'ensemble des autres filtres de search. * [Playbook — recherche géographique avancée](/playbooks/recherche-geographique-avancee) * [Search Properties](/api-v2-reference/properties-search/search-properties) # Webhooks API immobilier — retry, signature, payload Source: https://doc.fluximmo.io/concepts/webhooks Architecture webhooks Fluximmo : retry, signature, IDs vs payload complet, bonnes pratiques de réception. Un webhook Fluximmo est un `POST` HTTPS émis par notre infrastructure vers une URL que vous fournissez à la création d'une **alerte**. Il porte les matches d'annonces (nouveaux biens, changements de prix, mises hors ligne, fusions) en quasi temps réel — typiquement dans la minute qui suit l'ingestion d'une annonce ou l'occurrence d'un événement. ## Pipeline de livraison ```mermaid theme={null} flowchart LR A["Alerte
match"] --> Q1["Queue interne
Fluximmo"] Q1 --> P["POST
x-webhook-key"] P --> R{"HTTP 2xx
en moins de 10 s ?"} R -- "OUI" --> OK["Livré"] R -- "NON / timeout" --> RT["Retry policy
2 immédiats + 10 x 5 min"] RT -- "succès" --> OK RT -- "epuise" --> DROP["DROP apres ~50 min"] ``` **Côté client, le pattern recommandé est inverse** : ne traitez jamais le webhook dans la requête HTTP, **ackez en moins de 100 ms** et empilez le payload dans une queue durable (RabbitMQ, SQS, Kafka, Redis Streams). Un worker async fait ensuite le travail métier. ## Payloads : Adverts vs Properties | Aspect | Adverts | Properties | | ------------------------ | -------------------------------------------------------------- | ------------------------------------------------ | | **Contenu du payload** | `AdvertDto` complet (créations) + DTO réduit (updates) | **IDs uniquement** (`propertyFlxId[]`) | | **Refetch nécessaire ?** | Non pour les créations — voir note ci-dessous pour les updates | **Oui** : `GET /v2/protected/properties/{flxId}` | | **Volume réseau** | Élevé (jusqu'à plusieurs Mo par batch) | Faible (liste d'IDs) | | **Latence métier** | Faible (zéro round-trip API) | +1 round-trip HTTP par property | | **Cas d'usage typique** | Monitoring temps réel, miroir BDD | Analytics, déduplication, valorisation | Pourquoi cette asymétrie ? Une `Property` est une entité agrégée qui peut évoluer entre l'émission et la réception du webhook (un nouvel advert peut s'y rattacher). Refetcher garantit la version la plus fraîche au moment où vous la traitez. ### Shape canonique (côté ADVERT) Le payload reçu est toujours wrappé sous `data` avec deux branches `created` (nouvelles adverts matchées) et `updated` (changements sur des adverts précédemment matchées) : ```json theme={null} { "data": { "created": [ { "alert_id": "alr_xxx", "adverts": [ /* AdvertDto complet : flxId, propertyFlxId, currentPrice, habitation, location, source, isOnline... */ ] } ], "updated": [ { "alert_id": "alr_xxx", "adverts": [ /* DTO réduit : flxId, currentPrice (value + valuePerArea), isOnline */ ] } ] } } ``` **Events PRICE / REPUBLISHED / UNPUBLISHED ne sont pas explicitement émis.** Ils se **dérivent côté client** en comparant les adverts de `data.updated[]` avec votre état local stocké : `currentPrice.value` qui change ⇒ PRICE, `isOnline` qui flip ⇒ REPUBLISHED ou UNPUBLISHED. Voir [Match types & cycle alerte](/concepts/match-types-cycle-alerte) et le [playbook track prix](/playbooks/track-price-changes). ## Sécurité — header `x-webhook-key` Chaque webhook porte un header `x-webhook-key` que vous avez défini à la création de l'alerte (champ `webhook.headerValue` ou équivalent dans le payload alerte). Validez-le côté client avant tout traitement. ```python Python (FastAPI) theme={null} from fastapi import FastAPI, Request, Header, HTTPException import os EXPECTED_KEY = os.environ["FLUXIMMO_WEBHOOK_KEY"] app = FastAPI() @app.post("/webhook/fluximmo") async def handler(req: Request, x_webhook_key: str = Header(default="")): if x_webhook_key != EXPECTED_KEY: raise HTTPException(status_code=401, detail="invalid key") payload = await req.json() # ack rapide → enqueue → 200 await enqueue(payload) return {"ok": True} ``` ```js Node (Express) theme={null} const EXPECTED_KEY = process.env.FLUXIMMO_WEBHOOK_KEY; app.post("/webhook/fluximmo", express.json({ limit: "10mb" }), async (req, res) => { if (req.header("x-webhook-key") !== EXPECTED_KEY) { return res.status(401).end(); } await queue.publish(req.body); res.status(200).end(); }); ``` N'effectuez **aucun traitement métier synchrone** dans le handler HTTP : pas d'appel BDD lourd, pas d'appel API tiers, pas d'envoi d'email. Acceptez vite, faites le travail dans un worker. ## Politique de retry | Étape | Délai | Détail | | ------------------ | -------------------- | --------------------------------------- | | Tentative initiale | t = 0 | Timeout HTTP côté Fluximmo | | Retries immédiats | t + \~quelques s | 2 retries rapides après échec | | Retries longs | toutes les 5 min | 10 retries supplémentaires | | **DROP** | \~50 min après t = 0 | Le webhook est définitivement abandonné | **Codes HTTP considérés OK** : `200`, `201`, `202`, `203`, `204`, `205`. Tout autre code (4xx, 5xx) déclenche un retry. **SLO côté client** : votre handler doit répondre en **30 à 50 secondes maximum**. Au-delà, Fluximmo timeout et entre en retry. Un handler bloquant à 30 s sous charge devient une cascade d'échecs. **Idempotence obligatoire.** Vous recevrez parfois le même webhook deux fois (retry après timeout côté réseau alors que le serveur avait déjà accepté). Dédupliquez côté client avec une clé stable, par exemple `(advert.flxId, branch, alert_id)` où `branch` ∈ `{ "created", "updated" }`, ou `(advert.flxId, currentPrice.value, isOnline)` si vous voulez aussi capturer les diffs. ## Architecture recommandée ```mermaid theme={null} flowchart LR W["POST webhook
Fluximmo"] --> H["HTTP handler
(stateless)"] H -->|valide x-webhook-key| Q["Durable queue
SQS / RabbitMQ"] H -->|200 OK <100 ms| W Q --> WK["Async worker
(scalable)"] WK --> DB["BDD client"] WK --> NOTIF["Notif user
email / push"] ``` Le détail (sizing queue, dead-letter, observabilité, scaling worker) sera couvert dans le playbook [Architecture haute volumétrie](/playbooks/webhook-volume-architecture). ## Pour aller plus loin * [Match types & cycle de vie d'une alerte](/concepts/match-types-cycle-alerte) — quels events arrivent dans quel webhook. * [Webhook livraison — règles détaillées](/ressources/webhooks-livraison) * [Playbook — Architecture webhook haute volumétrie](/playbooks/webhook-volume-architecture) # Quickstart API Fluximmo — premier appel en 5 minutes Source: https://doc.fluximmo.io/demarrer/quickstart 5 minutes en self-service : sample no-auth → clé test gratuite → premier search. Vous allez : (1) voir un payload sans inscription, (2) récupérer une clé test gratuite sur `my.fluximmo.io`, (3) lancer votre premier search authentifié. Tout en self-service. **Couverture & fraîcheur** — ce que vous interrogez derrière l'API : * 75 000+ nouvelles annonces ingérées chaque jour * Médiane de 5 minutes entre publication portail et mise à disposition (webhook / API) * 99 % du marché immobilier français couvert * Service en production continue depuis 2017 L'endpoint `sample/properties` est public, sans authentification. Il renvoie 10 properties représentatives pour valider votre parsing. ```bash curl theme={null} curl --location 'https://api.fluximmo.io/v2/sample/properties' ``` ```python Python theme={null} import requests response = requests.get("https://api.fluximmo.io/v2/sample/properties") properties = response.json()["data"] print(f"Reçu {len(properties)} properties") ``` ```javascript Node theme={null} const response = await fetch("https://api.fluximmo.io/v2/sample/properties"); const { data: properties } = await response.json(); console.log(`Reçu ${properties.length} properties`); ``` Trois autres endpoints sample sont disponibles (adverts + payloads webhook type). Voir [Données d'exemple](/demarrer/sample-data). Rendez-vous sur [my.fluximmo.io](https://my.fluximmo.io) → onglet **API** → **Générer une clé test**. * Gratuite, valable **1 semaine** * Accès limité (volume et périmètre réduits, suffisant pour valider une intégration) * Aucun paiement, aucune carte bancaire requise Conservez la clé en variable d'environnement : ```bash theme={null} export FLUXIMMO_API_KEY="votre-cle-test" ``` L'endpoint `properties/search` retourne des biens **dédupliqués** multi-portails. Ici, on demande 10 biens en vente dans le 75001. ```bash curl theme={null} curl --location 'https://api.fluximmo.io/v2/protected/properties/search' \ --header 'x-api-key: '"$FLUXIMMO_API_KEY"'' \ --header 'Content-Type: application/json' \ --data '{ "size": 10, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [{ "postalCode": "75001" }], "offer": [{ "type": "OFFER_BUY" }] }, "fullTexts": [], "keywords": [] } }' ``` ```python Python theme={null} import os import requests response = requests.post( "https://api.fluximmo.io/v2/protected/properties/search", headers={"x-api-key": os.environ["FLUXIMMO_API_KEY"]}, json={ "size": 10, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [{"postalCode": "75001"}], "offer": [{"type": "OFFER_BUY"}], }, "fullTexts": [], "keywords": [], }, }, ) data = response.json() print(f"{data['data']['count']} matches, {len(data['data']['items'])} renvoyés") ``` ```javascript Node theme={null} const response = await fetch( "https://api.fluximmo.io/v2/protected/properties/search", { method: "POST", headers: { "x-api-key": process.env.FLUXIMMO_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ size: 10, sortBy: "FIRST_SEEN_AT", orderBy: "DESC", search: { filterProperty: { location: [{ postalCode: "75001" }], offer: [{ type: "OFFER_BUY" }], }, fullTexts: [], keywords: [], }, }), } ); const data = await response.json(); console.log(`${data.data.count} matches, ${data.data.items.length} renvoyés`); ``` La réponse retourne un objet paginé `{ data: { count, items: Property[], searchAfterHash } }`. Schéma complet : [`POST /v2/protected/properties/search`](/api-v2-reference/properties-search/search-properties). Comprendre la différence entre annonce brute et bien dédupliqué avant de choisir l'endpoint. ADVERT = payload complet, PROPERTY = IDs + refetch. Cycle de vie et events. Cycle de vie d'une alerte, events `PRICE` / `REPUBLISHED` / `UNPUBLISHED`. Mettre en place une alerte temps réel sur un département en 15 minutes. Spec consolidée à coller dans ChatGPT / Claude pour générer des payloads valides. Questions fréquentes sur les rate-limits, la facturation, la qualité de la donnée. **Vous générez vos payloads avec un LLM (ChatGPT, Claude) ?** Copiez la [page LLM-ready](/llms-context) dans votre prompt pour des payloads valides du premier coup. Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Données d'exemple API immobilier sans clé (no-auth) Source: https://doc.fluximmo.io/demarrer/sample-data 4 endpoints sample no-auth pour découvrir les structures Property, Advert et webhooks avant inscription. Aucun compte requis. Idéal pour tester votre parsing en local ou découvrir le format avant de demander une clé test. Les 4 endpoints renvoient des données réelles, anonymisées si besoin. ## 1. Échantillon de properties 10 properties (biens dédupliqués multi-portails). Schéma identique à `POST /v2/protected/properties/search`. ```bash curl theme={null} curl --location 'https://api.fluximmo.io/v2/sample/properties' ``` ```python Python theme={null} import requests response = requests.get("https://api.fluximmo.io/v2/sample/properties") properties = response.json()["data"] print(f"Reçu {len(properties)} properties") ``` ```javascript Node theme={null} const response = await fetch("https://api.fluximmo.io/v2/sample/properties"); const { data: properties } = await response.json(); console.log(`Reçu ${properties.length} properties`); ``` Référence : [`GET /v2/sample/properties`](/api-v2-reference/sample-properties/get-a-sample-of-properties). ## 2. Échantillon d'adverts 10 adverts (annonces brutes, 1 advert = 1 URL portail). Schéma identique au payload livré par une alerte ADVERT en webhook. ```bash curl theme={null} curl --location 'https://api.fluximmo.io/v2/sample/adverts' ``` ```python Python theme={null} import requests response = requests.get("https://api.fluximmo.io/v2/sample/adverts") adverts = response.json()["data"] print(f"Reçu {len(adverts)} adverts") ``` ```javascript Node theme={null} const response = await fetch("https://api.fluximmo.io/v2/sample/adverts"); const { data: adverts } = await response.json(); console.log(`Reçu ${adverts.length} adverts`); ``` Référence : [`GET /v2/sample/adverts`](/api-v2-reference/sample-adverts/get-a-sample-of-ads). ## 3. Webhook properties — payload type Exemple du body envoyé par Fluximmo sur un webhook `PROPERTY` (création / merge). Le payload contient les **IDs uniquement** : un refetch via `GET /v2/protected/properties/{flxId}` est requis pour récupérer la donnée enrichie. ```bash curl theme={null} curl --location 'https://api.fluximmo.io/v2/sample/webhook/properties' ``` ```python Python theme={null} import requests response = requests.get("https://api.fluximmo.io/v2/sample/webhook/properties") payload = response.json() print(payload) ``` ```javascript Node theme={null} const response = await fetch( "https://api.fluximmo.io/v2/sample/webhook/properties" ); const payload = await response.json(); console.log(payload); ``` Référence : [`GET /v2/sample/webhook/properties`](/api-v2-reference/sample-properties/sample-properties-webhook-body). ## 4. Webhook adverts — payload type Exemple du body envoyé par Fluximmo sur un webhook `ADVERT` (création, changement de prix, republication, mise hors-ligne). Le payload est **complet**, pas de refetch nécessaire. ```bash curl theme={null} curl --location 'https://api.fluximmo.io/v2/sample/webhook/adverts' ``` ```python Python theme={null} import requests response = requests.get("https://api.fluximmo.io/v2/sample/webhook/adverts") payload = response.json() print(payload) ``` ```javascript Node theme={null} const response = await fetch( "https://api.fluximmo.io/v2/sample/webhook/adverts" ); const payload = await response.json(); console.log(payload); ``` Référence : [`GET /v2/sample/webhook/adverts`](/api-v2-reference/sample-adverts/sample-advert-webhook-body). ## Property vs Advert — API à la demande vs flux temps-réel La différence principale est un **mode d'usage**, pas un format de données : * **`Property` = consultation API à la demande** (recherche/refetch synchrone, idéal pour interfaces utilisateur, dashboards, scoring ponctuel, enrichissement à la requête). * **`Advert` = flux temps-réel via webhook** (gros volume d'événements continus poussés en push, idéal pour réplique de DB, pipelines de traitement, alertes live, ingestion massive). Détail complet : [Property vs Advert](/concepts/property-vs-advert). | Critère | ADVERT | PROPERTY | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | **Définition** | Annonce individuelle sur un portail (1 URL = 1 advert). | Bien physique unique, agrégé à partir d'une chaîne de déduplication d'adverts. | | **Identifiant** | `flxId` (advert) + `propertyFlxId` (rattachement à la chaîne de dédup) | `flxId` (property) + `adverts[]` (constituants) | | **Mode d'accès** | **Webhook uniquement** via alerte ADVERT (pas d'endpoint de search public). Le payload de l'alerte est identique à celui d'un search property côté schéma de filtres. | **API BAAS à la demande** : `POST /v2/protected/properties/search` (+ alerte webhook qui livre des IDs uniquement). | | **Webhook payload** | **Payload COMPLET** de l'advert + bloc `event`. | **IDs uniquement** → refetch via `GET /v2/protected/properties/{flxId}` (facturé). | | **Events supportés** | OUI : `PRICE`, `REPUBLISHED`, `UNPUBLISHED` (`ALERT_MATCH_ADVERT_EVENT`). | Limité : `CREATED` et `MERGED` (uniquement quand fusion d'une nouvelle advert dans la property). Pas d'event `UNPUBLISHED` côté property. | | **Cas d'usage idéal** | Réplication BDD multi-portails, monitoring temps réel, tracking concurrentiel par seller, alerte chasseur avec payload self-contained. | **Recherche multi-portail BAAS API**, analytics agrégé, valorisation, market intel, dédup côté serveur Fluximmo. | | **Anti-pattern** | Analytics agrégé sans grouper par `propertyFlxId` (un bien physique compté N fois). | **Stocker en DB** les résultats search/alertes : pas conçu pour ça, pas d'event `UNPUBLISHED` côté property → drift garanti sans re-fetch régulier. | | **Coût/perf** | Volume webhook élevé (toutes les annonces matching, doublons inclus). Seule option pour répliquer la donnée Fluximmo en local. | À la demande via API : volume contrôlé, mais ownership donnée moindre (pas de stream continu côté client). | **Reconstituer la dédup côté client** — chaque advert porte un `propertyFlxId`. Deux adverts avec le même `propertyFlxId` sont des doublons d'un même bien physique. La première advert non-dupliquée a `flx_id == property_id` ; les suivantes héritent du `property_id` de la chaîne. Un client ADVERTS-only peut donc reconstruire sa propre vue agrégée à la `Property` côté DB. **Cycle de vie alerte** — Supprimer + recréer une alerte = perte d'historique des events sur les annonces matchées par l'ancienne alerte. Préférez `PATCH /alerts/{flxId}` pour modifier en place. **Pas de backfill à la création** — Une alerte ne match QUE les adverts/properties traitées **après** sa création. Les annonces antérieures qualifiantes ne seront jamais émises. Pour couvrir le passé : demandez le backfill historique sur votre webhook par mail à [contact@fluximmo.com](mailto:contact@fluximmo.com). **Events ADVERT réels** — `PRICE` (toute variation, même 1 €), `REPUBLISHED` (offline → online), `UNPUBLISHED` (online → offline). `CHECK` (vérification sans changement) ne déclenche **pas** de webhook. ## Étape suivante Récupérer une clé test gratuite et lancer un premier search authentifié. Décider quel endpoint correspond à votre cas d'usage. Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Moteurs BaaS Source: https://doc.fluximmo.io/doc/main-concepts/baas Cinq moteurs Fluximmo : recherche multi-portail, alertes webhook, analytics, géo, AI/estimation. Comment ils s'articulent côté API. L'offre BaaS Fluximmo se compose de **plusieurs moteurs**, chacun exposé via une famille d'endpoints REST. La même clé API y donne accès, l'authentification est unifiée (`x-api-key`), et les filtres de recherche sont alignés entre les moteurs lorsque c'est pertinent. ## Échelle & couverture Les moteurs Fluximmo s'appuient sur une infrastructure de collecte et de vérification opérée en continu depuis 2017. Les ordres de grandeur ci-dessous donnent le contexte avant de choisir un mode d'intégration (search ponctuel, alerte webhook, replay analytique). * **75 000+ nouvelles annonces ingérées par jour** sur les principaux portails français. * **\~1 million d'annonces vérifiées quotidiennement** (présence en ligne et variation de prix). * **Médiane de 5 minutes** entre publication portail et mise à disposition via webhook / API. * **99 % du marché immobilier français** couvert. * **Plusieurs centaines de millions d'annonces historisées** depuis 2017, requêtables. * **Plusieurs dizaines de millions d'events** (alertes, webhooks) traités chaque jour. * **Service en production continue depuis 2017**, sans interruption. ## Moteur de recherche Coeur de l'offre : interroger le catalogue Fluximmo dédupliqué multi-portails. Une requête, un résultat propre, sans doublons inter-portails. C'est le mode d'accès BAAS API par défaut pour toute intégration qui veut "rechercher des biens" sans gérer la déduplication elle-même. * **Endpoint principal** : [`POST /v2/protected/properties/search`](/api-v2-reference/properties-search/search-properties) — payload riche, pagination cursor, tri canonique `FIRST_SEEN_AT DESC`. * **Variante allégée** : [`POST /v2/protected/properties/search/lite`](/api-v2-reference/properties-search/lite-search-properties) — schéma de réponse réduit pour UI temps réel, mobile, autocomplete. * **Refetch** : `GET /v2/protected/properties/{flxId}` (unitaire) ou `POST /v2/protected/properties/_many` (bulk) pour récupérer un payload complet à partir d'un ou plusieurs IDs. Côté ADVERTS, l'endpoint `POST /v2/protected/adverts/search` existe mais n'est pas activé par défaut (capability sur demande — voir [Capabilities sur demande](/api-reference/capabilities-sur-demande)). En usage standard, les annonces brutes (1 URL = 1 advert) sont consommées via une **alerte webhook ADVERT**, qui livre le payload complet de chaque advert et ses events de cycle de vie. Le schéma de filtres est identique au search property. ## Moteur d'alertes (webhooks) Les alertes transforment une requête search en flux push : à chaque nouveau bien (ou nouvel event sur un bien déjà matché), Fluximmo POSTe sur votre webhook. * **Properties** : `PUT /v2/protected/properties/search/alerts` — payload IDs only, refetch ensuite. Match types `CREATED` + `MERGED` (uniquement à la fusion d'une nouvelle advert). * **Adverts** : `PUT /v2/protected/adverts/search/alerts` — payload **complet**, pas de refetch. Match types `CREATED` + `ADVERT_EVENT` (events `PRICE`, `REPUBLISHED`, `UNPUBLISHED`). Détails : [Match types & cycle de vie d'une alerte](/concepts/match-types-cycle-alerte), [Webhooks — architecture, retry, sécurité](/concepts/webhooks). ## Moteur analytique Statistiques agrégées sur le marché et sur votre périmètre : * **Properties analytics** : comptage et analytics agrégées sur les filtres `properties` (sans paginer) — utile pour estimer la taille d'un marché avant de lancer un search ou une alerte. * **Market analytics** : prix par zone (médianes, distributions, prix au m²), indicateurs de tension par ville. Endpoints sous la tab **Analytics**. ## Moteur géographique Géocodage adressé et inverse, basés sur la BAN/BANO du gouvernement français. Utiles pour transformer une adresse en coordonnées (puis injecter en `geoDistance` dans un search), ou inversement étiqueter un point en adresse lisible. Endpoints sous la tab **Géo**, détails dans le [playbook Géocoder pour search](/playbooks/geocoder-pour-search). ## Moteur AI / Estimation AVM (*Automated Valuation Model*) Fluximmo : prix central + intervalle pour un bien français à partir de sa localisation et de ses caractéristiques. Modèle V2 production, sub-second. * **Endpoint** : [`POST /v2/protected/estimate/property/lite`](/api-ai-reference/estimations/lite-property-estimation) * **Playbook** : [Estimer un bien](/playbooks/estimer-un-bien) ## Pour aller plus loin * [Property vs Advert](/concepts/property-vs-advert) — quelle entité choisir pour votre cas d'usage. * [Filtres communs](/concepts/filtres-communs) — référence des filtres partagés search property + alertes. * [Capabilities sur demande](/api-reference/capabilities-sur-demande) — endpoints non publics par défaut. # Properties & Adverts Source: https://doc.fluximmo.io/doc/main-concepts/properties-adverts Le marché immobilier peut être conceptualisé de différentes manières. Nous proposons deux conceptualisations distinctes selon vos besoins : Properties & Adverts. ## Introduction Comprendre les distinctions entre les Properties et les Adverts au sein de notre écosystème de données immobilières est essentiel pour exploiter efficacement ces données. Cette documentation propose un aperçu approfondi des différences et des cas d'usage des Properties et des Adverts. ## Properties (BAAS) Les Properties représentent des actifs immobiliers tangibles et servent de conteneurs pour les annonces associées. Elles offrent une vue consolidée des informations et sont essentielles pour comprendre le contexte plus large des actifs immobiliers. * **Agrégation des annonces :** Notre plateforme regroupe toutes les annonces associées à un bien spécifique : ces annonces sont soit des doublons provenant de différents portails, soit des mandats émanant de différentes agences, soit des republications de la même annonce (mise à jour de prix, etc.). * **Dédoublonnage :** Les Properties regroupent les annonces dupliquées issues de diverses sources, garantissant l'exactitude des données et réduisant la redondance. Cela s'avère particulièrement précieux lorsque vous traitez plusieurs annonces provenant de différents portails ou agences. * **Consolidation des informations :** Les Properties consolident également différents éléments d'information collectés à partir de toutes leurs annonces. Cela offre une vue d'ensemble complète des attributs du bien. * **Évolution dynamique :** Les Properties évoluent dans le temps à mesure que de nouvelles annonces sont ajoutées ou que les annonces existantes sont mises à jour. Cette intégration continue garantit que les données du bien restent pertinentes et actuelles. En raison de la nature dynamique des Properties, l'intégration via API est la méthode privilégiée pour accéder aux données des biens. Les webhooks ne prennent pas en charge la récupération des données des Properties, car celles-ci sont en perpétuel changement. ## Adverts (BAAS and WEBHOOK) Les Adverts sont des annonces immobilières individuelles provenant de diverses plateformes. Elles sont essentielles pour comprendre les spécificités de chaque annonce et ses caractéristiques. * **Dédoublonnage et association :** Nos algorithmes propriétaires d'AI dédoublonnent les annonces, en associant chaque annonce à son parent dupliqué le cas échéant. * **Unicité via les URLs :** Les Adverts sont identifiées de manière unique par leurs URLs. La republication de la même annonce avec une URL différente entraîne la création d'entrées distinctes, favorisant une représentation précise des données. Les Adverts sont accessibles à la fois via les API (Search ou Alerts) et via les webhooks, vous offrant une flexibilité dans la manière dont vous récupérez et intégrez les données d'annonces. ## Recherche Notre plateforme est exclusivement équipée d'un moteur de requête sophistiqué conçu pour permettre une extraction des données efficace et intuitive. Ce système est le fruit de notre expertise approfondie dans le développement de moteurs de recherche, visant à simplifier votre expérience de recherche et à permettre à vos équipes de se concentrer davantage sur les objectifs métier plutôt que sur les subtilités des filtres de recherche. La fonctionnalité de recherche est répartie en trois composantes principales : * Recherche par mot-clé : Cette fonctionnalité permet de récupérer les données contenant des mots-clés spécifiques dans leur description ou leur titre. C'est une manière directe et précise de trouver exactement ce que vous cherchez. * Recherche en texte intégral : Opérant sur les mêmes champs que la recherche par mot-clé, cette méthode emploie des algorithmes de tolérance aux fautes et probabilistes pour produire une plage de résultats plus large. Cela peut conduire à la découverte de données pertinentes qui ne seraient pas immédiatement évidentes. * Filtres : Nos puissantes options de filtrage vous permettent de cibler précisément les données dont vous avez besoin. Les filtres fonctionnent selon deux principes : Paramètres OR : S'appliquent aux champs qui sont des listes, facilitant un comportement de recherche non exclusif (OR). Cela signifie que vous pouvez récupérer des données correspondant à n'importe lequel des critères spécifiés dans ces champs. Par exemple, si vous avez une liste contenant `["CLASS_HOUSE", "CLASS_FLAT"]`, vous recevrez des annonces qui sont soit un appartement, soit une maison. Paramètres AND : S'appliquent à tous les autres champs, imposant un comportement de recherche exclusif (AND). Cela garantit que les données récupérées répondent à l'ensemble des critères spécifiés dans ces champs. Ce système de requête est intégré de manière cohérente à travers l'ensemble de notre stack, garantissant une fonctionnalité uniforme sur tous les endpoints. Que vous interrogiez des Properties ou tout autre type de données, vous disposerez des mêmes capacités de recherche robustes, vous permettant de créer une expérience de recherche encore plus dynamique et efficace. ## Conclusion Comprendre les différences entre les Properties et les Adverts est déterminant pour optimiser votre utilisation des données immobilières. Les Properties offrent un contexte consolidé et évoluent de manière dynamique, tandis que les Adverts fournissent les détails spécifiques de chaque annonce. Les API constituent le moyen privilégié pour accéder aux Properties comme aux Adverts, offrant des options de récupération de données complètes et adaptables. Pour toute demande technique ou clarification supplémentaire, n'hésitez pas à contacter notre équipe de support technique. # API Fluximmo — données immobilières françaises temps réel Source: https://doc.fluximmo.io/introduction Search, alertes webhook, estimation IA et géocodage BAN/BANO en une seule API. Depuis 2017. Fluximmo — API d'intelligence immobilière française Fluximmo — API d'intelligence immobilière française **L'API d'intelligence immobilière française.** Adverts temps réel, properties dédupliquées multi-portails, estimation AI et géocodage BAN/BANO en un seul endpoint. Depuis 2017, focus FR-first, transparence sur les sources et le scoring. ## Couverture & fraîcheur Nouvelles annonces ingérées chaque jour depuis les principaux portails français. Annonces vérifiées quotidiennement (statut online/offline et variation de prix). Délai entre la publication sur le portail et la mise à disposition via webhook/API. Couverture du marché immobilier français : grands réseaux d'agences, mandataires, agences locales. Plusieurs centaines de millions d'annonces historisées, exploitables pour analytics et valorisation. Dizaines de millions d'événements (alertes, webhooks) traités chaque jour. Service en production sans interruption depuis 2017. ## Par où commencer Trois chemins selon votre profil. 4 endpoints `sample` no-auth. Curl-it en 10 secondes pour voir les structures Property et Advert. Sample → clé test gratuite (1 semaine sur my.fluximmo.io) → premier search authentifié. Spec consolidée à coller dans ChatGPT ou Claude pour générer des payloads valides du premier coup. ## Cas d'usage couverts Webhooks à la seconde sur création / changement de prix / republication. Idéal pour chasseurs immobiliers et marketplaces. Recherche API sur properties dédupliquées multi-portails (Backend-as-a-Service) pour produits temps réel, et analytics/valorisation sur le même socle. Webhook ADVERT = payload complet, pas de refetch. Cycle de vie online/offline tracé event par event. Modèle V2 stable entraîné sur la donnée Fluximmo. Prix central + intervalle, sub-second. Interroger les diagnostics énergétiques (DPE) et les valeurs foncières (DVF) : recherche filtrée paginée, ou match des entrées les plus proches d'un bien. ## Pourquoi Fluximmo * **Depuis 2017 sur le marché français** — collecte multi-portails continue, équipe dédiée, pas de pivot. * **FR-first** — couverture nationale, métadonnées localisées (BAN/BANO), agences identifiées. DPE et DVF sont aussi des endpoints requêtables à part entière (search + match), pas seulement de la métadonnée attachée aux annonces. * **Property = bien physique unique** — déduplication inter-portails pour éviter les doublons en analytics. * **Géo riche** — géocodage adossé à la BAN/BANO Gouv (transparence sur la source), pas de boîte noire. * **`propertyFlxId` côté Advert** — chaque advert porte la racine de sa chaîne de dédup, ce qui permet de reconstituer la vue Property côté client si besoin. **Vous générez vos payloads avec un LLM (ChatGPT, Claude) ?** Copiez la [page LLM-ready](/llms-context) dans votre prompt pour des payloads valides du premier coup. ## Une équipe dédiée disponible Pour les questions techniques en cours d'intégration ou les besoins spécifiques (volumétrie, périmètre custom, conditions startup), écrivez-nous : [contact@fluximmo.com](mailto:contact@fluximmo.com). Nous répondons rapidement, sans étape commerciale obligatoire. Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Contexte LLM — copier dans votre prompt Source: https://doc.fluximmo.io/llms-context Bloc de contexte synthétique à coller dans ChatGPT/Claude/Cursor avant de demander un payload Fluximmo. Copiez intégralement le bloc ci-dessous dans votre prompt LLM (ChatGPT, Claude, Cursor) pour qu'il génère des payloads Fluximmo corrects sans hallucination. Cible: 2-4K tokens. ````md theme={null} # Fluximmo API v2 — Context (FR real-estate intelligence) ## 1. Identity Fluximmo is a French real-estate intelligence API (since 2017). Two core entities: - Property = unique deduplicated physical asset (1 bien = N adverts merged). - Advert = one listing on one portal (raw, with events). Full docs: https://doc.fluximmo.io . Sample no-auth: https://api.fluximmo.io/v2/sample/properties ## 2. Property vs Advert (decision matrix) | Need | Use | |--------------------------------------------|-------------| | Dedup market view, AVM, marketplace | Property | | Per-listing price drops, republish, offline| Advert | | Webhook with full payload + events | Advert | | Webhook IDs only (refetch via GET) | Property | | Lightweight autocomplete / mobile | Lite search | ## 3. Core types (TS-like) ```ts type SearchPropertiesPayload = { size: number, // 1-100 (full) or 1-25 (lite) sortBy: EnumSearchRequestSortBy, orderBy: "ASC" | "DESC", search: { filterProperty: FilterProperty } } type SearchAdvertsPayload = { size: number, sortBy: EnumSearchRequestSortBy, orderBy: "ASC" | "DESC", search: { filterAd: FilterAdvert } } type SaveSearchPropertiesPayload = { search_query: { filterProperty: FilterProperty }, webhook_url: string, // HTTPS public URL match: EnumPropertyAlertMatch[], // ["ALERT_MATCH_CREATED"] is the safe default search_name: string, context?: { metadata?: Record } // QueryContextDto — optional } type SaveSearchAdvertsPayload = { search_query: { filterAd: FilterAdvert }, webhook_url: string, match: EnumAdvertAlertMatch[], search_name: string, context?: { metadata?: Record } } type FilterProperty = { location?: FilterLocation[], // OR across array entries type?: EnumPropertyClass[], offer?: { type: EnumPropertyOfferType }[], price?: FilterPropertyPrice, // {initial|latest}.value.{min,max} habitation?: FilterTypeHabitation, meta?: FilterMeta } type FilterAdvert = { location?: FilterLocation, // single object (NOT array — unlike FilterProperty) type?: EnumPropertyClass[], offer?: { type: EnumPropertyOfferType }[], currentPrice?: FilterAdvertPrice, // field name is `currentPrice` (NOT `price`) habitation?: FilterTypeHabitation, meta?: FilterMeta, isOnline?: boolean // production: set to true on alerts } type FilterLocation = { postalCode?: string, // "75011" inseeCode?: string, // "75056" department?: string, // "75" (2 chars FR) irisCode?: string, // city is silently IGNORED by the engine — DO NOT USE locationCoordinate?: { location: { geoBoundingBox?: { // topLeft.lat > bottomRight.lat AND topLeft.lon < bottomRight.lon topLeft: { lat: number, lon: number }, bottomRight: { lat: number, lon: number } }, geoDistance?: { // Plane distance, not Haversine pin: { lat: number, lon: number }, distanceKm: number // > 0 required } } } } // NOTE: geoShape / POLYGON not exposed. Use geoBoundingBox or multi-zone OR. type FilterPropertyPrice = { initial?: { value: { min?: number, max?: number } }, latest?: { value: { min?: number, max?: number } } } type FilterAdvertPrice = { value?: { min?: number, max?: number }, // EUR valuePerArea?: { min?: number, max?: number } // EUR per m2 } type FilterTypeHabitation = { surface?: { total?: { min?: number, max?: number } }, // m2 bedroomCount?: { min?: number, max?: number }, roomCount?: { min?: number, max?: number }, floor?: { min?: number, max?: number } } type FilterMeta = { isTotallyOffline?: boolean, // PROPERTIES production: set to false firstSeenAt?: { min?: string, max?: string }, // ISO 8601 — production typically min: "2025-01-01T00:00:00.000Z" lastSeenAt?: { min?: string, max?: string }, lastUpdatedAt?: { min?: string, max?: string } } ``` ## 4. Enums (literal values, exact) ```ts type EnumPropertyOfferType = | "OFFER_BUY" | "OFFER_RENT" | "OFFER_LIFE_ANNUITY_SALE" | "OFFER_HOLIDAYS" | "OFFER_AUCTION" | "OFFER_BUSINESS_TAKE_OVER" | "OFFER_LEASE_BACK" | "OFFER_UNKNOWN" type EnumPropertyClass = | "CLASS_FLAT" | "CLASS_HOUSE" | "CLASS_PROGRAM" | "CLASS_LAND" | "CLASS_PARKING" | "CLASS_OFFICE" | "CLASS_SHOP" | "CLASS_PREMISES" | "CLASS_BUILDING" | "CLASS_ROOM" | "CLASS_OTHER" | "CLASS_UNKNOWN" type EnumPropertyAlertMatch = | "ALERT_MATCH_CREATED" // new property matched (safe default) | "ALERT_MATCH_MERGED" // ONLY when a new advert is merged into an already-matched property (advanced, higher volume on large filters) type EnumAdvertAlertMatch = | "ALERT_MATCH_CREATED" // new advert matched | "ALERT_MATCH_ADVERT_EVENT" // PRICE/REPUBLISHED/UNPUBLISHED on previously-matched advert type EnumSearchRequestSortBy = | "FIRST_SEEN_AT" | "LAST_SEEN_AT" | "LAST_UPDATED_AT" | "PRICE" | "RELEVANCE" type EnumOrderBy = "ASC" | "DESC" type EnumPricingScope = "PRICING_ONE_OFF" | "PRICING_MENSUAL" type EnumCurrency = "CURRENCY_EUR" | "CURRENCY_USD" type EnumAdvertReportReason = // body of POST /{adverts|properties}/{flxId}/report | "STILL_ONLINE" | "NOT_ONLINE_ANYMORE" | "WRONG_DATA" | "WRONG_LOCATION" | "DUPLICATE" | "WRONG_AGENCY" | "OTHER" // OTHER => `comment` becomes required ``` ## 5. Hard limits & auth - size: 1-100 (full search) or 1-25 (lite search) - search.keywords / search.fullTexts: max 20 items each - geoDistance.distanceKm > 0 (else 400) - geoBoundingBox: topLeft.lat > bottomRight.lat AND topLeft.lon < bottomRight.lon - API base: https://api.fluximmo.io - Auth: header `x-api-key: ` (get one at https://my.fluximmo.io, free 1-week trial) - GET /v2/protected/{adverts|properties}/url: BLOCKS up to ~45s (on-demand collection). Set the client timeout ABOVE 45s. 200 = document (1 credit); 202 = pending, retry the same url after body.data.retry_after_s (no Retry-After header); 422 wrapped in `data` = rejected by ingestion, NEVER retry. 202/422 are not billed. No server-side dedup: two concurrent calls on the same url = two collections and two credits. - POST /v2/protected/{adverts|properties}/{flxId}/check: 1 credit on ANY 2xx, including `nothing_to_check`. No request body. Property side re-checks online adverts only, 10 max. - POST /v2/protected/{adverts|properties}/{flxId}/report: FREE, body {reason, comment?}. Both answer 200 immediately; the refreshed data is NOT in the response — re-read GET /{flxId} a couple of minutes later, or receive it on your alert webhook. - Validation errors are HTTP 422 (not 400), code 10002, one message per call. ## 6. Match types & events - PROPERTIES alerts emit: - ALERT_MATCH_CREATED: a new property qualified - ALERT_MATCH_MERGED: ONLY when a new advert is merged into an already-matched property (not on every check) - ADVERTS alerts emit (alerts only — no public adverts/search endpoint exists): - ALERT_MATCH_CREATED: new advert matched - ALERT_MATCH_ADVERT_EVENT: event on a previously matched advert. Event subtypes: PRICE (any change, even 1 EUR), REPUBLISHED (offline -> online), UNPUBLISHED (online -> offline). CHECK is never emitted. - NO BACKFILL on alert creation: events apply only to items processed AFTER alert creation. PROPERTIES side: combine `search` (one-shot retrospective) + `alert` (continuous). ADVERTS side: ask Fluximmo to replay historical backfill on your webhook by mail to contact@fluximmo.com. - DELETING + RECREATING an alert loses event history. Use `PATCH /alerts/{flxId}` to modify in place. ## 7. 8 validated archetypes (Phase 3 PASS, 2026-05-04) ### A1. PROPERTIES — Paris achat appartement standard combo POST /v2/protected/properties/search ```json {"size":25,"sortBy":"FIRST_SEEN_AT","orderBy":"DESC","search":{"filterProperty":{"location":[{"postalCode":"75001"}],"type":["CLASS_FLAT","CLASS_HOUSE","CLASS_PROGRAM"],"offer":[{"type":"OFFER_BUY"}],"price":{"initial":{"value":{"min":100000,"max":350000}}},"habitation":{"surface":{"total":{"min":30,"max":110}},"bedroomCount":{"min":1,"max":3}},"meta":{"isTotallyOffline":false}}}} ``` ### A2. PROPERTIES — Dpt 13 location maison >=80m2 >=3ch POST /v2/protected/properties/search ```json {"size":25,"sortBy":"FIRST_SEEN_AT","orderBy":"DESC","search":{"filterProperty":{"location":[{"department":"13"}],"type":["CLASS_HOUSE"],"offer":[{"type":"OFFER_RENT"}],"habitation":{"surface":{"total":{"min":80}},"bedroomCount":{"min":3}},"meta":{"isTotallyOffline":false}}}} ``` ### A3. PROPERTIES — geoDistance 20km Tour Eiffel POST /v2/protected/properties/search ```json {"size":25,"sortBy":"FIRST_SEEN_AT","orderBy":"DESC","search":{"filterProperty":{"location":[{"locationCoordinate":{"location":{"geoDistance":{"pin":{"lat":48.85837,"lon":2.294481},"distanceKm":20}}}}],"type":["CLASS_FLAT"],"offer":[{"type":"OFFER_BUY"}],"meta":{"isTotallyOffline":false}}}} ``` ### A4. PROPERTIES — geoBoundingBox Paris POST /v2/protected/properties/search ```json {"size":25,"sortBy":"FIRST_SEEN_AT","orderBy":"DESC","search":{"filterProperty":{"location":[{"locationCoordinate":{"location":{"geoBoundingBox":{"topLeft":{"lat":48.9021,"lon":2.2241},"bottomRight":{"lat":48.8156,"lon":2.4699}}}}}],"offer":[{"type":"OFFER_BUY"}],"meta":{"isTotallyOffline":false}}}} ``` ### A5. PROPERTIES — Multi-zones OR (Paris + petite couronne 75/92/93/94) POST /v2/protected/properties/search ```json {"size":25,"sortBy":"FIRST_SEEN_AT","orderBy":"DESC","search":{"filterProperty":{"location":[{"department":"75"},{"department":"92"},{"department":"93"},{"department":"94"}],"type":["CLASS_FLAT"],"offer":[{"type":"OFFER_BUY"}],"meta":{"isTotallyOffline":false}}}} ``` ### A6. ADVERTS alert — Dpt 75 achat appartement (CREATED only) PUT /v2/protected/adverts/search/alerts ```json {"search_query":{"filterAd":{"location":{"department":"75"},"type":["CLASS_FLAT"],"offer":[{"type":"OFFER_BUY"}],"isOnline":true}},"webhook_url":"https://votre-domaine.com/webhooks/fluximmo","match":["ALERT_MATCH_CREATED"],"search_name":"Achat T2-T3 Paris"} ``` ### A7. ADVERTS alert — With events (PRICE / REPUBLISHED / UNPUBLISHED) PUT /v2/protected/adverts/search/alerts ```json {"search_query":{"filterAd":{"location":{"department":"75"},"type":["CLASS_FLAT"],"offer":[{"type":"OFFER_BUY"}],"currentPrice":{"value":{"max":600000}},"isOnline":true}},"webhook_url":"https://votre-domaine.com/webhooks/fluximmo","match":["ALERT_MATCH_CREATED","ALERT_MATCH_ADVERT_EVENT"],"search_name":"Paris achat - veille prix"} ``` ### A8. PROPERTIES alert — Combo standard match=[CREATED] PUT /v2/protected/properties/search/alerts ```json {"search_query":{"filterProperty":{"location":[{"postalCode":"75001"}],"type":["CLASS_FLAT"],"offer":[{"type":"OFFER_BUY"}],"meta":{"isTotallyOffline":false}}},"webhook_url":"https://votre-domaine.com/webhooks/fluximmo-properties","match":["ALERT_MATCH_CREATED"],"search_name":"Properties Paris achat appartement"} ``` ## 8. Open data — DPE & DVF (4 endpoints) All POST, base https://api.fluximmo.io, auth header `x-api-key`. Independent from Property/Advert (no `search`/`filterProperty` wrapper). Two access patterns per dataset: - `*/search` = filtered paginated list, no scoring. `filters` keys are **snake_case**. Cursor pagination. - `*/match` = ranked candidates by similarity score, for reconciling a listing/asset to open data. `filters` keys are **snake_case**. `scoring_targets` influence ranking only — they never exclude candidates. DPE = Diagnostic de Performance Énergétique (ADEME). DVF = Demandes de Valeurs Foncières (recorded sales). ### 8.1 Shared filter value shapes ```ts type NumberRangeFilter = { min?: number, max?: number } // min=gte, max=lte type DateRangeFilter = { from?: string, to?: string } // ISO date/date-time, from=gte, to=lte type GeoDistanceFilter = { // all 3 required if used lat: number, lon: number, distance: string // "", unit: m|km|mi|ft|yd|nmi e.g. "10km" } ``` ### 8.2 POST /v2/protected/opendata/dpe/search ```ts type DpeSearchPayload = { filters?: DpeInheritFiltersDto, sortBy?: "numero_dpe" | "date_etablissement_dpe" | "type_batiment" | "surface_habitable_logement" | "etiquette_dpe" | "conso_5_usages_par_m2_ep" | "emission_ges_5_usages" | "emission_ges_5_usages_par_m2" | "zone_climatique", sortOrder?: "asc" | "desc", size?: number, // 1-100, default 20 cursor?: string } type DpeInheritFiltersDto = { numero_dpe?: string, flx_id?: NumberRangeFilter, date_etablissement_dpe?: DateRangeFilter, date_visite_diagnostiqueur?: DateRangeFilter, date_reception_dpe?: DateRangeFilter, date_derniere_modification_dpe?: DateRangeFilter, date_fin_validite_dpe?: DateRangeFilter, identifiant_ban?: string, numero_voie_ban?: string, code_insee_ban?: string, code_postal_ban?: string, code_departement_ban?: string, code_region_ban?: string, location?: GeoDistanceFilter, type_batiment?: ("appartement" | "maison" | "immeuble")[], periode_construction?: ("avant 1948" | "1948-1974" | "1975-1977" | "1978-1982" | "1983-1988" | "1989-2000" | "2001-2005" | "2006-2012" | "2013-2021" | "après 2021")[], surface_habitable_logement?: NumberRangeFilter, surface_habitable_immeuble?: NumberRangeFilter, surface_ventilee?: NumberRangeFilter, nombre_niveau_logement?: NumberRangeFilter, nombre_niveau_immeuble?: NumberRangeFilter, nombre_appartement?: NumberRangeFilter, etiquette_dpe?: ("A"|"B"|"C"|"D"|"E"|"F"|"G")[], // arrays — OR etiquette_ges?: ("A"|"B"|"C"|"D"|"E"|"F"|"G")[], conso_5_usages_ep?: NumberRangeFilter, conso_5_usages_ef?: NumberRangeFilter, conso_5_usages_par_m2_ep?: NumberRangeFilter, conso_5_usages_par_m2_ef?: NumberRangeFilter, emission_ges_5_usages?: NumberRangeFilter, emission_ges_5_usages_par_m2?: NumberRangeFilter, qualite_isolation_enveloppe?: ("insuffisante"|"moyenne"|"bonne"|"très bonne")[], qualite_isolation_menuiseries?: ("insuffisante"|"moyenne"|"bonne"|"très bonne")[], zone_climatique?: ("H1a"|"H1b"|"H1c"|"H2b"|"H2c"|"H2d"|"H3")[] } ``` Response: `{ data: object[], total: number, size: number, hasMore: boolean, cursor?: string }` ### 8.3 POST /v2/protected/opendata/dpe/match ```ts type DpeMatchPayload = { filters?: DpeFiltersDto, size?: number, // 1-20, default 5 scoring_targets?: MatchDpeScoringTargetsDto } type DpeFiltersDto = { code_departement_ban?: string, code_insee_ban?: string, code_postal_ban?: string, numero_dpe?: string, type_batiment?: "appartement" | "maison" | "immeuble", // single string, NOT array surface_habitable_logement?: NumberRangeFilter, // surface habitable logement m2 date_etablissement_dpe?: DateRangeFilter, location?: GeoDistanceFilter } type MatchDpeScoringTargetsDto = { conso_5_usages_par_m2_ep?: number, // kWh/m2/an (gauss decay) emission_ges_5_usages_par_m2?: number, // kgCO2/m2/an (gauss decay) etiquette_dpe?: "A"|"B"|"C"|"D"|"E"|"F"|"G", etiquette_ges?: "A"|"B"|"C"|"D"|"E"|"F"|"G", nombre_niveau_immeuble?: number, // 0-200 numero_etage_appartement?: number, // -10..200 (negative = basement) periode_construction?: string, // same enum as periode_construction nombre_niveau_logement?: number // 0-200 } ``` Response: `{ data: [{ score: number /*0-100*/, confidence: "HIGH"|"MEDIUM"|"LOW"|"REJECT", dpe: {} }], meta: { ambiguous: boolean /*top-2 gap <=5*/, total: number } }` ### 8.4 POST /v2/protected/opendata/dvf/search ```ts type DvfSearchPayload = { filters?: DvfInheritFiltersDto, sortBy?: "id" | "transaction_date" | "price_eur" | "price_per_m2_living_eur" | "price_per_m2_land_eur" | "surface_m2", sortOrder?: "asc" | "desc", size?: number, // 1-100, default 20 cursor?: string } type DvfInheritFiltersDto = { id?: string, transaction_date?: DateRangeFilter, transaction_type?: ("Vente" | "Vente en l'etat futur d'achevement" | "Vente terrain a batir" | "Echange")[], // exact strings, unaccented price_eur?: NumberRangeFilter, price_per_m2_living_eur?: NumberRangeFilter, price_per_m2_land_eur?: NumberRangeFilter, location?: GeoDistanceFilter, departement_code?: string, insee_code?: string, postal_code?: string, iris_code?: string, iris_type?: ("H"|"A"|"D"|"Z")[], property_type?: ("Appartement"|"Maison"|"Autre"|"Terrain" |"Tertiaire"|"Dépendance"|"Volume")[], // accented, capitalized surface_m2?: NumberRangeFilter, main_rooms?: NumberRangeFilter, land_surface_m2?: NumberRangeFilter, is_new_build?: boolean, is_open_market?: boolean, carrez_surface_m2?: NumberRangeFilter, soil_surface_m2?: NumberRangeFilter, garden_surface_m2?: NumberRangeFilter } ``` Response: `{ data: object[], total: number, size: number, hasMore: boolean, cursor?: string }` ### 8.5 POST /v2/protected/opendata/dvf/match ```ts type DvfMatchPayload = { filters?: DvfFiltersDto, size?: number, // 1-20, default 5 scoring_targets?: MatchDvfScoringTargetsDto } type DvfFiltersDto = { departement_code?: string, insee_code?: string, postal_code?: string, id?: string, property_type?: "Appartement"|"Maison"|"Autre"|"Terrain" |"Tertiaire"|"Dépendance"|"Volume", // single string, NOT array surface_m2?: NumberRangeFilter, // surface habitable m2 transaction_date?: DateRangeFilter, location?: GeoDistanceFilter } type MatchDvfScoringTargetsDto = { price_eur?: number, // EUR (gauss decay) main_rooms?: number, // weighted filter is_new_build?: boolean, transaction_type?: "Vente" | "Vente en l'etat futur d'achevement" | "Vente terrain a batir" | "Echange" } ``` Response: `{ data: [{ score: number /*0-100*/, confidence: "HIGH"|"MEDIUM"|"LOW"|"REJECT", dvf: {} }], meta: { ambiguous: boolean, total: number } }` ### 8.6 DPE/DVF examples #### O1. DPE search — appartements F/G dpt 75, surface 30-70 m2 POST /v2/protected/opendata/dpe/search ```json {"filters":{"code_departement_ban":"75","type_batiment":["appartement"],"etiquette_dpe":["F","G"],"surface_habitable_logement":{"min":30,"max":70}},"sortBy":"date_etablissement_dpe","sortOrder":"desc","size":20} ``` #### O2. DPE search — geo 10km + DPE établi depuis 2024 POST /v2/protected/opendata/dpe/search ```json {"filters":{"location":{"lat":48.8566,"lon":2.3522,"distance":"10km"},"date_etablissement_dpe":{"from":"2024-01-01T00:00:00.000Z"}},"size":50} ``` #### O3. DPE match — reconcile a listing to its DPE POST /v2/protected/opendata/dpe/match ```json {"filters":{"code_postal_ban":"75013","type_batiment":"appartement","surface_habitable_logement":{"min":52,"max":62},"location":{"lat":48.8323,"lon":2.3556,"distance":"300m"}},"size":5,"scoring_targets":{"etiquette_dpe":"D","conso_5_usages_par_m2_ep":210,"numero_etage_appartement":3}} ``` #### O4. DVF search — ventes appartements dpt 13, prix 150k-300k depuis 2023 POST /v2/protected/opendata/dvf/search ```json {"filters":{"departement_code":"13","property_type":["Appartement"],"transaction_type":["Vente"],"price_eur":{"min":150000,"max":300000},"transaction_date":{"from":"2023-01-01"}},"sortBy":"transaction_date","sortOrder":"desc","size":20} ``` #### O5. DVF match — comparable sales for a 3-room flat POST /v2/protected/opendata/dvf/match ```json {"filters":{"insee_code":"83137","property_type":"Appartement","surface_m2":{"min":60,"max":75},"transaction_date":{"from":"2022-01-01"}},"size":5,"scoring_targets":{"price_eur":285000,"main_rooms":3,"transaction_type":"Vente"}} ``` ## 9. Anti-patterns (read before generating) - DO NOT confuse Property and Advert. Property = unique deduplicated asset (webhook = IDs only, refetch needed). Advert = one listing on one portal (webhook = full payload + events). - DO NOT use `city` in FilterLocation: it is silently IGNORED. Use postalCode / inseeCode / department / irisCode. - DO NOT use geoShape / POLYGON: not currently exposed. Use geoBoundingBox or geoDistance, optionally combined with multi-zone OR (array of FilterLocation entries). - DO NOT mix GeoJSON `[lon, lat]` with API `{lat, lon}`: the API everywhere uses `{lat, lon}` objects, never arrays. - DO NOT swap geoBoundingBox corners: topLeft.lat must be > bottomRight.lat AND topLeft.lon must be < bottomRight.lon. - DO NOT confuse FilterPropertyPrice (`price.initial.value.{min,max}` / `price.latest.value.{min,max}`) with FilterAdvertPrice (`currentPrice.value.{min,max}` — different field name AND nested under `value`). They are not interchangeable. - DO NOT rely on backfill at alert creation: alerts only match items processed AFTER creation. Combine search + alert. - DO NOT delete + recreate an alert to modify it (loses event history). Use `PATCH /alerts/{flxId}`. - DO NOT use Properties alerts to track per-listing price changes: PRICE / REPUBLISHED / UNPUBLISHED events only exist on Adverts alerts (`ALERT_MATCH_ADVERT_EVENT`). - DO NOT skip `meta.isTotallyOffline`: production best practice is `meta.isTotallyOffline=false` to exclude completely offline items. - DO NOT use `ALERT_MATCH_MERGED` casually on Properties alerts: it multiplies webhook volume. Default to `["ALERT_MATCH_CREATED"]`. - Open-data filters are snake_case on BOTH `*/search` AND `*/match` (`code_departement_ban`, `transaction_date`, `surface_m2`…). Only the pagination params (`sortBy`, `sortOrder`, `cursor`, `size`) stay camelCase. - DO NOT wrap DPE/DVF payloads in `search`/`filterProperty`: the body is flat (`filters`, `size`, `sortBy`/`scoring_targets`). - DO NOT pass arrays to `type_batiment` / `property_type` on `*/match`: those are single strings (arrays only exist on `*/search`: `type_batiment`, `property_type`). - DO NOT use `scoring_targets` to filter: they only re-rank candidates, never exclude. Use `filters` to restrict the candidate set. - DO NOT accent or recapitalize `transaction_type` values: use exact strings like `Vente en l'etat futur d'achevement` (no accents). `property_type` values ARE accented/capitalized (`Appartement`, `Dépendance`). - DO NOT omit `distance` on `location`: the open-data geo filter requires `{lat, lon, distance}` all three, with a unit suffix (e.g. `"10km"`). ## 10. Links - Full docs: https://doc.fluximmo.io - Sample no-auth Property: https://api.fluximmo.io/v2/sample/properties - Sample no-auth Advert: https://api.fluximmo.io/v2/sample/adverts - Sample webhook Property: https://api.fluximmo.io/v2/sample/webhook/properties - Sample webhook Advert: https://api.fluximmo.io/v2/sample/webhook/adverts - Concepts: /concepts/property-vs-advert , /concepts/match-types-cycle-alerte , /concepts/recherche-geographique , /concepts/webhooks - Free trial API key: https://my.fluximmo.io (1 week, no commercial calls) Last updated: 2026-08-09 Token count target: 2000-4000 tokens (~5-10K chars) ```` # Recevoir une alerte ADVERT par département Source: https://doc.fluximmo.io/playbooks/alerte-advert-departement Webhook temps réel sur les nouvelles annonces d'achat appartement dans un département (chasseur immo). ## Goal Recevoir, en quasi temps réel, **chaque nouvelle annonce de vente d'appartement** publiée sur n'importe quel portail dans un département donné — pour vous permettre d'être le premier à contacter le vendeur. ## Scénario Vous êtes chasseur immobilier (ou app B2C de chasse). Vous voulez surveiller toutes les nouvelles publications d'appartements à l'achat dans le département **75** (Paris). Volume attendu : \~200-500 nouvelles annonces / jour selon la période. Vous devez aussi détecter, plus tard, les **baisses de prix** et les **mises hors ligne** sur ces mêmes annonces. **Quand utiliser ADVERTS plutôt que PROPERTIES ?** Les alertes ADVERT sont nécessaires si vous voulez : * **post-traiter les annonces** / construire un pipeline de traitement interne (enrichissement, scoring, dédup custom) * **exposer vos propres APIs** à vos utilisateurs finaux * **répliquer la BDD Fluximmo** side-by-side * **recevoir 100 % des events de dépublication** (créations, mises à jour de prix, mises hors ligne) Pour un cas d'usage purement "trouver les biens", préférez PROPERTY (cf. [property-vs-advert](/concepts/property-vs-advert)). ## Étapes Le handler HTTP doit valider le header `x-webhook-key`, **enqueuer** le payload dans une file durable (SQS, RabbitMQ, Redis Streams), puis répondre **200**. Aucun traitement métier synchrone — sinon Fluximmo timeout et retry, vous saturerez votre app. ```text theme={null} # Pseudocode handler on POST /webhooks/fluximmo: if header.x-webhook-key != EXPECTED_KEY: return 401 enqueue(request.body) return 200 ``` Smoke test depuis votre poste : ```bash theme={null} curl -i -X POST https://votre-domaine.com/webhooks/fluximmo \ -H "Content-Type: application/json" \ -H "x-webhook-key: $FLUXIMMO_WEBHOOK_KEY" \ -d '{"ping": true}' ``` Détails — politique de retry, SLO réponse, sécurité — sur [/concepts/webhooks](/concepts/webhooks). Filtre `isOnline: true` recommandé en production : vous ne recevez que des adverts actuellement publiées sur leur portail. ```bash theme={null} curl -X PUT "https://api.fluximmo.io/v2/protected/adverts/search/alerts" \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "search_query": { "filterAd": { "location": { "department": "75" }, "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "isOnline": true } }, "webhook_url": "https://votre-domaine.com/webhooks/fluximmo", "match": ["ALERT_MATCH_CREATED"], "search_name": "Achat appart Paris (75)" }' ``` Conservez l'`flxId` retourné : c'est la clé pour **modifier** l'alerte plus tard (étape 4) sans perdre l'historique. Dès qu'une nouvelle advert qualifiante est ingérée (\~1 min après publication portail), Fluximmo POSTe le payload **complet de l'advert** sur `webhook_url`. Pas de refetch nécessaire, contrairement aux alertes Properties. Structure type (shape canonique — cf. [exemple webhook adverts](/api-v2-reference/sample-adverts/sample-advert-webhook-body)) : ```json theme={null} { "data": { "created": [ { "alert_id": "alrt_...", "adverts": [ { "flxId": "adv_...", "propertyFlxId": "adv_...", "source": "", "currentPrice": { "value": 285000 }, "habitation": { "surface": { "total": 42 }, "roomCount": 2 }, "location": { "postalCode": "75011" }, "isOnline": true } ] } ], "updated": [] } } ``` Boucle d'extraction : `for entry in body.data.created: for advert in entry.adverts: ...`. Mappez `advert.flxId` sur votre BDD comme **clé d'idempotence** — vous recevrez parfois le même match deux fois (retry). Une fois la première vague de matches digérée, étendez le tableau `match` avec `ALERT_MATCH_ADVERT_EVENT`. **À faire** : `PATCH /v2/protected/adverts/search/alerts/{alertId}` (modify in place). **À NE PAS faire** : `DELETE` puis `PUT` d'une nouvelle alerte — vous **perdez** l'historique des adverts déjà matchées, donc plus aucun event sur ces annonces. ```bash theme={null} curl -X PATCH \ "https://api.fluximmo.io/v2/protected/adverts/search/alerts/$ALERT_FLX_ID" \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "match": ["ALERT_MATCH_CREATED", "ALERT_MATCH_ADVERT_EVENT"] }' ``` Les events arrivent désormais dans la branche `data.updated[]` du même webhook. Le payload `updated` ne porte que `flxId`, `currentPrice`, `isOnline` (DTO réduit) — vous **dérivez** côté client le type d'event en comparant avec votre état stocké : `currentPrice.value` change → PRICE, `isOnline` flip → REPUBLISHED ou UNPUBLISHED. Cf. [Match types & cycle alerte](/concepts/match-types-cycle-alerte). Une alerte ne match **que** ce qui est ingéré **après** sa création (`createdAt`). Pour récupérer les annonces déjà au catalogue, écrivez à [contact@fluximmo.com](mailto:contact@fluximmo.com) en précisant : * Votre `clientId` * Le `flxId` de l'alerte créée à l'étape 2 * La période de backfill souhaitée (ex. derniers 30 jours, derniers 6 mois) * Le volume estimé que votre webhook peut absorber Le backfill est rejoué sur votre webhook avec le même format de payload que les matches normaux — votre handler le traite sans changement de code, idempotence via `advert.flxId`. ## Architecture / flow ```mermaid theme={null} flowchart LR U["Vous
(PUT alerte)"] --> FX["Fluximmo
(crawler + matcher)"] FX -- "advert qualifiée" --> WH["POST webhook
x-webhook-key"] WH --> H["Handler HTTP
(stateless, < 1 s)"] H -->|"valide key"| Q["Queue durable
(SQS / RabbitMQ)"] H -->|"200 OK"| FX Q --> WK["Worker async
(scalable)"] WK --> DB["Votre BDD
(idempotent sur flxId)"] WK --> NOTIF["Notif user
email / push"] ``` ## Pièges fréquents * **Traitement synchrone dans le handler** → timeout, retry, cascade d'échecs sous charge. Ackez en \< 1 s, traitez en worker async. * **Idempotence oubliée** : un même webhook peut être livré 2 fois (retry après timeout réseau). Dédupliquez avec une clé stable comme `(advert.flxId, branch, alert_id)` où `branch` ∈ `{ "created", "updated" }`. * **`DELETE` + recréation** au lieu de `PATCH` → perte de l'historique des adverts matchées, donc **plus aucun event** sur ces annonces. * **Pas de backfill côté serveur sans demande** : l'alerte ne voit que le futur. Pour le passé, demande explicite par mail (cf. étape 5). * **Volume sous-estimé** : achat appartement dpt 75 ≈ 200-500 nouvelles annonces/jour. Provisionnez la queue et le worker en conséquence (peak intra-day en matinée). ## Pour aller plus loin * [Match types & cycle de vie d'une alerte](/concepts/match-types-cycle-alerte) * [Webhooks — architecture, retry, sécurité](/concepts/webhooks) * [Créer une alerte adverts (référence API)](/api-v2-reference/adverts-alerts/create-a-new-advert-alert-for-this-search) * [Playbook — Architecture webhook haute volumétrie](/playbooks/webhook-volume-architecture) * [Playbook — Suivi des changements de prix](/playbooks/track-price-changes) # Estimer un bien immobilier via l'API Fluximmo Source: https://doc.fluximmo.io/playbooks/estimer-un-bien Estimez le prix d'un bien immobilier français via l'API : localisation et caractéristiques en entrée, prix et fourchette (priceRange) en sortie. ## Goal Obtenir une **estimation de prix** (vente ou location) pour un bien à partir de ses caractéristiques (localisation, surface, nombre de pièces, type), avec un intervalle indicatif — sans entraîner son propre modèle. ## Scénario Un produit AVM (fintech, proptech, courtier, agence) doit afficher une estimation au moment où l'utilisateur saisit son bien. Le besoin : un prix central + un intervalle, et la possibilité de scorer un portefeuille en arrière-plan. **Endpoint synchrone et latent.** Compter **quelques secondes** par requête — pas un appel adapté à un thread UI bloquant. Prévoir un état de chargement explicite côté UI, et un job en arrière-plan pour les batches portfolio. ## Étapes Le minimum requis : **localisation géocodée** (`location.locationCoordinate.location` au format `[lng, lat]`), **caractéristiques du bien** (`habitation.surface.total`, `habitation.roomCount`, `habitation.bedroomCount` — tous trois requis ; optionnel `land.surface` pour les maisons), **type** (`CLASS_FLAT` | `CLASS_HOUSE`) et **offer** (objet `{ type: "OFFER_BUY" }` — seul `OFFER_BUY` est supporté sur le lite). ```json theme={null} { "location": { "locationCoordinate": { "location": [2.3376, 48.8606] } }, "habitation": { "surface": { "total": 60 }, "roomCount": 3, "bedroomCount": 2 }, "type": "CLASS_FLAT", "offer": { "type": "OFFER_BUY" } } ``` **Champs optionnels qui affinent l'estimation** (tous documentés au swagger) : * `land.surface` — pour les maisons * `habitation.propertyCondition` : `constructionYear`, `generalConditions[]` * `habitation.climate` : `epcEnergy` (DPE), `epcClimate` (GES) * `habitation.characteristics` : `hasBalcony`, `hasCellar`, `hasGarage`, `hasLift`, `hasParking`, `hasPool`, `hasTerrace` * `habitation.features` : `furniture`, `propertyFloor`, `propertyTotalFloor` Plus le payload est riche, plus l'estimation est calibrée — surtout sur des biens atypiques (DPE F/G, dernier étage sans ascenseur, etc.). Voir [`POST /estimate/property/lite`](/api-ai-reference/estimations/lite-property-estimation) pour le schéma OpenAPI complet. ```bash theme={null} curl -X POST https://api.fluximmo.io/v2/protected/estimate/property/lite \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "location": { "locationCoordinate": { "location": [2.3376, 48.8606] } }, "habitation": { "surface": { "total": 60 }, "roomCount": 3, "bedroomCount": 2 }, "type": "CLASS_FLAT", "offer": { "type": "OFFER_BUY" } }' ``` La réponse, sous la clé `data`, contient (cf. `EstimationResponseDto` du swagger) : * `data.price` — estimation centrale (€) — **requis** * `data.priceRange` — intervalle `{ lower, upper }` — **requis** * `data.metadata` — objet libre, optionnel, sans schéma défini par l'API ```text theme={null} # Pseudocode parse(payload): central = payload.data.price range_lower = payload.data.priceRange.lower range_upper = payload.data.priceRange.upper spread_pct = (range_upper - range_lower) / central * 100 if central > 0 return { central, range_lower, range_upper, spread_pct, metadata = payload.data.metadata } ``` **Pas de champ de fiabilité officiel.** Le swagger ne définit ni `confidence`, ni `spread`, ni `sample_size`. `spread_pct` est une **heuristique calculée côté client** : plus l'intervalle `[lower, upper]` est large par rapport à `central`, plus l'incertitude est forte sur cette zone / ce type de bien. **Afficher l'intervalle complet à l'utilisateur** plutôt que le seul prix central — c'est l'information honnête que l'API vous donne. Pour scorer un portefeuille (N biens), lancer un **job en arrière-plan**, jamais une boucle dans une requête HTTP entrante. L'endpoint étant synchrone et latent, paralléliser agressivement saturerait le service — et ne ferait pas gagner de temps utilisateur, puisque le batch tourne déjà hors UI. * **Séquentiel par défaut.** Le débit utile est dicté par la latence backend, pas par votre concurrence client. Si vous tenez à paralléliser, restez **bas** (1-2 workers) et validez avec `contact@fluximmo.com` avant de pousser plus haut. * **Cache.** C'est le vrai levier de débit. Clé = hash des champs métier (coordonnées arrondies + `surface.total` + `roomCount` + `bedroomCount` + `type` + `offer.type`, plus les optionnels que vous utilisez réellement). Les estimations sont stables sur quelques heures. * **Quota.** Chaque appel est crédité. Avant un batch, vérifier le solde via [`GET /v2/protected/consumption`](/api-v2-reference/consumption/get-your-credit-consumption). ```text theme={null} # Pseudocode batch (worker en arrière-plan) foreach bien in portfolio: lng, lat = bien.location.locationCoordinate.location key = hash(round(lng,3), round(lat,3), bien.habitation.surface.total, bien.habitation.roomCount, bien.habitation.bedroomCount, bien.type, bien.offer.type) if cache[key] not stale: result = cache[key] else: result = POST /v2/protected/estimate/property/lite (bien) # appel séquentiel cache[key] = result persist(bien.id, result) ``` ## Architecture / flow ```mermaid theme={null} flowchart LR User[App / Worker] Lite["POST /estimate/property/lite"] AI[Modèle AVM Fluximmo] Output[Résultat utilisateur
price + priceRange] User -->|"location + habitation + type + offer"| Lite Lite --> AI AI -->|"price, priceRange, metadata"| User User --> Output ``` ## Pièges fréquents **Endpoint synchrone latent.** Compter quelques secondes par appel. Ne **jamais** appeler depuis un thread UI bloquant ni boucler en synchrone dans une requête HTTP entrante : prévoir un job en arrière-plan + état de chargement explicite côté UI. **Géocodage incomplet.** Sans coordonnées `lat`/`lon` correctement résolues dans `location.locationCoordinate.location`, l'estimation tombe sur des moyennes nationales — résultat peu fiable. Toujours géocoder l'adresse en amont via [`/geocoding/search`](/api-geocoding-reference/search/recherche-par-géocodage-direct). **Zones non couvertes.** DOM-TOM, étranger, communes \< 500 habitants : la base d'apprentissage est plus mince. L'API n'expose **pas** de signal officiel de fiabilité dans la réponse — surveiller la largeur de `priceRange` (`upper - lower`) par rapport à `price`, et afficher un disclaimer côté UI si l'intervalle est large. **Surcoût quota.** Chaque appel est crédité. Pour un batch portfolio, vérifier `GET /v2/protected/consumption` avant et mettre en cache (clé = hash des champs métier). Sinon vous brûlez votre solde sur des requêtes redondantes. ## Pour aller plus loin * [Estimation lite — référence API](/api-ai-reference/estimations/lite-property-estimation) * [Géocoder pour search](/playbooks/geocoder-pour-search) — pipeline géocodage en amont * [Consumption](/api-v2-reference/consumption/get-your-credit-consumption) — surveiller le quota Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Géocoder une adresse pour filtrer une recherche Source: https://doc.fluximmo.io/playbooks/geocoder-pour-search Pipeline BAN/BANO Gouv (search/reverse) → injecter (lat, lon) ou postalCode dans un filtre Fluximmo. ## Goal Partir d'une **adresse FR en texte libre** (saisie utilisateur, ligne CRM, point d'intérêt) et arriver à une **recherche Fluximmo géolocalisée** sans accroc : géocoder, choisir le bon mode de filtre, et appeler `/properties/search`. ## Scénario Vous construisez une app où l'utilisateur tape une adresse ("12 rue de Rivoli, Paris" ou "Tour Eiffel"), et vous voulez lister les annonces Fluximmo dans un rayon autour. Vous n'avez pas les coordonnées GPS, juste la string. Ce playbook vous donne le pipeline complet. ## Étapes Le service de géocodage exposé par Fluximmo est le service public **BAN/BANO du gouvernement français** (`api-adresse.data.gouv.fr`), opéré et proxifié par Fluximmo. **Pourquoi passer par nous plutôt que d'appeler le service Gouv directement ?** * Pas de rate-limit côté Gouv à gérer vous-même (Fluximmo absorbe). * Une seule clé d'API pour toute la stack (search Fluximmo + géocodage). * Endpoints harmonisés et monitoring unifié. **Transparence assumée** : ce n'est pas une brique propriétaire. C'est de la donnée publique fiable, simplement intégrée à votre écosystème API Fluximmo. `GET /geocoding/search?q=` renvoie `lat`, `lon`, `postalCode`, `inseeCode`, et un `score` de confiance. ```bash theme={null} curl -G "https://geo.fluximmo.io/search" \ --data-urlencode "q=12 rue de Rivoli, Paris" \ --data-urlencode "limit=1" \ -H "x-api-key: $FLUXIMMO_API_KEY" ``` ```text theme={null} # Pseudocode parse top = response.features[0] lat = top.geometry.coordinates[1] lon = top.geometry.coordinates[0] postal_code = top.properties.postcode insee_code = top.properties.citycode score = top.properties.score # 0..1 ``` Toujours encoder l'adresse (URL encoding) — utilisez `--data-urlencode` côté curl, ou un équivalent natif (`URLSearchParams`, `params=...`) côté code applicatif. Selon la précision recherchée, trois options se présentent. **Option A — Par `postalCode`** (large, agrège toutes les communes du CP) Idéal pour des analyses macro ou marchés où le CP est l'unité naturelle. ```json theme={null} { "filterProperty": { "location": [{ "postalCode": "75001" }] } } ``` **Option B — Par `inseeCode`** (granularité commune) Légèrement plus fin que le CP en zone rurale (un CP peut couvrir plusieurs communes). ```json theme={null} { "filterProperty": { "location": [{ "inseeCode": "75101" }] } } ``` **Option C — Par `geoDistance`** (rayon précis autour du pin) C'est la vraie option "X km autour de cette adresse". ```json theme={null} { "filterProperty": { "location": [{ "locationCoordinate": { "location": { "geoDistance": { "pin": { "lat": 48.858, "lon": 2.295 }, "distanceKm": 5 } } } }] } } ``` Voir [Recherche géographique](/concepts/recherche-geographique) pour le détail des modes (`postalCode`, `inseeCode`, `department`, `geoBoundingBox`, `geoDistance`, multi-zones OR). ```bash theme={null} curl -X POST "https://api.fluximmo.io/v2/protected/properties/search" \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "size": 50, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [{ "locationCoordinate": { "location": { "geoDistance": { "pin": { "lat": 48.858, "lon": 2.295 }, "distanceKm": 5 } } } }], "offer": [{ "type": "OFFER_RENT" }], "type": ["CLASS_FLAT"], "meta": { "isTotallyOffline": false, "firstSeenAt": { "min": "2025-01-01T00:00:00.000Z" } } } } }' ``` Cas d'usage : étiqueter un point GPS issu d'un résultat Fluximmo (par exemple une app mobile qui affiche l'adresse sur une fiche annonce). `GET /geocoding/reverse?lat=...&lon=...` ```bash theme={null} curl -G "https://geo.fluximmo.io/reverse" \ --data-urlencode "lat=48.858" \ --data-urlencode "lon=2.295" \ -H "x-api-key: $FLUXIMMO_API_KEY" ``` ```text theme={null} # Pseudocode addr = response.features[0].properties.label ``` Pour géocoder N adresses en lot. * **Sync** : `POST` d'un fichier CSV en multipart, réponse synchrone (limite de taille). * **Async** : pipeline batch async disponible sur demande — écrivez à [contact@fluximmo.com](mailto:contact@fluximmo.com) pour activation et SLA. Choisir async dès que le volume dépasse quelques milliers de lignes ou que la latence sync devient pénalisante. ## Architecture / flow ```mermaid theme={null} flowchart LR Adresse["Adresse string
'1 rue de Rivoli, Paris'"] --> GeoSearch["GET /geocoding/search"] GeoSearch --> Coords["lat, lon, postalCode,
inseeCode, score"] Coords --> Choose{"Précision
voulue ?"} Choose -- "Large (CP entier)" --> ByCP["postalCode"] Choose -- "Commune" --> ByInsee["inseeCode"] Choose -- "Rayon précis" --> ByDist["geoDistance
{pin, distanceKm}"] ByCP --> FluxSearch["POST /v2/protected/
properties/search"] ByInsee --> FluxSearch ByDist --> FluxSearch FluxSearch --> Results["Annonces filtrées"] ``` ## Pièges fréquents **Adresse approximative → plusieurs candidats.** Le géocodage renvoie une liste triée par `score` (0 à 1). Toujours prendre le premier candidat (`features[0]`), et idéalement filtrer si `score < 0.5` (qualité douteuse). **Outre-mer / étranger.** BAN/BANO couvre la métropole et certains DOM-TOM. Pour des adresses étrangères, le service ne renverra rien d'utile — vérifier la couverture avant. **Encoder l'adresse.** Les caractères spéciaux (`é`, `'`, espaces) cassent les URL non-encodées. Utiliser `--data-urlencode` (curl) ou un équivalent natif côté code applicatif. **`distanceKm: 0`.** Un rayon nul n'a pas de sens et est rejeté. Mettre au minimum `0.5`. **Code postal ≠ commune.** En zone rurale, un même CP peut couvrir 5+ communes. Si vous avez besoin d'une granularité commune, utiliser `inseeCode` plutôt que `postalCode`. ## Pour aller plus loin * [Géocodage direct (recherche)](/api-geocoding-reference/search/recherche-par-géocodage-direct) * [Géocodage inverse](/api-geocoding-reference/reverse/recherche-par-géocodage-inverse) * [Concept — Recherche géographique](/concepts/recherche-geographique) * [Playbook — Recherche géographique avancée](/playbooks/recherche-geographique-avancee) * Pipeline batch async géocodage — sur demande (`contact@fluximmo.com`) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Recherche géographique avancée Source: https://doc.fluximmo.io/playbooks/recherche-geographique-avancee geoBoundingBox pour cartes interactives, geoDistance pour rayons, multi-zones OR pour multi-territoires. ## Goal Exploiter les deux modes géométriques de l'API Fluximmo (`geoBoundingBox`, `geoDistance`) ainsi que la combinaison multi-zones (logique `OR`) pour couvrir trois cas réels : carte interactive avec pan/zoom, rayon autour d'un point géocodé, et chasse multi-territoires. ## Scénario Vous construisez une expérience de recherche géo plus riche qu'un simple filtre `postalCode` ou `department`. Selon le contexte produit, vous devez : * répondre dynamiquement à un viewport carte (Cas A) ; * limiter la recherche à un rayon métrique autour d'un POI géocodé (Cas B) ; * agréger plusieurs territoires administratifs et géométriques en une seule requête (Cas C). Ce playbook donne pour chacun la requête type et les contraintes à respecter. Tous les payloads ci-dessous se passent dans le wrapper standard `{ "size": N, "search": { "filterProperty": { "location": [...] } } }` du DTO `/v2/protected/properties/search`. Le même schéma de filtres est réutilisé dans le payload des alertes ADVERTS. Voir [Recherche géographique](/concepts/recherche-geographique). ## Étapes **Objectif** : à chaque pan/zoom utilisateur, requêter Fluximmo avec la nouvelle bounding box du viewport. **Contraintes critiques** : * `topLeft.lat` doit être **strictement supérieure** à `bottomRight.lat` * `topLeft.lon` doit être **strictement inférieure** à `bottomRight.lon` * une bbox inversée renvoie 0 résultat (pas d'erreur HTTP) **Optimisations UI** : * debounce de 300–500 ms côté front pour éviter de spammer pendant le pan * adapter `size` au niveau de zoom (zoom large → 50, zoom rue → 200) * annuler la requête en vol si une nouvelle est déclenchée ```bash theme={null} curl -X POST 'https://api.fluximmo.io/v2/protected/properties/search' \ -H 'x-api-key: $FLUXIMMO_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "size": 50, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [ { "locationCoordinate": { "location": { "geoBoundingBox": { "topLeft": { "lat": 48.9021, "lon": 2.2241 }, "bottomRight": { "lat": 48.8156, "lon": 2.4699 } } } } } ], "meta": { "isTotallyOffline": false } } } }' ``` ```text theme={null} # Pseudocode garde-fous search_in_viewport(top_left, bottom_right, size = 50): assert top_left.lat > bottom_right.lat # sinon 0 résultat assert top_left.lon < bottom_right.lon return POST /properties/search { size, search: { filterProperty: { location: [...] } } } ``` **Objectif** : un investisseur cible "20 km autour de la gare TGV de Bordeaux Saint-Jean". **Workflow** : 1. **Géocoder** l'adresse via `/geocoding/search` (BAN/BANO) — voir [Géocoder pour search](/playbooks/geocoder-pour-search). 2. **Injecter** les coordonnées dans un filtre `geoDistance` (`pin` + `distanceKm`). ```bash theme={null} curl -X POST 'https://api.fluximmo.io/v2/protected/properties/search' \ -H 'x-api-key: $FLUXIMMO_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "size": 100, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [ { "locationCoordinate": { "location": { "geoDistance": { "pin": { "lat": 44.8262, "lon": -0.5564 }, "distanceKm": 20 } } } } ], "meta": { "isTotallyOffline": false } } } }' ``` **Objectif** : une agence opère sur Paris + petite couronne (75, 92, 93, 94). Plutôt que 4 requêtes séquentielles, on combine les 4 territoires dans un seul tableau `location`. **Sémantique** : tous les éléments du tableau `location` sont combinés en `OR` côté moteur. Vous pouvez **mixer librement** les modes : un `department`, un `geoDistance`, un `geoBoundingBox` dans le même tableau. **Avantage** : 1 requête + 1 facture quota au lieu de N. Pagination cohérente sur l'union. ```bash theme={null} curl -X POST 'https://api.fluximmo.io/v2/protected/properties/search' \ -H 'x-api-key: $FLUXIMMO_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "size": 100, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [ { "department": "75" }, { "department": "92" }, { "department": "93" }, { "department": "94" } ], "meta": { "isTotallyOffline": false } } } }' ``` Mix possible : `[{ "department": "69" }, { "locationCoordinate": { "location": { "geoDistance": { "pin": { "lat": 45.7640, "lon": 4.8357 }, "distanceKm": 5 } } } }, { "postalCode": "69100" }]` (Lyon dpt + 5 km autour du centre + Villeurbanne). ## Architecture / flow Arbre de décision pour choisir le bon mode géo selon le besoin produit. ```mermaid theme={null} flowchart TD Start[Besoin geo] --> Q1{Forme
administrative ?} Q1 -- Oui --> Admin[postalCode
inseeCode
department] Q1 -- Non --> Q2{Carte
interactive ?} Q2 -- Oui --> BBox[geoBoundingBox
Cas A] Q2 -- Non --> Q3{Rayon autour
d un point ?} Q3 -- Oui --> Dist[geoDistance
Cas B] Q3 -- Non --> Q4{Plusieurs
territoires ?} Q4 -- Oui --> Multi[Tableau location
OR - Cas C] Q4 -- Non --> Admin Multi -.mixe.-> BBox Multi -.mixe.-> Dist Multi -.mixe.-> Admin ``` ## Pièges fréquents **Bounding box inversée** : si `topLeft.lat <= bottomRight.lat` ou `topLeft.lon >= bottomRight.lon`, l'API renvoie `0` résultat **sans erreur**. Validez toujours côté client avant l'appel. **`distanceKm <= 0`** : un rayon nul ou négatif retourne une erreur de validation. Définissez un minimum applicatif (ex. 0.5 km). **Champ `city`** : le filtre `city` (texte libre) n'est plus supporté en V2. Géocodez l'adresse puis utilisez `geoDistance`, ou passez par `postalCode` / `inseeCode`. Voir [Filtres communs](/concepts/filtres-communs). **Wrapper DTO oublié** : tous les filtres `location` doivent être imbriqués dans `{ "search": { "filterProperty": { "location": [...] } } }`. Un payload plat est rejeté en 400. ## Pour aller plus loin * [Concepts — Recherche géographique](/concepts/recherche-geographique) * [Concepts — Filtres communs](/concepts/filtres-communs) * [API — POST /properties/search](/api-v2-reference/properties-search/search-properties) * [Playbook — Géocoder pour search](/playbooks/geocoder-pour-search) * [Playbook — Search properties analytique](/playbooks/search-properties-analytique) # Répliquer la BDD adverts en sync continue Source: https://doc.fluximmo.io/playbooks/replicate-bdd-adverts Backfill historique sur webhook (par mail) + alerte ADVERT continue → BDD locale en miroir avec events PRICE/REPUBLISHED/UNPUBLISHED. ## Goal Maintenir une copie locale (PostgreSQL/MySQL/ClickHouse) de toutes les annonces (`adverts`) sur un périmètre donné, **synchronisée en continu** sur votre webhook : alerte ADVERT pour le flux post-création + demande de backfill historique pour rattraper le passé. ## Scénario Cas d'usage typiques : * **Notification live pour un agrégateur de recherche immobilière** — pousser les nouvelles annonces vers les utilisateurs finaux dès leur ingestion ; * **Veille temps-réel pour chasseur immobilier ou investisseur** — alertes sur publications fraîches matchant un cahier des charges ; * **Pipeline interne de traitement / réplique de DB / exposition d'API custom dérivées** — enrichir les annonces avec ses propres champs (scoring, tags internes, attribution) et requêter sans appeler Fluximmo à chaque page vue ; * **Capter 100% des événements de cycle de vie** — créations, changements de prix, dépublications, republications, sur l'ensemble du périmètre. L'objectif est d'avoir, à tout instant, la BDD locale en miroir de Fluximmo sur le périmètre choisi, avec un retard \< 1 minute sur le flux courant. ## Étapes Pour un cas réplication, `adverts` est **toujours** le bon choix : * **Payload webhook complet** : l'objet advert entier est livré dans le webhook, pas uniquement un `flxId`. Pas besoin de re-fetcher. * **Suivi inter-portails** : un même bien apparaissant sur deux portails distincts (par exemple un grand réseau d'agences et un mandataire indépendant) = 2 adverts liés au même `propertyFlxId`. * **Events** : `PRICE`, `REPUBLISHED`, `UNPUBLISHED` sont émis sur l'advert (pas sur la property). * **Reconstitution Property côté DB** : grouper les adverts par `propertyFlxId` permet de retrouver une vue dédupliquée localement. Voir [Property vs Advert](/concepts/property-vs-advert) pour les détails. ```sql theme={null} CREATE TABLE adverts ( flx_id TEXT PRIMARY KEY, property_id TEXT NOT NULL, -- racine de la chaîne de dédup raw_payload JSONB NOT NULL, price INTEGER, last_modified_at TIMESTAMPTZ NOT NULL, is_online BOOLEAN NOT NULL DEFAULT TRUE, unpublished_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_adverts_property ON adverts(property_id); CREATE INDEX idx_adverts_last_mod ON adverts(last_modified_at); CREATE TABLE advert_price_history ( id BIGSERIAL PRIMARY KEY, flx_id TEXT NOT NULL REFERENCES adverts(flx_id), price INTEGER NOT NULL, observed_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` L'index `idx_adverts_property` est ce qui permettra la reconstitution de la vue Property à l'étape 6. La création d'alertes ADVERT sur webhook se fait **sur demande** par mail. Écrivez à [contact@fluximmo.com](mailto:contact@fluximmo.com) en précisant : * Votre `clientId` * Le `webhook_url` cible (HTTPS public, doit ack en \< 1 s) * Les filtres souhaités (par exemple : département 75, achat appartement, `isOnline: true`) * Les types de match désirés : `ALERT_MATCH_CREATED` + `ALERT_MATCH_ADVERT_EVENT` (créations + events `PRICE`/`REPUBLISHED`/`UNPUBLISHED`) * Le volume estimé que votre receiver peut absorber Une fois l'alerte créée, vous recevez son `flxId` pour la modifier ultérieurement (en `PATCH`, jamais en `DELETE` + recréation). L'alerte ne fait **pas de backfill automatique** : elle ne match que les adverts ingérées **après** sa création. Pour rattraper le passé, écrivez à nouveau à [contact@fluximmo.com](mailto:contact@fluximmo.com) en précisant : * Le `flxId` de l'alerte créée à l'étape 3 * La période de backfill (ex. derniers 30 jours, 6 mois, 2 ans) * Le volume estimé que votre receiver peut absorber pendant la rejouage Le backfill est rejoué sur votre webhook avec le **même format de payload** que les matches normaux. Votre handler le traite sans changement de code, idempotence via `advert.flxId`. Le crawler Fluximmo attend un `200` rapide (\< 1 s). Toute logique métier doit être **différée** dans une queue (SQS / RabbitMQ / Redis Streams). ```text theme={null} # Pseudocode handler on POST /webhooks/fluximmo: if header.x-webhook-key != EXPECTED_KEY: return 401 enqueue(request.body) # SQS, RabbitMQ, Redis Streams return 200 ``` Le webhook a la shape canonique `{ data: { created, updated } }` : * **`data.created[].adverts[]`** = `AdvertDto` complet → UPSERT total côté DB * **`data.updated[].adverts[]`** = DTO réduit (`flxId`, `currentPrice`, `isOnline` uniquement) → UPDATE partiel + diff vs état stocké pour dériver les events PRICE / REPUBLISHED / UNPUBLISHED ```text theme={null} # Pseudocode worker handle_payload(body): # Branche CREATED — AdvertDto complet for entry in body.data.created or []: for advert in entry.adverts: UPSERT INTO adverts (flx_id, property_id, raw_payload, price, last_modified_at, is_online) VALUES (advert.flxId, advert.propertyFlxId, advert, advert.currentPrice.value, advert.lastModifiedAt, advert.isOnline) INSERT INTO advert_price_history (flx_id, price, source) VALUES (advert.flxId, advert.currentPrice.value, 'CREATED') # Branche UPDATED — DTO réduit, events dérivés client-side for entry in body.data.updated or []: for advert in entry.adverts: prev = SELECT price, is_online FROM adverts WHERE flx_id = advert.flxId new_price = advert.currentPrice.value new_online = advert.isOnline if prev is None: # advert pas en miroir local : log + ignorer (ou alerter pour reconciliation) continue UPDATE adverts SET price = new_price, is_online = new_online, updated_at = NOW() WHERE flx_id = advert.flxId if new_price != prev.price: INSERT INTO advert_price_history (flx_id, price, source) VALUES (advert.flxId, new_price, 'PRICE_DERIVED') if new_online and not prev.is_online: UPDATE adverts SET unpublished_at = NULL WHERE flx_id = advert.flxId if not new_online and prev.is_online: UPDATE adverts SET unpublished_at = NOW() WHERE flx_id = advert.flxId ``` **Reconstitution de la vue Property côté DB** — quand vous voulez interroger votre miroir comme si c'était `/properties` : ```sql theme={null} -- Une "Property" = ensemble d'adverts partageant le même property_id SELECT property_id, count(*) AS sources_count, min(last_modified_at) AS first_seen_at, max(last_modified_at) AS last_modified_at, bool_or(is_online) AS has_any_online, -- prix médian sur les adverts encore en ligne percentile_cont(0.5) WITHIN GROUP (ORDER BY price) FILTER (WHERE is_online) AS median_price_online FROM adverts WHERE property_id IS NOT NULL GROUP BY property_id; ``` **Idempotence** : Fluximmo livre parfois 2 fois le même webhook (retries sur timeout). L'`UPSERT` par `flxId` rend le worker safe par construction. **Reconciliation** : si vous suspectez une dérive (panne queue, bug worker, perte webhook), demandez à Fluximmo un nouveau backfill ciblé sur la fenêtre concernée (par mail à `contact@fluximmo.com`, en précisant la période). En pratique : **PATCH** votre alerte plutôt que `DELETE` + recréation, pour conserver le lien historique et continuer à recevoir des events sur les adverts déjà matchées. ## Architecture / flow ```mermaid theme={null} flowchart LR Mail["Mail à Fluximmo
(création alerte
+ backfill)"] --> FX[Fluximmo crawler] FX -- "advert ingérée
ou backfill rejoué" --> Webhook[POST webhook user] Webhook --> Ack[ACK 200 rapide] Ack --> Queue[Queue SQS / Redis Streams] Queue --> Worker[Worker async] Worker -- "UPSERT (data.created)
+ UPDATE partiel & diff (data.updated)" --> BDD[(BDD locale)] BDD --> View["Vue Property locale
(GROUP BY propertyFlxId)"] ``` ## Pièges fréquents **Properties au lieu de Adverts** : le webhook properties ne livre que des `flxId`, pas le payload complet, et n'émet pas d'events `PRICE`/`REPUBLISHED`/`UNPUBLISHED`. Mauvais choix pour une réplication marketplace. **Pas de backfill explicite** : l'alerte ne backfille pas seule. Sans étape 4 (demande par mail), vous n'aurez que les annonces **créées après** la création de l'alerte — trou historique permanent. **Traitement synchrone dans le webhook** : un endpoint qui fait `INSERT` puis `200` peut dépasser 1 s sous charge → timeouts → retries → effet de bord cumulatif. Toujours ACK d'abord, traiter ensuite (étape 5). **Pas d'idempotence** : sans `UPSERT` par `flxId`, les retries du crawler créent des doublons en BDD locale. **`DELETE` + recréer l'alerte** = perte des events historiques. Toujours `PATCH /alerts/{flxId}` pour modifier en place. ## Pour aller plus loin * [Concepts — Property vs Advert](/concepts/property-vs-advert) (`propertyFlxId` pour reconstituer la dédup côté client) * [Concepts — Match types & cycle d'alerte](/concepts/match-types-cycle-alerte) * [Concepts — Webhooks](/concepts/webhooks) * [API — PUT /adverts/search/alerts](/api-v2-reference/adverts-alerts/create-a-new-advert-alert-for-this-search) * [API — Sample advert webhook body](/api-v2-reference/sample-adverts/sample-advert-webhook-body) * [Playbook — Track price changes](/playbooks/track-price-changes) * [Playbook — Webhook volume architecture](/playbooks/webhook-volume-architecture) # Recherche analytique PROPERTIES (Paris achat appartement) Source: https://doc.fluximmo.io/playbooks/search-properties-analytique Cartographier le marché Paris achat appartement avec déduplication multi-portails, pagination cursor, stats prix m². ## Goal Construire un dataset **dédupliqué** d'appartements à l'achat sur Paris (puis l'Île-de-France), paginer pour l'exhaustivité, et calculer des statistiques **prix au m²** par département — sans biais lié aux doublons inter-portails. ## Scénario Vous êtes une fintech ou une équipe analytics qui doit cartographier le marché parisien : stock disponible, médianes prix/m², distribution par dpt. Compter les **annonces** brutes (Adverts) gonfle le volume car chaque bien est publié sur 3-8 portails. Vous travaillez donc sur **Properties** (entités dédupliquées) avec pagination cursor et un workflow reproductible. ## Étapes Une `Property` = 1 bien physique unique = N adverts agrégés. C'est l'objet à utiliser pour tout calcul agrégé : compter des biens (pas des publications), médianes prix, stocks, durées en ligne. Détail de l'asymétrie et des anti-patterns : [/concepts/property-vs-advert](/concepts/property-vs-advert). Trois bonnes pratiques : * `meta.isTotallyOffline: false` → exclut les biens dont **toutes** les adverts sont offline (sinon votre stock contient des fantômes). * `sortBy: "FIRST_SEEN_AT"` + `orderBy: "DESC"` → vous parcourez du plus récent au plus ancien, idéal pour reprendre une exploration à chaud. * `meta.firstSeenAt.min` → borne temporelle pour ignorer le très ancien (typiquement `2025-01-01T00:00:00.000Z`). * `size: 100` → max autorisé sur l'endpoint full search (lite est plafonné à 25). ```bash theme={null} curl -X POST "https://api.fluximmo.io/v2/protected/properties/search" \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "size": 100, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [{ "postalCode": "75001" }], "type": ["CLASS_FLAT", "CLASS_HOUSE", "CLASS_PROGRAM"], "offer": [{ "type": "OFFER_BUY" }], "price": { "initial": { "value": { "min": 100000, "max": 350000 } } }, "habitation": { "surface": { "total": { "min": 30, "max": 110 } }, "bedroomCount":{ "min": 1, "max": 3 } }, "meta": { "isTotallyOffline": false, "firstSeenAt": { "min": "2025-01-01T00:00:00.000Z" } } } } }' ``` La réponse est wrappée sous `data` et contient `items[]` (jusqu'à 100 properties), `count`, et un champ `searchAfterHash`. C'est un **cursor** opaque : ne le décodez pas, contentez-vous de le replacer dans la requête suivante. ```json theme={null} { "data": { "items": [ /* up to 100 Property */ ], "count": 1247, "searchAfterHash": "eyJzZWFyY2hfYWZ0ZXIiOlsxNzM..." } } ``` Boucle : tant que la dernière page ramène `size` items, replongez avec `searchAfterHash`. Stoppez quand `len(items) < size` (fin du dataset) ou quand votre cap user-defined est atteint. ```text theme={null} # Pseudocode pagination all_items = [] cursor = null HARD_CAP = 5000 while all_items.length < HARD_CAP: payload = base_payload if cursor: payload.searchAfterHash = cursor resp = POST /v2/protected/properties/search (payload) items = resp.data.items all_items.append_all(items) if items.length < SIZE: break # dernière page cursor = resp.data.searchAfterHash if !cursor: break ``` Deuxième page en curl direct : ```bash theme={null} curl -X POST "https://api.fluximmo.io/v2/protected/properties/search" \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "size": 100, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "searchAfterHash": "", "search": { "filterProperty": { "location": [{"postalCode":"75001"}], "type": ["CLASS_FLAT"], "offer": [{"type":"OFFER_BUY"}], "meta": {"isTotallyOffline": false} } } }' ``` Une fois le dataset collecté, agrégez par les 2 premiers caractères du `postalCode` (= département). ```text theme={null} # Pseudocode agrégation by_dpt = {} foreach p in all_items: cp = p.location.postalCode price_psm = p.price.latest.valuePerArea if cp and price_psm: dpt = cp[0..2] by_dpt[dpt].append(price_psm) foreach dpt, vals in by_dpt: sort(vals) n = vals.length q1 = vals[floor(0.25 * (n - 1))] med = vals[floor(0.50 * (n - 1))] q3 = vals[floor(0.75 * (n - 1))] print(dpt, n, q1, med, q3) ``` Pour passer de Paris-1er à Paris + petite couronne, remplacez `location[]` par 4 zones combinées en `OR`. ```json theme={null} { "size": 100, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "location": [ { "department": "75" }, { "department": "92" }, { "department": "93" }, { "department": "94" } ], "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "meta": { "isTotallyOffline": false, "firstSeenAt": { "min": "2025-01-01T00:00:00.000Z" } } } } } ``` Logique multi-zones complète et autres modes (bbox, geoDistance) : [/concepts/recherche-geographique](/concepts/recherche-geographique). ## Architecture / flow ```mermaid theme={null} flowchart LR C["Votre script
(POST search)"] --> FX["Fluximmo
(dedup engine)"] FX --> R1["Page 1
+ searchAfterHash"] R1 --> C C --> FX2["POST avec
searchAfterHash"] FX2 --> R2["Page 2..N"] R2 --> DF["DataFrame
(en mémoire)"] DF --> AGG["groupby(dpt)
+ describe"] AGG --> OUT["Stats prix m²
par dpt"] ``` ## Pièges fréquents * **Confondre Properties et Adverts** → analytics biaisé : un même bien compté 3-8 fois (1 par portail) gonfle le stock et fausse les médianes. Toujours `Properties` pour les KPIs marché. * **Oublier `meta.isTotallyOffline: false`** → vous comptez des biens dont toutes les annonces sont offline (fantômes du catalogue). * **Tenter une pagination par offset** : ça n'existe pas. Le seul mode est cursor `searchAfterHash`. Ne décodez pas le cursor — c'est opaque. * **Mixer `postalCode` et `city` dans la même entrée** : `city` est ignoré par le moteur. Voir [warning city deprecated](/concepts/recherche-geographique). * **Dépasser `size` max** : 100 sur full search, 25 sur lite search. Au-delà, l'API reject ou tronque. * **Ne pas dédoublonner sur `flxId`** entre pages : un append concurrent peut renvoyer un même item deux fois si le tri change. Conservez un `set` des `flxId` vus. ## Pour aller plus loin * [Property vs Advert](/concepts/property-vs-advert) * [Filtres communs](/concepts/filtres-communs) * [Recherche géographique](/concepts/recherche-geographique) * [Search Properties (référence API)](/api-v2-reference/properties-search/search-properties) * [Lite Search Properties (référence API)](/api-v2-reference/properties-search/lite-search-properties) * [Playbook — recherche géographique avancée](/playbooks/recherche-geographique-avancee) # Suivre les changements de prix en temps réel Source: https://doc.fluximmo.io/playbooks/track-price-changes Alerte ADVERT avec events PRICE/REPUBLISHED/UNPUBLISHED, parser le payload, stocker l'historique des prix. ## Goal Recevoir un webhook **à chaque variation de prix** (et republication / dépublication) sur un périmètre d'annonces, parser le payload, persister un historique `advert_id → (price, occurred_at)` exploitable pour calculer des tendances ou alerter sur les baisses. ## Scénario Un chasseur immobilier intelligent (ou une fintech / un CRM agence) veut être notifié dès qu'une annonce **déjà découverte** baisse son prix, est republiée, ou disparaît. L'objectif : déclencher une action métier (notif client, re-scoring d'opportunité) **dans la minute** qui suit le changement, sans poller l'API. ## Étapes Seules les **alertes adverts** émettent des events `PRICE`, `REPUBLISHED` et `UNPUBLISHED`. Les alertes properties ne reçoivent que `CREATED` (nouvelle property découverte) et `MERGED` (uniquement quand une nouvelle advert est fusionnée). Si le besoin est *price tracking*, il faut **obligatoirement** raisonner au niveau advert. Voir [Property vs Advert](/concepts/property-vs-advert) et [Match types & cycle alerte](/concepts/match-types-cycle-alerte) pour la liste exhaustive des events. Combiner `ALERT_MATCH_CREATED` (première découverte) et `ALERT_MATCH_ADVERT_EVENT` (cycle de vie). Sans `ALERT_MATCH_CREATED`, vous ne recevriez **jamais** d'events car aucune advert n'aurait été matchée historiquement par cette alerte. ```bash theme={null} curl -X PUT https://api.fluximmo.io/v2/protected/adverts/search/alerts \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "search_query": { "filterAd": { "location": { "department": "75" }, "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "currentPrice": { "value": { "max": 600000 } }, "isOnline": true } }, "webhook_url": "https://votre-domaine.com/webhooks/fluximmo", "match": ["ALERT_MATCH_CREATED", "ALERT_MATCH_ADVERT_EVENT"], "search_name": "Paris achat - veille prix" }' ``` Cf. [Créer une alerte adverts](/api-v2-reference/adverts-alerts/create-a-new-advert-alert-for-this-search) (exemple `B2`). Le webhook a la shape canonique `{ data: { created: [{adverts, alert_id}], updated: [{adverts, alert_id}] } }`. Les events `PRICE` / `REPUBLISHED` / `UNPUBLISHED` ne sont **pas émis explicitement** par le serveur — ils se **dérivent côté client** en comparant les adverts de la branche `updated` avec votre état stocké local. Le DTO réduit côté `updated` ne contient que `flxId`, `currentPrice {value, valuePerArea}`, `isOnline`. ```text theme={null} # Pseudocode handler on POST /webhooks/fluximmo: body = parse(body) # Branche CREATED — AdvertDto complet for entry in body.data.created or []: for advert in entry.adverts: on_new_advert(advert.flxId, advert) store(advert.flxId, price = advert.currentPrice.value, online = advert.isOnline) # Branche UPDATED — DTO minimal, event dérivé client-side for entry in body.data.updated or []: for advert in entry.adverts: prev = load(advert.flxId) # état local stocké new_price = advert.currentPrice.value new_online = advert.isOnline if prev is None: # advert pas encore vue : traiter comme CREATED partiel store(advert.flxId, price = new_price, online = new_online) continue if new_price != prev.price: on_price_change(advert.flxId, prev.price, new_price) # event PRICE dérivé if new_online and not prev.online: on_republished(advert.flxId, advert) # event REPUBLISHED dérivé if not new_online and prev.online: on_unpublished(advert.flxId) # event UNPUBLISHED dérivé store(advert.flxId, price = new_price, online = new_online) return 200 ``` Idempotence : dédup côté client avec une clé stable comme `(advert.flxId, alert_id, currentPrice.value, isOnline)`. Vous recevrez parfois le même webhook deux fois sur retry réseau. Une table append-only par `(flx_id, occurred_at)` suffit. Chaque event `PRICE` ajoute une ligne. ```sql theme={null} CREATE TABLE advert_price_history ( flx_id TEXT NOT NULL, price INTEGER NOT NULL, occurred_at TIMESTAMP NOT NULL DEFAULT NOW(), source TEXT NOT NULL, -- 'CREATED' | 'PRICE_DERIVED' | 'REPUBLISHED_DERIVED' PRIMARY KEY (flx_id, occurred_at) ); CREATE INDEX idx_price_history_flx ON advert_price_history (flx_id, occurred_at DESC); ``` À chaque webhook (`CREATED` ou `PRICE`), insérer une ligne avec le prix courant. Pour `UNPUBLISHED`, marquer dans une table séparée `advert_lifecycle` plutôt que de polluer l'historique de prix. ```text theme={null} # Pseudocode trend(flx_id): rows = SELECT price, occurred_at FROM advert_price_history WHERE flx_id = ? ORDER BY occurred_at if rows.length < 2: return null first = rows.first.price last = rows.last.price return { first, last, delta_pct: round((last - first) / first * 100, 2) } on_price_change(flx_id, old_price, new_price): if old_price > 0: delta_pct = (new_price - old_price) / old_price * 100 if delta_pct <= -5: # baisse de 5 % ou plus notify_client(flx_id, old_price, new_price, delta_pct) insert_price_history(flx_id, new_price, source = "PRICE_DERIVED") ``` ## Architecture / flow ```mermaid theme={null} sequenceDiagram participant Crawler as Fluximmo Crawler participant Engine as Match Engine participant Webhook as Votre Worker participant DB as advert_price_history participant Biz as Logique métier Crawler->>Engine: Détecte changement prix sur advert Engine->>Engine: Match advert vs alertes existantes Engine->>Webhook: POST {data:{created:[],updated:[{adverts:[{flxId,currentPrice}]}]}} Webhook->>Webhook: Diff vs état local → event PRICE dérivé Webhook->>DB: INSERT (flx_id, price, occurred_at) Webhook->>Biz: trigger règle (delta_pct <= -5%) Biz->>Biz: Notif client / re-scoring ``` ## Pièges fréquents **Confondre Properties et Adverts.** Les alertes properties ne reçoivent **pas** d'events `PRICE`. Si vous ne recevez jamais de variation de prix, vérifiez que vous utilisez bien `PUT /v2/protected/adverts/search/alerts` (pas `/properties/search/alerts`). **Oublier le diff client-side.** La branche `data.updated[]` ne porte qu'un DTO réduit (`flxId`, `currentPrice`, `isOnline`) — pas de `event_type`, pas de `previous`. Sans diff vs votre état local, vous mélangerez prix, republications et dépublications. Stockez systématiquement (price, isOnline) par flxId pour pouvoir comparer. **Recréer l'alerte au lieu de la modifier.** Les events ne s'appliquent qu'aux adverts **historiquement matchées par cette alerte précise**. `DELETE` + `PUT` repart de zéro. Utiliser [`PATCH /alerts/{flxId}`](/api-v2-reference/adverts-alerts/update-the-search-criteria-of-an-advert-alert) pour préserver l'historique. **Filtrage trop strict côté UI.** Une variation de **1 €** déclenche un webhook `PRICE`. Si vous affichez chaque event au client final, prévoyez un seuil minimum (ex. `abs(delta_pct) >= 1`) côté UI — pas côté ingestion (gardez tout en BDD). ## Pour aller plus loin * [Match types & cycle alerte](/concepts/match-types-cycle-alerte) — tous les events ADVERT et PROPERTY détaillés * [Webhooks](/concepts/webhooks) — format de livraison, retries, sécurité, idempotence * [Property vs Advert](/concepts/property-vs-advert) — pourquoi Adverts pour le price tracking * [Créer une alerte adverts](/api-v2-reference/adverts-alerts/create-a-new-advert-alert-for-this-search) — référence complète Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Chasseur immo Paris — alerte achat appartement T2-T3 Source: https://doc.fluximmo.io/playbooks/veille-chasseur-properties Persona chasseur immobilier basé à Paris : alertes properties pour mandats achat appartement T2-T3 (350-650k€) consommées dans une app interne — infra légère vs adverts. ## Cas concret Vous êtes chasseur immobilier basé à Paris. Vous recherchez des appartements T2-T3 dans le 11e/12e arrondissement, budget 350-650k€, pour le compte de mandats clients. Ce playbook montre comment recevoir en temps réel les nouvelles publications correspondant à ce profil et déclencher vos workflows internes (fiche mandat, notif au client, prise de RDV visite). ## Goal Faire remonter, **dans l'app interne du chasseur**, les nouveaux appartements à l'achat sur Paris correspondant à des **critères avancés combinés** (arrondissement, budget, surface, DPE) — sans monter une infra webhook lourde. ## Scénario Un chasseur (ou cabinet de chasse / gestion de patrimoine) gère **un petit nombre de mandats actifs** (quelques dizaines, pas des milliers) — typiquement des recherches achat appartement Paris intra-muros. Pour chaque mandat, il a une fiche client avec des critères précis (arrondissements, T2/T3, budget, DPE). Il veut que son app interne pousse une notif sur la fiche dès qu'un bien matche, sans poller l'API et sans devoir maintenir une queue + worker en production. C'est exactement le créneau des **alertes properties** : volume faible, payload directement consommable, infra réceptrice minimale. ## Pourquoi properties (et pas adverts) Pour ce cas d'usage : | | Properties | Adverts | | --------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------- | | **Volume webhook** | Faible — 1 webhook par nouvelle property qualifiée | Élevé — 1 webhook par publication / republication / changement de prix | | **Payload reçu** | IDs only (`flxId`) — refetch via `GET /properties/{flxId}` | Objet complet inline | | **Dédup côté serveur** | Oui (1 property = 1 bien réel, multi-portails fusionnés) | Non — chaque source compte | | **Infra receveur minimale** | Endpoint stateless + BDD | Endpoint + queue durable + worker async **obligatoires** | | **Adapté à** | App interne chasseur immo, CRM agence, B2C léger | Marketplace, réplica de base d'annonces, price tracking temps réel | Si la priorité est le **price tracking en temps réel** ou la **réplication d'un référentiel d'annonces brutes**, basculer sur le playbook adverts dédié — voir [Recevoir une alerte ADVERT par département](/playbooks/alerte-advert-departement) et [Suivre les changements de prix](/playbooks/track-price-changes). ## Étapes Côté app interne, chaque mandat porte (au minimum) : * Un identifiant client / mandate (ex. `Mandat #42 — Mme Dupont`) * Une zone : arrondissements parisiens en `postalCode` (ex. `75011`, `75012`) — voir [Recherche géographique](/concepts/recherche-geographique) pour les autres modes (`inseeCode`, `geoBoundingBox`, `geoDistance`) * Un budget `[priceMin, priceMax]` (typiquement 350-650k€ pour un T2-T3 Paris intra-muros) * Une surface habitable `[surfaceMin, surfaceMax]` (ex. 45-80 m² pour T2-T3) * Un nombre de pièces minimum (ex. `roomCount.min: 2`) * Optionnel : DPE max (`epcEnergy` ≤ D pour passoires hors-mandat), type (`CLASS_FLAT`) Le mapping vers le `filterProperty` Fluximmo est direct (cf. étape 2). Conserver côté app : `mandate_id ↔ alert_flxId` (renvoyé par Fluximmo à la création). `PUT /v2/protected/properties/search/alerts`. Garder `match: ["ALERT_MATCH_CREATED"]` (combo standard recommandé — `ALERT_MATCH_MERGED` multiplie le volume sans valeur ajoutée pour ce cas d'usage). Filtrer `meta.isTotallyOffline = false` pour ne pas être notifié sur des biens définitivement retirés. ```bash theme={null} curl -X PUT "https://api.fluximmo.io/v2/protected/properties/search/alerts" \ -H "x-api-key: $FLUXIMMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "search_query": { "filterProperty": { "location": [{ "postalCode": "75011" }, { "postalCode": "75012" }], "type": ["CLASS_FLAT"], "offer": [{ "type": "OFFER_BUY" }], "habitation": { "surface": { "total": { "min": 45, "max": 80 } }, "roomCount": { "min": 2 }, "climate": { "epcEnergy": [ "ENERGY_CLASSIFICATION_A", "ENERGY_CLASSIFICATION_B", "ENERGY_CLASSIFICATION_C", "ENERGY_CLASSIFICATION_D" ] } }, "price": { "latest": { "value": { "min": 350000, "max": 650000 } } }, "meta": { "isTotallyOffline": false } } }, "webhook_url": "https://app-interne.example.com/webhooks/fluximmo/mandate-42", "match": ["ALERT_MATCH_CREATED"], "search_name": "Mandat #42 — Mme Dupont (Paris 11/12, 350-650k, DPE ≤ D)" }' ``` Stocker le `flxId` retourné dans la fiche mandat. C'est lui qui sert à **modifier** ou **désactiver** l'alerte plus tard sans perdre l'historique des matches. **Plusieurs mandats d'un coup ?** Utiliser `PUT /v2/protected/properties/search/alerts/many` avec un body `{ "alerts": [SaveSearchPropertiesPayloadDto, ...] }` pour seeder N alertes en un appel — pratique au chargement initial d'un portefeuille de mandats. Pour les alertes **properties**, le payload webhook ne contient **que des flxIds** (chaînes nues dans `data.created[].properties` / `data.updated[].properties`) — pas l'objet complet. Vous refetchez ensuite chaque property via [`GET /v2/protected/properties/{flxId}`](/api-v2-reference/properties/get-a-property-by-its-flx_id), ou en bulk via [`POST /v2/protected/properties/_many`](/api-v2-reference/properties/retreive-properties-by-their-flx_ids) si vous batchez plusieurs IDs. Pour ce cas d'usage à faible volume, un handler stateless suffit — **pas besoin** de queue + worker comme pour les alertes adverts. Détails (signature `x-webhook-key`, retry policy, sécurité) sur [/concepts/webhooks](/concepts/webhooks). ```text theme={null} # Pseudocode handler on POST /webhooks/fluximmo/mandate-{mandate_id}: if header.x-webhook-key != EXPECTED_KEY: return 401 body = parse(request.body) for entry in body.data.created or []: alert_id = entry.alert_id mandate = lookup_mandate_by_alert_id(alert_id) # mapping local for flx_id in entry.properties: # entry.properties est un array de flxId (strings) if already_seen(mandate.id, flx_id): continue # idempotence detail = GET /v2/protected/properties/{flx_id} push_to_mandate_feed(mandate.id, detail) mark_seen(mandate.id, flx_id) # Idem côté updated (matches re-déclenchés / merges) for entry in body.data.updated or []: alert_id = entry.alert_id mandate = lookup_mandate_by_alert_id(alert_id) for flx_id in entry.properties: if already_seen(mandate.id, flx_id): continue detail = GET /v2/protected/properties/{flx_id} push_to_mandate_feed(mandate.id, detail) mark_seen(mandate.id, flx_id) return 200 ``` **Idempotence.** Un même match peut être livré 2 fois (retry après timeout réseau). Dédupliquer avec une clé stable comme `(mandate_id, propertyFlxId)`. | Événement métier | Endpoint | | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | | Le client affine ses critères (élargit la zone, baisse le budget…) | `PATCH /v2/protected/properties/search/alerts/{alertId}` | | Le mandat est mis en pause | `DELETE /v2/protected/properties/search/alerts/{alertId}` (désactivation logique, pas suppression physique) | | Reprise du mandat | `GET /v2/protected/properties/search/alerts/{alertId}/activate` | | Audit / debug | `GET /v2/protected/properties/search/alerts/{alertId}` (config) — `GET /v2/protected/properties/search/alerts/history/{alertId}` (matches passés) | **Ne jamais `DELETE` puis recréer une alerte** pour modifier des critères — vous perdez l'historique des properties déjà matchées sur cette alerte. Toujours `PATCH`. Côté app interne, deux vues complémentaires : * **Feed par mandat** — liste des matches récents sur la fiche client, triée par date de match. * **Feed unifié** — toutes les nouveautés cross-mandats du jour, pour le coup d'œil matinal du chasseur. Si une property matche **plusieurs mandats** (zones qui se recouvrent, critères proches), votre handler la recevra sur chaque webhook — afficher le bien une seule fois dans le feed unifié, en listant les mandats concernés (ne pas spammer). ## Architecture / flow ```mermaid theme={null} flowchart LR Manager["Chasseur
(app interne)"] -->|"PUT alerte par mandat"| FX["Fluximmo
(crawler + matcher properties)"] FX -- "property qualifiée" --> WH["POST webhook
(IDs only)"] WH --> H["Handler stateless
(valide x-webhook-key)"] H -->|"GET /properties/{flxId}"| FX H --> DB["BDD app interne
(mandate_id, property_flx_id)"] DB --> Feed["Feed in-app
(par mandat + unifié)"] Feed --> Manager ``` ## Pièges fréquents **Choisir adverts au lieu de properties par défaut.** Les alertes adverts demandent une queue durable + worker async pour tenir le volume — c'est de l'infra inutile si vous gérez quelques dizaines de mandats. Properties = bon défaut pour app interne. Bascule vers adverts uniquement si vous avez besoin du **price tracking** ou de la **réplication brute** (cf. [Property vs Advert](/concepts/property-vs-advert)). **Oublier le refetch.** Le payload properties contient **seulement** des flxIds (chaînes). Sans `GET /properties/{flxId}` (ou `POST /properties/_many` pour grouper), vous n'avez aucun détail à pousser dans la fiche mandat. **Filtres trop larges = spam.** Sur properties le volume reste raisonnable, mais une alerte « Île-de-France, tous types, 0-2M€ » noiera quand même la fiche client. Calibrer la zone et le budget au plus près du mandat réel. **`DELETE` + recréation au lieu de `PATCH`.** Vous perdez l'historique des properties déjà matchées par l'alerte — donc plus de cohérence dans le feed du mandat. Modifier en place. ## Pour aller plus loin * [Property vs Advert](/concepts/property-vs-advert) — quand basculer vers adverts * [Recherche géographique](/concepts/recherche-geographique) — `postalCode`, `inseeCode`, `geoBoundingBox`, `geoDistance` * [Match types & cycle alerte](/concepts/match-types-cycle-alerte) — détail des events * [Webhooks](/concepts/webhooks) — signature, retry, sécurité * [Créer une alerte properties — référence API](/api-v2-reference/properties-alerts/create-a-new-alert-for-this-property-search) * [Playbook adverts (gros volume)](/playbooks/alerte-advert-departement) * [Suivre les changements de prix](/playbooks/track-price-changes) — pour brancher du price tracking par-dessus Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Recevoir des webhooks à fort volume — bonnes pratiques Source: https://doc.fluximmo.io/playbooks/webhook-volume-architecture Architecture queue + worker async, ack rapide, idempotence, retry/DLX, observability. ## Goal Architecturer un endpoint webhook capable d'absorber des **pics de plusieurs centaines/milliers de webhooks par minute** sans en perdre un seul, avec ack \< 1 s et traitement métier asynchrone résilient. ## Scénario Vous êtes un client à fort volume — chasseur national, marketplace, agrégateur — et vous recevez en continu des webhooks `advert` ou `property` de Fluximmo, parfois en pic (rebond après une coupure, seed initial, période de marché chargée). Votre handler actuel fait du sync (DB write, fetch externe, scoring ML) : il timeout, Fluximmo retry, vous dropez du signal. Ce playbook donne l'architecture standard pour résoudre ça **une bonne fois pour toutes**. ## Étapes Quand votre handler webhook fait du **traitement synchrone lourd** (insertion DB + fetch d'une API tierce + inférence ML), il dépasse facilement 5–10 s sur les pics. Côté Fluximmo : * **Politique de retry** : 2 retries immédiats puis 10 retries espacés de 5 minutes (\~50 min de fenêtre totale). * **Codes acceptés** : `200`, `201`, `202`, `203`, `204`, `205`. Tout le reste = échec → retry. * Si après les 12 tentatives votre endpoint n'a toujours pas répondu 2xx, le webhook est **droppé**. Pas de re-livraison ultérieure. **Conséquence concrète** : 1 webhook perdu = 1 advert/property non synchronisé chez vous. À l'échelle, c'est un trou dans la base. C'est un **problème architectural**, pas un problème de code applicatif. La bonne réponse est de **découpler la réception du traitement**. Le pattern standard est : **endpoint léger qui ack en \< 1 s + queue + worker async**. ```mermaid theme={null} flowchart TD F["Fluximmo"] -->|POST webhook| EP["Endpoint webhook"] EP -->|"ack 200 < 1s"| F EP -->|push raw payload| Q[("Queue
SQS / RabbitMQ /
Redis Streams / Kafka")] Q -->|consume| W["Worker(s) async"] W -->|"DB upsert,
fetch externe, ML"| Done["ack message"] W -- "échecs × N" --> DLQ[("DLQ")] ``` L'endpoint ne fait **que** trois choses : valider le header de sécurité, pousser le raw payload dans la queue, répondre 200. Tout le métier vit dans le worker.
| Queue | Quand l'utiliser | Effort d'intégration | | ------------------ | ---------------------------------------- | -------------------- | | **AWS SQS** | Déjà sur AWS, simple, scalable, managed | Faible | | **RabbitMQ** | On-prem ou multi-cloud, routing avancé | Moyen | | **Redis Streams** | Déjà du Redis, volume modéré | Faible | | **Google Pub/Sub** | Déjà sur GCP | Faible | | **Kafka** | Très haut volume + besoin de replay long | Élevé | Pour 80 % des clients : **SQS standard** ou **RabbitMQ** suffisent largement. Ne sur-architecturez pas avec Kafka si vous n'avez pas de besoin de replay long terme. Le seul rôle : valider la signature, pousser dans la queue, répondre 200. ```python Python (FastAPI) theme={null} from fastapi import FastAPI, Request, Response, HTTPException import boto3, os app = FastAPI() sqs = boto3.client("sqs") QUEUE_URL = os.environ["SQS_QUEUE_URL"] WEBHOOK_KEY = os.environ["FLUXIMMO_WEBHOOK_KEY"] @app.post("/webhooks/fluximmo") async def receive(request: Request): if request.headers.get("x-webhook-key") != WEBHOOK_KEY: raise HTTPException(401) body = await request.body() sqs.send_message(QueueUrl=QUEUE_URL, MessageBody=body.decode("utf-8")) return Response(status_code=200) ``` ```javascript Node (Express) theme={null} import express from "express"; import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs"; const app = express(); app.use(express.raw({ type: "*/*" })); const sqs = new SQSClient({}); const QUEUE_URL = process.env.SQS_QUEUE_URL; const WEBHOOK_KEY = process.env.FLUXIMMO_WEBHOOK_KEY; app.post("/webhooks/fluximmo", async (req, res) => { if (req.header("x-webhook-key") !== WEBHOOK_KEY) return res.sendStatus(401); await sqs.send(new SendMessageCommand({ QueueUrl: QUEUE_URL, MessageBody: req.body.toString("utf-8"), })); res.sendStatus(200); }); app.listen(8080); ``` ```bash curl (test endpoint) theme={null} curl -X POST https://votre-app.com/webhooks/fluximmo \ -H "x-webhook-key: " \ -H "Content-Type: application/json" \ -d '{"data":{"created":[{"alert_id":"alr_demo","adverts":[{"flxId":"adv_demo"}]}],"updated":[]}}' ``` Le code ci-dessus tient en \~15 lignes. C'est **voulu**. Plus l'endpoint est mince, plus il est rapide et fiable. Le worker consomme la queue et exécute la logique métier. Il peut être déployé en N instances pour scaler horizontalement. ```python Python theme={null} import boto3, json, logging sqs = boto3.client("sqs") QUEUE_URL = "..." def handle(body: dict): # Webhook canonique : { data: { created, updated } } for entry in body.get("data", {}).get("created", []): for advert in entry.get("adverts", []): db.upsert_advert_full(advert["flxId"], advert, alert_id=entry["alert_id"]) for entry in body.get("data", {}).get("updated", []): for advert in entry.get("adverts", []): # DTO réduit : flxId, currentPrice, isOnline → diff vs état local db.update_advert_partial(advert["flxId"], advert, alert_id=entry["alert_id"]) def consume_loop(): while True: resp = sqs.receive_message( QueueUrl=QUEUE_URL, MaxNumberOfMessages=10, WaitTimeSeconds=20, # long polling ) for msg in resp.get("Messages", []): try: payload = json.loads(msg["Body"]) handle(payload) sqs.delete_message(QueueUrl=QUEUE_URL, ReceiptHandle=msg["ReceiptHandle"]) except Exception as e: logging.exception("worker failed: %s", e) # ne pas delete → SQS retry → après N échecs → DLQ ``` ```javascript Node theme={null} import { SQSClient, ReceiveMessageCommand, DeleteMessageCommand } from "@aws-sdk/client-sqs"; const sqs = new SQSClient({}); const QUEUE_URL = process.env.SQS_QUEUE_URL; async function handle(body) { for (const entry of body?.data?.created ?? []) { for (const advert of entry.adverts ?? []) { await db.upsertAdvertFull(advert.flxId, advert, entry.alert_id); } } for (const entry of body?.data?.updated ?? []) { for (const advert of entry.adverts ?? []) { // DTO réduit : flxId, currentPrice, isOnline → diff vs état local await db.updateAdvertPartial(advert.flxId, advert, entry.alert_id); } } } async function loop() { while (true) { const { Messages = [] } = await sqs.send(new ReceiveMessageCommand({ QueueUrl: QUEUE_URL, MaxNumberOfMessages: 10, WaitTimeSeconds: 20, })); for (const msg of Messages) { try { await handle(JSON.parse(msg.Body)); await sqs.send(new DeleteMessageCommand({ QueueUrl: QUEUE_URL, ReceiptHandle: msg.ReceiptHandle, })); } catch (e) { console.error("worker failed", e); } } } } loop(); ``` Les retries Fluximmo (et les retries internes de votre queue) peuvent livrer **le même webhook 2 fois**. Sans idempotence, vous dupliquez. **Patterns** : * **UPSERT** par `flxId` côté DB (`INSERT ... ON CONFLICT (flx_id) DO UPDATE`). * **Dedup key** : sur chaque advert iteré, hash de `(flxId, branch, alert_id)` dans une table de dédoublonnage avec TTL court (24h). ```python theme={null} def dedup_and_handle(body): for branch_name in ("created", "updated"): for entry in body.get("data", {}).get(branch_name, []): alert_id = entry.get("alert_id", "") for advert in entry.get("adverts", []): key = f"{advert['flxId']}:{branch_name}:{alert_id}" if redis.set(f"dedup:{key}", "1", nx=True, ex=86400): handle_advert(advert, branch_name, alert_id) # sinon : déjà traité, skip silencieux ``` **Dead Letter Queue** (DLQ) : configurez SQS / RabbitMQ pour qu'après N échecs (typiquement 5), le message bascule dans une DLQ. C'est votre filet de sécurité — vous pouvez l'inspecter et rejouer manuellement. **Métriques à monitorer** : * `webhooks_received` (compteur) — débit entrant * `webhooks_acked_under_1s` (ratio) — santé de l'endpoint * `queue_depth` (gauge) — bouchon en formation ? * `worker_lag_seconds` (gauge) — délai bout-en-bout * `dlq_count` (gauge) — messages échoués définitivement **Alerting** (Datadog, PagerDuty, Grafana) : * queue\_depth > seuil pendant > 5 min → scale up workers * dlq\_count > 0 → investigation manuelle * webhooks\_acked\_under\_1s \< 99 % → investiguer l'endpoint Fluximmo envoie un header `x-webhook-key` avec une valeur secrète que vous avez configurée. **Toujours le valider** avant de pousser dans la queue, sinon n'importe qui peut spammer votre endpoint. ```python theme={null} if request.headers.get("x-webhook-key") != WEBHOOK_KEY: raise HTTPException(401) ``` Voir [Concept — Webhooks](/concepts/webhooks) pour la configuration côté dashboard et les bonnes pratiques de rotation.
## Architecture / flow ```mermaid theme={null} flowchart LR Flux["Fluximmo"] -->|POST + x-webhook-key| EP["Endpoint
webhook"] EP -->|valide header| Auth{"Header
OK ?"} Auth -- "non" --> R401["401"] Auth -- "oui" --> Push["push raw payload"] Push --> Q[("Queue
SQS / RabbitMQ /
Redis Streams")] EP -->|"ack 200 < 1s"| Flux Q --> W1["Worker 1"] Q --> W2["Worker 2"] Q --> Wn["Worker N"] W1 --> Dedup{"Dedup
flxId ?"} W2 --> Dedup Wn --> Dedup Dedup -- "déjà vu" --> Skip["skip"] Dedup -- "neuf" --> Biz["UPSERT DB,
fetch externe, ML"] Biz --> OK["delete msg"] Biz -- "fail × N" --> DLQ[("DLQ")] Mon["Monitoring
queue_depth,
worker_lag, dlq"] -.-> Q Mon -.-> DLQ ``` ## Pièges fréquents **Traitement sync dans le handler.** L'erreur n°1. Sur un pic, le handler timeout, Fluximmo retry, votre app sature, effet boule de neige. Toujours découpler via une queue. **Pas de queue.** "Je traite directement, j'ai un load balancer, ça suffit." Non. Sur un pic réel, les workers saturent en quelques secondes et vous perdez du signal. La queue absorbe. **Pas d'idempotence.** Les retries (Fluximmo + retries de la queue) livrent le même message plusieurs fois. Sans UPSERT ni dedup-key, vous duplicate-write en DB. **Ignorer le header `x-webhook-key`.** Endpoint public + pas de validation = n'importe qui peut spammer votre queue. Validation systématique avant tout traitement. **Sous-dimensionner workers / queue.** Une queue qui se remplit plus vite qu'elle ne se vide = backlog persistant. Surveillez `queue_depth` et autoscalez les workers. **Ne pas monitorer la queue depth.** Sans observabilité sur la profondeur de la queue, un bouchon est invisible jusqu'à ce qu'un client se plaigne. Métriques + alerting dès le jour 1. **Renvoyer 5xx sur erreur métier.** Si vous renvoyez 500 quand votre DB échoue, Fluximmo retry. Préférez ack 200 + push en queue + gestion d'erreur côté worker (qui finira en DLQ si vraiment cassé). ## Pour aller plus loin * [Concept — Webhooks (politique de retry, codes acceptés, header `x-webhook-key`)](/concepts/webhooks) * [Concept — Match types et cycle d'alerte](/concepts/match-types-cycle-alerte) * [Playbook — Répliquer une BDD adverts via webhooks](/playbooks/replicate-bdd-adverts) * [Playbook — Tracker les changements de prix](/playbooks/track-price-changes) Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Bonnes pratiques Source: https://doc.fluximmo.io/ressources/bonnes-pratiques Pagination, retry, idempotence, refetch, validation webhook x-webhook-key — checklist production-ready. Cette page rassemble les patterns que nous voyons systématiquement chez les intégrations qui tournent en production sans alerte. ## Pagination — `searchAfterHash` Fluximmo utilise une pagination **par curseur**, pas par offset. Chaque réponse inclut un `searchAfterHash` à renvoyer dans la requête suivante pour récupérer la page d'après. Il n'existe **pas** de paramètre `offset` ou `page`. Toute tentative de "skipper" des pages doit passer par le curseur. ```python Python theme={null} def paginate(client, payload, page_size=100): payload["size"] = page_size cursor = None while True: if cursor: payload["searchAfterHash"] = cursor resp = client.post("/v2/protected/properties/search", json=payload).json() data = resp.get("data", {}) items = data.get("items", []) if not items: break yield from items cursor = data.get("searchAfterHash") if not cursor: break ``` ## Retry côté client (request) | Code HTTP | Retry ? | Stratégie | | ---------------- | -------------------- | ------------------------------------------------------ | | `2xx` | Non | OK | | `4xx` (sauf 429) | **Non** | Erreur côté appelant — corriger le payload | | `429` | Oui | Respecter `Retry-After` si présent, sinon backoff | | `5xx` | Oui (3-5 tentatives) | **Backoff exponentiel + jitter** : `2^n + random(0,1)` | Le **jitter** (composante aléatoire) évite le "thundering herd" si plusieurs workers retentent en même temps après un incident. ## Idempotence côté worker Tous les flux Fluximmo (search, alertes, webhooks) peuvent livrer le même `flxId` plusieurs fois. Conséquence : votre persistance doit être idempotente. ```sql PostgreSQL — UPSERT par flxId theme={null} INSERT INTO properties (flx_id, payload, updated_at) VALUES ($1, $2, NOW()) ON CONFLICT (flx_id) DO UPDATE SET payload = EXCLUDED.payload, updated_at = EXCLUDED.updated_at; ``` ## Refetch après webhook PROPERTIES Les webhooks Properties **ne livrent que les `flxId`** (pas le payload complet). Vous devez refetcher la donnée pour la persister. ```python Refetch unitaire theme={null} flx_id = webhook_payload["flxId"] prop = client.get(f"/v2/protected/properties/{flx_id}").json()["data"] upsert(prop) ``` ```python Refetch bulk (recommandé) theme={null} # Si votre webhook livre N IDs, batcher l'appel bulk plutôt que N appels unitaires ids = [w["flxId"] for w in webhook_batch] props = client.post("/v2/protected/properties/_many", json={"flxIds": ids}).json() for prop in props.get("data", []): upsert(prop) ``` Les webhooks **Adverts**, eux, contiennent le payload complet. Pas de refetch nécessaire. ## Validation `x-webhook-key` Toujours valider le header avec une comparaison **timing-safe** (voir [Livraison des webhooks](/ressources/webhooks-livraison#validation-du-header-x-webhook-key)). ```python Python theme={null} import secrets if not secrets.compare_digest(received_key, expected_key): abort(401) ``` ```javascript Node.js theme={null} import crypto from "node:crypto"; if (!crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))) { return res.sendStatus(401); } ``` ## Filtre par défaut : exclure les biens offline Activer un filtre offline est en général ce que vous voulez en production, sauf cas analytique rétrospectif. * **Côté PROPERTIES** : `filterProperty.meta.isTotallyOffline: false` exclut les biens dont **toutes** les annonces sont actuellement offline. * **Côté ADVERTS (alerte)** : `filterAd.isOnline: true` ne retient que les annonces actuellement publiées sur leur portail. ```json Payload search PROPERTIES theme={null} { "size": 25, "sortBy": "FIRST_SEEN_AT", "orderBy": "DESC", "search": { "filterProperty": { "meta": { "isTotallyOffline": false, "firstSeenAt": { "min": "2025-01-01T00:00:00.000Z" } } } } } ``` ## Tri par défaut : `FIRST_SEEN_AT DESC` `sortBy` au root du payload accepte : `FIRST_SEEN_AT`, `PRICE`, `LAST_UPDATED_AT`, `LAST_SEEN_AT`, `RELEVANCE`. Combiné avec `orderBy: "DESC"`, le tri canonique en production est `FIRST_SEEN_AT DESC` — vous récupérez d'abord les biens les plus récemment ingérés. Ne changez pas `sortBy` / `orderBy` entre deux pages : le cursor de pagination devient invalide. ## Couvrir passé + futur Pour ne rater aucun bien correspondant à un critère : 1. **Côté PROPERTIES** — `POST /v2/protected/properties/search` one-shot pour le passé + alerte property pour le flux continu. 2. **Côté ADVERTS** — alerte advert pour le flux continu + demande de **backfill historique sur webhook** par mail à [contact@fluximmo.com](mailto:contact@fluximmo.com) (volume + dates précisés). 3. **Dédupliquez** côté client avec `flxId` comme clé d'idempotence. ## Checklist production-ready * [ ] Clé API stockée en variable d'environnement (jamais en dur) * [ ] Header `x-api-key` envoyé sur toutes les requêtes * [ ] Retry avec backoff exponentiel + jitter sur 5xx et 429 * [ ] Pas de retry sur 4xx (sauf 429) * [ ] UPSERT par `flxId` côté persistance * [ ] Endpoint webhook valide `x-webhook-key` en timing-safe * [ ] Endpoint webhook acquitte en \< 1s (queue + worker async) * [ ] Refetch via `/properties/{flxId}` ou bulk après webhook Properties * [ ] Filtre offline activé par défaut (`meta.isTotallyOffline=false` côté PROPERTIES, `isOnline=true` côté ADVERTS) * [ ] Tri canonique `sortBy: "FIRST_SEEN_AT", orderBy: "DESC"` au root du payload search * [ ] Filtre temporel `meta.firstSeenAt.min` aligné sur votre besoin (typiquement `2025-01-01T00:00:00.000Z` minimum) * [ ] Monitoring : taux d'erreur 5xx, latence webhook ack, taille de queue # Codes d'erreur Source: https://doc.fluximmo.io/ressources/codes-erreur Format de réponse erreur Fluximmo + tableau des codes principaux + comment les corriger. L'API Fluximmo renvoie deux familles de codes : les **codes HTTP standards** (statut transport) et les **codes applicatifs Fluximmo** (raison métier précise). ## Format de réponse erreur Toutes les erreurs métier suivent la même enveloppe JSON : ```json theme={null} { "error": { "message": "Description lisible de l'erreur", "code": 10002 } } ``` Le champ `error.code` est un entier stable. Codez votre logique de retry/alerting sur ce code, pas sur le `message` (qui peut évoluer). ## Codes applicatifs Fluximmo | Code | Sens | Cause typique | Comment corriger | | ------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | | `10001` | Request timeout | La requête a dépassé le délai imparti côté Fluximmo (recherche très large, service amont lent) | Réduire la fenêtre de recherche ou `size`, puis réessayer avec un backoff | | `10002` | Validation error | Body ou query invalide : champ obligatoire manquant, type incorrect, valeur hors énumération. Renvoyé en **422** | Valider le payload contre le DTO. Voir [`/llms-context`](/llms-context) pour copier le schéma à votre LLM | | `10003` | Erreur générique | Code fourre-tout : c'est aussi celui des **404** (`flx_id` inconnu), des **401** et des erreurs amont non classées | Lire le `message` pour le détail et le code HTTP pour décider s'il faut réessayer | | `10004` | Service amont indisponible | Un service interne est injoignable. Toujours accompagné d'un **HTTP 503** | Réessayer plus tard avec un backoff exponentiel — la requête n'a rien consommé | `10003` est **générique**, pas spécifique : un `404` « ressource inexistante » et une erreur inattendue portent le même code. C'est le **statut HTTP** qui vous dit quoi faire ; le code applicatif affine la cause. Une erreur de validation est renvoyée en **422**, pas en 400, et ne contient **qu'un seul message** même si plusieurs champs sont invalides. Corrigez, rappelez, recommencez — ou validez côté client avant l'appel. ## Codes HTTP standards | HTTP | Sens | Action conseillée | | ----- | -------------------- | ----------------------------------------------------------------------------------------------------------- | | `200` | OK | Traiter la réponse | | `400` | Bad Request | Erreur côté appelant — voir `error.code` pour le détail. **Ne pas retry** | | `401` | Unauthorized | Clé manquante ou invalide (associé à `10003`). **Ne pas retry** | | `403` | Forbidden | Endpoint non activé sur votre clé. Voir [capabilities sur demande](/api-reference/capabilities-sur-demande) | | `404` | Not Found | Ressource (`flxId`, alerte, url portail) inexistante ou supprimée (associé à `10003`) | | `422` | Unprocessable Entity | Validation du body ou de la query (associé à `10002`). **Ne pas retry** sans corriger le payload | | `429` | Too Many Requests | Rate limit. Respecter `Retry-After` si présent, sinon backoff exponentiel + jitter | | `503` | Service Unavailable | Service amont injoignable (associé à `10004`). Retry avec backoff — rien n'a été facturé | | `5xx` | Server Error | Erreur serveur. Retry avec backoff exponentiel (max 3-5 tentatives) | Certaines routes utilisent aussi des **2xx métier** qui ne sont pas des erreurs : `202` (collecte en cours) et `422` enveloppé dans `data` (annonce rejetée par l'ingestion) sur [`/adverts/url`](/api-v2-reference/adverts/retrieve-advert-by-url) et [`/properties/url`](/api-v2-reference/properties/retrieve-a-property-by-url). Distinguez-les d'une vraie erreur par la clé racine : `data` contre `error`. ## Exemple de gestion d'erreur ```python Python theme={null} import requests import time def call_fluximmo(url, payload, api_key, max_retries=3): for attempt in range(max_retries): r = requests.post(url, json=payload, headers={"x-api-key": api_key}) if r.status_code == 200: return r.json() # Erreurs métier — pas de retry if r.status_code in (400, 401, 403, 404, 422): err = r.json().get("error", {}) raise ValueError(f"Fluximmo error {err.get('code')}: {err.get('message')}") # Rate limit — respecter Retry-After if r.status_code == 429: wait = int(r.headers.get("Retry-After", 2 ** attempt)) time.sleep(wait) continue # 5xx — backoff exponentiel if r.status_code >= 500: time.sleep(2 ** attempt) continue r.raise_for_status() ``` ```javascript Node.js theme={null} async function callFluximmo(url, payload, apiKey, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { const r = await fetch(url, { method: "POST", headers: { "x-api-key": apiKey, "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (r.ok) return r.json(); if ([400, 401, 403, 404, 422].includes(r.status)) { const { error } = await r.json(); throw new Error(`Fluximmo error ${error.code}: ${error.message}`); } if (r.status === 429) { const wait = Number(r.headers.get("Retry-After") ?? 2 ** attempt); await new Promise((res) => setTimeout(res, wait * 1000)); continue; } if (r.status >= 500) { await new Promise((res) => setTimeout(res, 2 ** attempt * 1000)); continue; } } throw new Error("Max retries exceeded"); } ``` Voir aussi [Bonnes pratiques](/ressources/bonnes-pratiques#retry-cote-client-request) pour les patterns de retry recommandés. # Foire aux questions Source: https://doc.fluximmo.io/ressources/faq Réponses aux 15 questions les plus fréquentes — onboarding, propriétés vs adverts, webhooks, rate limits, billing. Cette page regroupe les questions revenant le plus souvent en intégration. Pour un sujet précis, suivez le lien vers la page dédiée. ## Onboarding Créez un compte sur [my.fluximmo.io](https://my.fluximmo.io). Une clé test est générée automatiquement, valable **1 semaine**, sans carte bancaire. Voir [Authentification](/authentification). Quotas réduits (\~5 req/s, valable 7 jours) mais accès **complet** aux modules Search, Refetch, Analytics, IA et Géocodage. Seule restriction : la **création d'alertes webhook** se fait sur demande (par mail à [contact@fluximmo.com](mailto:contact@fluximmo.com)). Suffisante pour coder et valider une intégration de bout en bout. Activation depuis votre espace client [my.fluximmo.io](https://my.fluximmo.io) — onglet **API**. Pas d'engagement initial, pas de paiement avant validation de votre intégration. Pour un besoin custom (volume élevé, endpoints non publics, devis sur-mesure), écrivez à [contact@fluximmo.com](mailto:contact@fluximmo.com). Aucun engagement initial. Le pricing se fait au volume, et les conditions custom sont fixées au cas par cas selon votre besoin. ## Property vs Advert * **`/properties`** : un objet par bien physique unique (dédupliqué). Idéal pour de la BDD, du matching, des analytics. * **`/adverts`** : un objet par annonce publiée (un même bien peut générer plusieurs adverts). Idéal pour suivre la diffusion, les changements de prix, les portails. Voir la [matrice de décision](/concepts/property-vs-advert). Posez-vous la question : *« j'ai besoin du bien ou de l'annonce ? »*. Si la réponse contient "diffusion", "portail", "prix qui change", "republication" → adverts. Sinon → properties. Les webhooks Properties livrent uniquement le `flxId` pour optimiser le débit. Vous appelez ensuite `GET /v2/protected/properties/{flxId}` (ou `POST /v2/protected/properties/_many` pour le bulk) pour récupérer la donnée fraîche. Voir [Webhooks](/concepts/webhooks). ## Webhooks & alertes * Acquitter en **moins d'1 seconde** avec un code 2xx (200-205). * Mettre la logique métier en file d'attente côté worker (NE PAS traiter dans le handler HTTP). * Implémenter une logique **UPSERT par flxId** pour absorber les retries. Voir [Livraison des webhooks](/ressources/webhooks-livraison) et le playbook [Architecture haut volume](/playbooks/webhook-volume-architecture). La politique de retry peut livrer le même payload deux fois (ex : ack > 1s côté client, ou réponse 5xx transitoire). C'est attendu : votre logique de persistance doit être **idempotente** (UPSERT par `flxId`). Le cycle de vie redémarre à zéro : vous re-recevrez les matches initiaux. Pour modifier les critères sans re-livraison, **mettez à jour l'alerte existante** plutôt que de la recréer. Voir [Cycle de vie d'une alerte](/concepts/match-types-cycle-alerte). ## Filtres & géographie Le filtre `location[].city` est **ignoré** par le moteur. Utilisez `postalCode`, `inseeCode` ou `department`. Voir [Recherche géographique](/concepts/recherche-geographique). Le tableau `location[]` accepte plusieurs entrées combinées en OR. Mélangez codes postaux, INSEE, départements et bbox/distance dans une même requête. * **`geoBoundingBox`** : pour des zones administratives ou rectangulaires (carte, secteur agence). Cohérence des bornes (`topLeft` au-dessus et à gauche de `bottomRight`) validée runtime côté serveur. * **`geoDistance`** : pour un rayon autour d'un point (recherche client, point de vente). `distanceKm` strictement positif (validé runtime côté serveur). ## Billing & support Endpoint dédié : [`GET /v2/protected/admin/consumption`](/api-v2-reference/consumption/get-your-credit-consumption) (paramètres requis : `from_date`, `to_date`). Le dashboard my.fluximmo.io affiche aussi un récapitulatif. Certains endpoints de niche (Market analytics avancé, Properties analytics avancé, Agences, etc.) ne figurent pas dans la documentation publique par défaut. Ils peuvent être ajoutés à votre clé sur demande, ou faire l'objet d'un sur-mesure (devis). Détails dans la [matrice des capabilities](/api-reference/capabilities-sur-demande). [contact@fluximmo.com](mailto:contact@fluximmo.com) — incluez votre `clientId`, l'endpoint concerné, un exemple de requête et la réponse reçue. Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. # Limites & rate limits de l'API Fluximmo Source: https://doc.fluximmo.io/ressources/limites-rate-limits Limites payload (size, keywords, geo), quotas (clé test vs prod), comportement en cas de dépassement. Cette page rassemble toutes les bornes — payload, quotas, rate limits — utiles pour dimensionner votre intégration. ## Limites payload Bornes statiques validées côté API. Un dépassement renvoie `400` avec `error.code = 10002` ou `10003`. | Limite | Valeur | | ---------------------------- | ----------------------------------------------------------------------- | | `search.size` (full search) | **1 à 100** | | `search.size` (lite search) | **1 à 25** | | `search.keywords[]` | max **20 items** | | `search.fullTexts[]` | max **20 items** | | `geoDistance.distanceKm` | **> 0** (sinon erreur) | | `geoBoundingBox.topLeft.lat` | doit être **> `bottomRight.lat`** | | `geoBoundingBox.topLeft.lon` | doit être **\< `bottomRight.lon`** | | `location[]` | combinable (postalCode + insee + department + bbox/distance dans un OR) | Le filtre `location[].city` est **ignoré** par le moteur. Utilisez `postalCode`, `inseeCode` ou `department`. Voir [Recherche géographique](/concepts/recherche-geographique). ## Rate limits | Type de clé | Cadence indicative | | -------------------------- | -------------------------------- | | **Clé test** | \~5 req/s | | **Clé production starter** | \~15 req/s | | **Clé production custom** | 50+ req/s sur devis selon volume | Comportement en cas de dépassement : * HTTP **`429 Too Many Requests`** * Header **`Retry-After`** (en secondes) lorsque applicable — à respecter * En l'absence de `Retry-After`, appliquer un **backoff exponentiel + jitter** (voir [Bonnes pratiques](/ressources/bonnes-pratiques#retry-cote-client-request)) Pour ajuster votre cadence (gros batch, montée en charge, contrainte temps réel), écrivez à [contact@fluximmo.com](mailto:contact@fluximmo.com) avec votre `clientId` et votre besoin. ## Quotas par type de clé | Type de clé | Validité | Périmètre | | ------------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Clé test** | 1 semaine | Quotas réduits, suffisants pour découverte, POC et validation d'intégration. Accès complet aux modules Analytics, IA, Géocodage. Seule restriction : **création d'alertes webhook sur demande** (par mail à [contact@fluximmo.com](mailto:contact@fluximmo.com)). | | **Clé production** | Selon contrat | Activée depuis l'espace client [my.fluximmo.io](https://my.fluximmo.io). Pas d'engagement initial, pas de paiement avant validation de votre intégration. Pricing au volume. | Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis. ## Endpoints non publics par défaut Certains endpoints de niche ne sont pas exposés publiquement par défaut. Ils peuvent être ajoutés à votre clé sur demande, ou faire l'objet d'un sur-mesure (devis). Détails : [Capabilities sur demande](/api-reference/capabilities-sur-demande). ## Suivi de consommation L'endpoint dédié donne votre consommation en temps réel : [`GET /v2/protected/admin/consumption`](/api-v2-reference/consumption/get-your-credit-consumption) Le dashboard [my.fluximmo.io](https://my.fluximmo.io) affiche également un récapitulatif (jour, mois, par endpoint). Branchez l'endpoint `consumption` à votre monitoring pour lever une alerte avant d'atteindre 80% du quota mensuel. ## Que faire en cas de dépassement ? 1. **`429`** : appliquer le backoff, ralentir la cadence. 2. **Quota mensuel atteint** : les requêtes peuvent être rejetées. Contacter le support pour augmenter le quota. 3. **Endpoint non activé** (`403`) : demander l'activation via [contact@fluximmo.com](mailto:contact@fluximmo.com). Voir [Codes d'erreur](/ressources/codes-erreur) pour la gestion fine. # Startups & sur-mesure Source: https://doc.fluximmo.io/ressources/startups-et-sur-mesure Conditions adaptées pour les jeunes équipes, sur-mesure au cas par cas, études de cas anonymisées. ## Volumes & SLAs Le socle data sur lequel vous construisez : Nouvelles annonces ingérées chaque jour sur les principaux portails français. Entre publication portail et mise à disposition via webhook / API. Couverture du marché immobilier français. Présence en ligne et variation de prix re-vérifiées quotidiennement. Plusieurs centaines de millions d'annonces historisées et requêtables. Service opéré sans interruption depuis 2017. ## Pour qui Fluximmo accompagne les équipes en phase d'amorçage, seed ou Series A — typiquement moins de 50 personnes — qui construisent un produit où la donnée immobilière temps réel est un composant central. Cette page décrit ce qui est proposé, comment en bénéficier, et présente quelques exemples anonymisés. ## Ce qui est proposé * **Conditions tarifaires adaptées** ajustées au cas par cas selon volume, vertical et stade. Pas de grille publique : on cale le bon point ensemble selon votre profil. * **Onboarding accompagné** — un échange court pour cadrer les endpoints utiles, le mode de consommation (REST polling vs webhooks), et les éventuelles capabilities sur-mesure pertinentes. * **Endpoints non publics ou custom** disponibles sur demande selon votre use case. Voir [capabilities sur demande](/api-reference/capabilities-sur-demande). * **Support direct** par email pendant la phase d'intégration — pas de ticket à ouvrir, réponse rapide pendant les heures ouvrées. ## Comment en bénéficier Envoyez un email à [contact@fluximmo.com](mailto:contact@fluximmo.com) en présentant votre use case en 5 lignes : 1. **Qui vous êtes** : nom de l'équipe, taille, stade 2. **Ce que vous construisez** : produit, audience cible 3. **Pourquoi de la donnée immobilière** : à quoi sert-elle dans le produit 4. **Volume estimé** : ordre de grandeur (requêtes / mois, biens à monitorer, zones) 5. **Vos questions principales** : pricing, capabilities, intégration Email d'abord, call si pertinent. ## Études de cas anonymisées Trois exemples illustratifs, tous anonymisés (verticale + outcome uniquement) : Une fintech immobilière a réduit ses coûts data en remplaçant trois fournisseurs séparés (annonces, DVF, géocodage) par Fluximmo. Bénéfice secondaire : un seul contrat, une seule intégration à maintenir. Un moteur d'estimation AVM en phase seed a stabilisé son pipeline d'ingestion en deux semaines via les webhooks `ADVERT` et le playbook de réplication BDD. Avant : polling fragile avec dérive. Après : flux push fiable et resync hebdo automatisée. Un chasseur immobilier petite équipe a gagné plusieurs heures par jour sur la veille manuelle en passant aux alertes `ADVERT` temps réel avec filtres `geoDistance` et critères surface / pièces. Le delta de réactivité sur les nouvelles mises en marché est devenu un argument commercial. ## Avant de nous contacter Si vous voulez tester avant de discuter, une **clé API test gratuite** est disponible en self-service — c'est souvent le moyen le plus rapide de qualifier l'adéquation produit / besoin. ## Liens utiles * [Capabilities sur demande](/api-reference/capabilities-sur-demande) — endpoints non publics par défaut * [DVF vs Fluximmo](/concepts/dvf-vs-fluximmo) — comparatif et complémentarité * [Authentification](/authentification) — démarrer avec une clé # Livraison des webhooks Source: https://doc.fluximmo.io/ressources/webhooks-livraison Politique de retry, codes acceptés, SLO, validation x-webhook-key, debug. Cette page documente le contrat de livraison côté Fluximmo : ce que votre endpoint doit renvoyer, combien de fois on retente, et comment sécuriser la réception. **Important : avant de créer une alerte, vérifier que le webhook répond bien `200` aux données de sample disponibles sans auth (`GET /v2/sample/webhook/{adverts|properties}`).** ## Politique de retry Quand votre endpoint ne répond pas avec un code 2xx valide (voir plus bas), Fluximmo retente la livraison selon le calendrier suivant : | Tentative | Délai depuis le précédent | Cumulé approx. | | ------------------ | ------------------------- | ---------------- | | 1 (initiale) | — | T0 | | 2 (retry immédiat) | quelques secondes | \~5 s | | 3 (retry immédiat) | quelques secondes | \~10 s | | 4 à 13 | 5 min | jusqu'à \~50 min | | **DROP** | — | après \~50 min | Au bout de **2 retries immédiats + 10 retries espacés de 5 minutes** (≈ 50 minutes au total), le webhook est définitivement abandonné. Aucune relivraison automatique au-delà. ## Codes considérés OK | Code HTTP renvoyé | Interprétation Fluximmo | | ---------------------------------------- | ---------------------------------------------------- | | `200`, `201`, `202`, `203`, `204`, `205` | Livraison acquittée — pas de retry | | Tout le reste (3xx, 4xx, 5xx, timeout) | Échec — retry programmé selon la politique ci-dessus | Renvoyez `204 No Content` (ou `200`) **immédiatement après réception**, puis traitez le payload de manière asynchrone côté worker. C'est le pattern le plus robuste. ## SLO côté client * **Ack \< 1 seconde recommandé** : au-delà, la requête peut être considérée comme un timeout côté Fluximmo et déclencher un retry. * **Pas de traitement métier dans le handler HTTP** : pousser le payload dans une file (Redis, SQS, RabbitMQ…) et acquitter immédiatement. * **Idempotence requise** : un même `flxId` peut être livré plusieurs fois (retry sur ack lent, sur 5xx transitoire). Utilisez un UPSERT par `flxId` côté worker. ## Validation du header `x-webhook-key` Chaque webhook URL est associée à un secret partagé (généré à la création de l'alerte / abonnement). Fluximmo l'envoie dans le header HTTP **`x-webhook-key`**. **Validez systématiquement** ce header avant de traiter le payload. Un endpoint webhook public sans validation est une cible de spoofing. ```python Python theme={null} import os import secrets from flask import Flask, request, abort app = Flask(__name__) EXPECTED_KEY = os.environ["FLUXIMMO_WEBHOOK_KEY"] @app.post("/webhook/fluximmo") def receive(): received = request.headers.get("x-webhook-key", "") if not secrets.compare_digest(received, EXPECTED_KEY): abort(401) # Pousser dans la queue, acquitter immédiatement queue.publish(request.get_json()) return "", 204 ``` ```javascript Node.js theme={null} import express from "express"; import crypto from "node:crypto"; const app = express(); const EXPECTED = Buffer.from(process.env.FLUXIMMO_WEBHOOK_KEY); app.post("/webhook/fluximmo", express.json(), (req, res) => { const received = Buffer.from(req.header("x-webhook-key") ?? ""); if (received.length !== EXPECTED.length || !crypto.timingSafeEqual(received, EXPECTED)) { return res.sendStatus(401); } queue.publish(req.body); res.sendStatus(204); }); ``` Utilisez **`secrets.compare_digest`** (Python) ou **`crypto.timingSafeEqual`** (Node) pour éviter les attaques temporelles. Une comparaison `==` classique est exploitable. ## Idempotence Les retries sont identiques (même payload, même `flxId`). Côté persistance : * **UPSERT par `flxId`** : `INSERT ... ON CONFLICT (flx_id) DO UPDATE` (PostgreSQL) ou équivalent. * **Tracker des `webhookId` déjà vus** si vous avez besoin d'un dédoublonnage strict (rare). * **Webhooks Properties** : ne contiennent que des IDs — refetch via `GET /v2/protected/properties/{flxId}` ou l'endpoint bulk. ## Debug Si vous suspectez des webhooks perdus : 1. **Vérifier les logs côté client** : codes HTTP renvoyés, latence de réponse, statut du worker. 2. **Tester manuellement** votre endpoint avec un payload type (voir `/__samples/` ou les exemples de l'API reference). 3. **Vérifier que le header `x-webhook-key`** n'est pas en conflit avec une couche réseau (proxy, WAF) qui le strip. 4. **Contacter le support** ([contact@fluximmo.com](mailto:contact@fluximmo.com)) avec : URL endpoint, période suspectée, alertId concernée. Le replay manuel n'est pas garanti une fois le DROP atteint. Pour une architecture haut volume (file de message, idempotence, reprise sur crash), voir le playbook [Architecture webhook haut volume](/playbooks/webhook-volume-architecture).