> ## Documentation Index
> Fetch the complete documentation index at: https://doc.fluximmo.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Chasseur immo Paris — alerte achat appartement T2-T3

> 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)

<Snippet file="decision-matrix-property-vs-advert.mdx" />

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

<Steps>
  <Step title="1. Modéliser le mandat client → critères">
    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).
  </Step>

  <Step title="2. Créer une alerte properties par mandat">
    `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.

    <Snippet file="warning-cycle-vie-alerte.mdx" />

    <Note>
      **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.
    </Note>
  </Step>

  <Step title="3. Recevoir le webhook (IDs only) et refetch le détail">
    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
    ```

    <Note>
      **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)`.
    </Note>
  </Step>

  <Step title="4. Cycle de vie du mandat (PATCH / désactiver / réactiver)">
    | É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`.
  </Step>

  <Step title="5. UI in-app : feed unifié multi-mandats">
    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).
  </Step>
</Steps>

## Architecture / flow

```mermaid theme={null}
flowchart LR
  Manager["Chasseur<br/>(app interne)"] -->|"PUT alerte par mandat"| FX["Fluximmo<br/>(crawler + matcher properties)"]
  FX -- "property qualifiée" --> WH["POST webhook<br/>(IDs only)"]
  WH --> H["Handler stateless<br/>(valide x-webhook-key)"]
  H -->|"GET /properties/{flxId}"| FX
  H --> DB["BDD app interne<br/>(mandate_id, property_flx_id)"]
  DB --> Feed["Feed in-app<br/>(par mandat + unifié)"]
  Feed --> Manager
```

## Pièges fréquents

<Warning>
  **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)).
</Warning>

<Warning>
  **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.
</Warning>

<Warning>
  **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.
</Warning>

<Warning>
  **`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.
</Warning>

## 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

<Card title="Clé test gratuite — 1 semaine" icon="key" href="https://my.fluximmo.io">
  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.
</Card>
