Aller au contenu
Référence technique

Documentation API v1

Base URL : https://cherchertrouver.immo/api/v1

Des chapitres réservés s’affichent ici une fois connecté au compte auquel nous les avons ouverts.

Donner cette documentation à un assistant

Ces exports contiennent toute la référence en un seul fichier : points d’accès, paramètres, bornes et codes de réponse. Un assistant les ingère d’un bloc et sait ensuite appeler l’API sans inventer de paramètre.

Version publique permanente, sans compte : https://cherchertrouver.immo/llms.txt

Authentification

Toutes les requêtes doivent inclure votre clé API. Deux méthodes acceptées :

En-tête X-Api-Key Recommandé
X-Api-Key: imk_votre_cle
Authorization Bearer
Authorization: Bearer imk_votre_cle
Ne jamais exposer votre clé côté client (navigateur, app mobile). Toujours appeler l’API depuis votre serveur.

Limites et quotas

Chaque réponse acceptée inclut ces en-têtes pour surveiller votre consommation. Une réponse 429 porte Retry-After et X-Annonces-From, mais pas les compteurs de quota :

En-têteDescription
X-RateLimit-LimitNombre max de requêtes par minute
X-RateLimit-RemainingRequêtes restantes dans la fenêtre courante
X-RateLimit-ResetHorodatage UNIX de réinitialisation de la fenêtre
X-Quota-Items-LimitPlafond journalier d’annonces nouvelles (24 heures glissantes) ; jamais plus grand que le volume mensuel de l’offre
X-Quota-Items-UsedAnnonces reçues pour la première fois au cours des 24 dernières heures
X-Quota-Requests-LimitPlafond de requêtes sur 24 heures glissantes
X-Quota-Requests-UsedRequêtes faites au cours des 24 dernières heures
X-Quota-Mois-LimitQuota d’annonces de la période (offre souscrite)
X-Quota-Mois-UsedAnnonces déjà découvertes sur la période
X-Quota-Mois-RemainingCe qu’il vous reste sur la période
X-Quota-Mois-ResetDate de remise à zéro, sur un abonnement (absent sur la fenêtre glissante de l’offre gratuite)
X-Annonces-FromDate à partir de laquelle votre clé reçoit les annonces (cf. Périmètre du catalogue)
Ce qui compte : des annonces, pas des requêtes. Une annonce compte une seule fois, quand votre clé la reçoit pour la première fois ; ensuite elle ne coûte plus rien, quels que soient le point d’accès et la date (pages relues, flux repris, fiche ouverte). Une réponse en erreur, une fiche introuvable (404) et le point d’accès /ping ne comptent pas.
  • /annonces : chaque annonce de la page ; les autres publications du même bien, dans sources, sont comprises.
  • /annonces/feed : chaque publication, puisque le flux livre à part chaque site d’un même bien.
  • /annonces/doublons : une annonce par groupe, sa principale.
  • /annonces/map : chaque point.
  • /annonces/:source/:reference : l’annonce.
  • /annonces/retirees et les calculs des outils : aucune annonce.
Les fenêtres : le plafond journalier compte les annonces reçues pour la première fois au cours des 24 dernières heures, en continu : c’est une fenêtre glissante, qui ne repart pas à minuit. Le plafond de requêtes par jour se compte de la même façon, sur toutes vos requêtes, y compris celles qui relisent des annonces déjà reçues. Les compteurs suivent vos appels à une minute près. Relire un catalogue déjà reçu ne coûte donc aucune annonce, seulement des requêtes. Deux précautions : la taille d’une page est ramenée à ce qui reste sur vos quotas, comme si toutes ses annonces étaient nouvelles, et un quota atteint ferme tous les points d’accès d’annonces (402), annonces connues comprises. Gardez de la marge avant de relire.
Les quatre en-têtes X-Quota-Mois-* sont absents si votre compte n’a pas de quota mensuel : c’est le cas des comptes ouverts avant la mise en place des paliers, qui conservent leur régime d’origine et son seul plafond journalier.
Deux quotas, et ils ne se remplacent pas : le journalier protège le service, le mensuel délimite l'offre souscrite. Le mensuel se compte sur votre période de facturation ; sur l'offre gratuite, qui n'a pas d'abonnement, il se compte sur une fenêtre glissante de 30 jours. Au-delà, les annonces neuves reçoivent un 402 de code ITEMS_QUOTA : le corps porte quota, consomme, la date de reprise (repart_le sur un abonnement, se_libere_le sur la fenêtre glissante) et le palier_suivant. Pour distinguer un quota atteint d'une panne : /ping continue de répondre. Les autres points d'accès d'annonces, eux, renvoient tous 402, y compris pour une annonce que vous connaissez déjà : le refus est posé avant la recherche.
OffreAnnonces / moisAnnonces uniques / jourRequêtes / jourRequêtes / minuteRequêtes / seconde
Découverte 50 200 300 60 1
Secteur 1 000 200 600 60 1
Ville 5 000 500 1 500 60 1
Département 20 000 2 000 6 000 60 1
Région 60 000 6 000 18 000 120 2
National 150 000 15 000 45 000 300 5
Intégral 400 000 40 000 120 000 600 10
Sur mesure Nous consulter Nous consulter Sur mesure Sur mesure Sur mesure
C'est la colonne Annonces / mois qui mord en premier : dimensionnez dessus, pas sur le plafond journalier. Une annonce déjà reçue ne consomme rien, une requête si : les plafonds de requêtes laissent trois appels par annonce, de quoi lire une page puis ouvrir chaque fiche. Les tarifs de chaque palier sont sur la page API.

Périmètre du catalogue

Deux règles distinguent ce que renvoie l’API de ce qu’affiche la page publique. Elles jouent en sens inverse : la première réduit le périmètre, la seconde l’élargit.

1. Date de début d’accès

Une clé ne restitue que les annonces entrées dans notre catalogue à partir d’une date donnée, renvoyée dans l’en-tête X-Annonces-From de chaque réponse. Par défaut, cette date est fixée 30 jours avant la création de votre compte : vous essayez l’API dès l’inscription, sur un mois d’annonces. L’abonnement porte ensuite sur le flux courant.

Ce mois est l’historique maximal offert, essai compris. Un historique plus ancien est une option payante, à prévoir à la commande (CGV, article 4).

  • Elle dépend du compte, pas de la clé : en révoquer une et en créer une autre ne déplace pas cette date.
  • Elle porte sur la date d’entrée dans notre base, et non sur la date de publication sur le site d’origine ni sur updated_at. Le paramètre updated_since ne permet donc pas de remonter au-delà.
  • Elle s’applique à toutes les offres, gratuite comme payantes.
Un compte tout juste créé ne voit donc que quelques heures de collecte. Si vous évaluez l’API et obtenez un volume sans rapport avec le marché, c’est presque toujours cela : demandez-nous une fenêtre d’historique, nous l’ouvrons pour la durée de votre évaluation.

2. Aucun filtre de fraîcheur

La page publique n’affiche que les annonces revues dans les deux dernières semaines. L’API lève cette contrainte : elle vous sert aussi les annonces que nous n’avons pas revues depuis, sur la période à laquelle vous avez accès. C’est délibéré : une veille incrémentale a besoin de revoir une annonce même quand elle cesse d’être rafraîchie.

En revanche, une annonce explicitement passée à vendue, sous compromis ou archivée sort du flux, sur l’API comme sur le site. Une annonce qui disparaît de vos résultats est donc soit vendue, soit hors de votre période d’accès : ne déduisez pas de sa présence qu’un bien est toujours disponible, ni de son absence qu’il a été vendu.

Pour vous rapprocher de ce qu’affiche le site, filtrez avec updated_since : il porte sur la date de notre dernier passage de collecte, exposée dans le champ updated_at. Une annonce que nous n’avons pas revue depuis deux semaines a de fortes chances de ne plus être en ligne.

Format des erreurs

Toutes les erreurs retournent un JSON avec error (message lisible) et code (identifiant machine) :

{
  "error": "Limite de requêtes dépassée (60 req/min max)",
  "code": "RATE_LIMIT_PER_MIN",
  "retry_after_seconds": 42
}
HTTPCodeCause
401API_KEY_MISSINGAucune clé fournie
401API_KEY_INVALIDClé invalide, révoquée ou bloquée
400BAD_REQUESTParamètres invalides (détails dans details[])
404NOT_FOUNDRessource introuvable
429RATE_LIMIT_PER_SECLimite par seconde dépassée
429RATE_LIMIT_PER_MINLimite par minute dépassée
402QUOTA_EXCEEDEDQuota journalier d’annonces atteint
402ITEMS_QUOTAQuota d’annonces de la période atteint : le corps porte la date de reprise et le palier suivant
503SOURCE_UNAVAILABLEUne source de données (ventes notariées, commerces, écoles) n’a pas répondu : réessayer après retry_after_seconds. Ce n’est pas une absence de résultat
402PAID_PLAN_REQUIREDFiltre par site (sources, exclude_sources, /sources), réservé aux offres à partir de 118,80 € TTC par mois
GET

/api/v1/ping

Health check, vérifie que votre clé est valide et retourne les limites configurées pour votre offre. Utile pour tester l’authentification sans consommer de quota.

items_per_day est le plafond d’annonces distinctes par jour, distinct du volume mensuel de votre offre. Il vaut le dixième du volume mensuel, avec un minimum de 200. L’offre Secteur (1 000 annonces par mois pour 12 € TTC) est ainsi bornée à 200 annonces par jour. Le plafond de chaque offre figure aussi sur la page des tarifs. Pour l’offre Découverte, items_per_day et l’en-tête X-Quota-Items-Limit valent son volume, 50 annonces sur 30 jours : aucun plafond quotidien ne permet de le dépasser.
Réponse
{
  "ok": true,
  "api_key_name": "Clé personnelle",
  "tier": "free",
  "rate_limit_per_sec": 1,
  "rate_limit_per_min": 60,
  "items_per_day": 50,
  "annonces_from": "2026-03-19T00:00:00.000Z",
  "quota_mois": {
    "palier": "Découverte",
    "limit": 50,
    "used": 12,
    "remaining": 38,
    "reset": null
  },
  "server_time": "2026-04-18T10:00:00.000Z"
}

quota_mois est le volume de votre offre, celui qui refuse les appels quand il est atteint : mêmes valeurs que les en-têtes X-Quota-Mois-*. reset vaut null sur l’offre gratuite, dont la période glisse sur trente jours. quota_mois vaut null si votre compte n’a pas de quota mensuel. annonces_from est la date à partir de laquelle votre clé reçoit les annonces, la même que l’en-tête X-Annonces-From (Périmètre du catalogue).

GET

/api/v1/annonces

Recherche d’annonces, rendue par pages. Tous les filtres sont optionnels. Les partenaires API accèdent au catalogue complet, sans filtre de récence.

Filtre implicite : seules les annonces avec au moins une photo sont renvoyées (filtre implicite, non désactivable). Les annonces sans photo sont systématiquement filtrées côté base avant le découpage en pages.
Photos. images donne les adresses des photos de l’annonce, dans l’ordre du site d’origine, qui les héberge, et images_count leur nombre. dpe_chart_url et ges_chart_url donnent, quand le site en publie, l’adresse du schéma de ses étiquettes. Les photos restent la propriété de leurs auteurs : l’API transmet leurs adresses, pas un droit sur les images (CGU, propriété intellectuelle ; CGV, articles 2 et 3). Du 11/10 au 15/10/2026, ces champs ont été servis vides : ils sont de nouveau remplis. La carte (/annonces/map) ne porte pas de photo.
Étiquettes. dpe, ges, dpe_value et ges_value sont servis tels que l’annonce les publie. Quand ils se contredisent (une étiquette énergie meilleure que sa consommation, une étiquette climat contredite par ses émissions, le même nombre en consommation et en émissions), energy_to_verify le dit, avec la phrase de la fiche du site ; rien de ce qu’une règle en vigueur explique (logement de moins de 40 m², altitude, réforme de l’électricité du 1er janvier 2026) n’y figure.
Paramètres de requête
ParamètreTypeDescription
qstringRecherche plein texte (titre, description)
typestringType de bien : Appartement, Maison, Terrain…
transactionenumvente ou location
villestringNom de ville (ex : Paris, Lyon)
cpstringCode postal (ex : 75015)
deptstringNuméro de département (ex : 75, 2A)
regionstringNom de région (ex : Bretagne)
zonestringZone dessinée : de 3 à 25 coins latitude,longitude séparés par ; (ex : 47.50,-0.60;47.50,-0.50;47.40,-0.50). Seules les annonces situées dans le polygone sont rendues.
Prix
prix_minintegerPrix minimum (€)
prix_maxintegerPrix maximum (€)
prix_m2_minintegerPrix au m² minimum
prix_m2_maxintegerPrix au m² maximum
Surface et caractéristiques
surface_minnumberSurface minimale (m²)
surface_maxnumberSurface maximale (m²)
pieces_minintegerNombre de pièces minimum
chambres_minintegerNombre de chambres minimum
annee_mininteger Année de construction minimale. Renseignée sur environ 35 % des annonces : ce filtre écarte donc aussi les biens dont nous ne connaissons pas l’année. À utiliser pour trouver du récent à coup sûr, pas pour affirmer qu’un secteur n’en a pas.
annee_maxinteger Année de construction maximale. Même réserve de couverture.
DPE / GES
dpestring ou arrayClasses DPE : A,B,C ou dpe=A&dpe=B
gesstring ou arrayClasses GES (même format)
Veille incrémentale (date)
updated_since ISO 8601 Annonces (re-)collectées depuis cette date (= colonne updated_at de la réponse). À combiner avec sort=recent pour faire de la veille incrémentale fiable (« donne-moi ce qui a bougé depuis ma dernière poll »). Ex : updated_since=2026-06-09, ou la valeur updated_at reçue, telle quelle.
created_since ISO 8601 Annonces dont la mise en ligne sur le site source est postérieure à cette date (= colonne published_at). Moins fiable que updated_since pour la veille (certaines annonces sont ré-éditées sans changer leur date pub).
fresh boolean true : seulement les annonces encore actuelles, la règle du site (vues ces 14 derniers jours, ou confirmées en ligne il y a peu). Par défaut, toutes : un catalogue suivi dans le temps en a besoin. Vaut pour /annonces, /annonces/map et /annonces/feed.
nature string viager : seulement les viagers et ventes en démembrement, écartés par défaut comme sur le site.
inclure_natures boolean true : inclure aussi ce que le site écarte par défaut (viager, location saisonnière, chambre en colocation, local professionnel, bâtiment non habitable). Chaque annonce porte alors sa nature. Vaut pour /annonces, /annonces/map et /annonces/feed.
Pages et tri
pageintegerPage demandée (défaut : 1, max : 1000)
page_sizeintegerRésultats par page (défaut : 25, max : 100)
cursor string opaque Parcours par repère : chaque réponse porte le point de reprise de la suivante. À préférer dès qu’on descend loin dans les résultats. Au lieu d'incrémenter page, passer la valeur de next_cursor reçue dans la réponse précédente. O(1) par page quelle que soit la profondeur (contre page=N qui devient lent au-delà de 50 pages). Compatible avec tous les autres filtres ; ne pas envoyer page en même temps (cursor prend le pas).
sort enum recent (tri par updated_at DESC : date de collecte, idéal veille incrémentale) · price_asc · price_desc · surface_desc · pricem2_asc
sourceenumall (défaut) · interne · scraped
sourcesliste Ne garder que ces sites : site_a,site_b. Identifiants exacts servis par GET /sources. offre payante
exclude_sourcesliste Tout garder sauf ces sites : site_a. Plus pratique que sources quand on veut presque tout, et plus robuste : une liste blanche se périme dès qu’un site est ajouté, une liste noire non. Combinable avec sources : la différence est appliquée. offre payante
Ce que vaut la position : position_precision vaut adresse (rien n’indique un centre ; arrondie à ~111 m, et le centre d’une petite commune peut s’y trouver), secteur (le site ne donne que le centre d’un quartier ou d’une commune, ou une position qu’il arrondit au centième de degré, à environ 1 km) ou commune : le point est partagé par au moins cinq biens différents du même code postal, c’est donc un centre de commune ou de code postal posé faute d’adresse. position_coherente vaut false quand la position publiée est à plus de 10 km du centre de la commune déclarée : nous le signalons sans rien effacer, car c’est parfois la commune qui est fausse. Une exception : au-delà de 100 km, dans un autre département (ou loin de toute commune), quand le code postal et le nom de la commune s’accordent, la position ne peut pas être celle du bien. Nous servons alors le centre de la commune (position_source vaut commune), et position_coherente reste à false pour le dire. Sur un centre partagé (commune), il vaut null : sans adresse, il n’y a rien à contredire. insee_code : la commune retrouvée par son code postal et son nom, toutes graphies confondues, ou null quand le code postal dessert plusieurs communes et que le nom n’en désigne aucune. street : la voie que l’annonce publie, sans numéro ("Rue du Haras"), quand elle existe dans la commune du bien et n’est pas celle de l’agence (une voie que l’agence écrit sur la moitié de ses annonces, dans plusieurs communes ou pour plusieurs types de biens) ; sinon null, ce qui est le cas de la plupart des annonces. Le numéro n’est jamais servi, comme la position est arrondie : le logement n’est pas désigné. position_source : annonce (la position qu’elle publie) ou groupe : l’annonce n’en donne qu’un centre, et un autre site publie celle du même bien (même dedup_key). Elle n’est reprise que d’un grand site d’annonces, ou de plusieurs sites d’accord à moins de 300 m ; position_precision vaut alors adresse. Ou commune : le centre de la commune de l’annonce, à la place d’un point partagé qui en désignait une autre du même code postal, ou d’une position écartée (ci-dessus) ; à Paris, Lyon et Marseille, le centre de son arrondissement, à la place d’un point approché qui en désignait un autre (une annonce du 69003 posée place Bellecour, dans le 2e, est servie au centre du 3e) ; position_precision vaut alors commune.
Viager et démembrement : écartés des recherches par défaut, comme sur le site (nature=viager pour eux seuls, inclure_natures=true pour tout). Quand une annonce en est une, nature vaut "viager", price n’est pas le prix du bien (le bouquet d’un viager, le capital d’une vente à terme), price_per_m2 vaut null, et l’objet viager donne ce que l’annonce publie : rente mensuelle, valeur du bien libre, coût total estimé, âge des vendeurs.
Un prix ou une surface à vérifier : quand le prix au m² que donnent le prix et la surface publiés ne peut pas être le bon (très loin de la médiane des annonces du secteur pour ce type de bien, ou loyer trop bas pour un logement), price_per_m2 vaut null, comme sur la fiche et les cartes du site, et l’objet price_to_verify dit pourquoi : ce qui est sans doute faux (surface, price_or_surface ou rent), le prix au m² calculé, celui du secteur, et la phrase de la fiche. Il vaut null pour un prix plausible. La carte (/annonces/map), qui ne lit ni la description ni les mentions, juge sur le titre seul.
Une description incomplète : certains sites n’en publient qu’un extrait de quelques centaines de caractères. description_truncated vaut alors true, et le texte complet se lit sur external_url.
La copropriété : l’objet copropriete donne le nombre de lots, les charges courantes (annual_charges, monthly_charges) et la procédure en cours que l’annonce écrit, lus dans ses mentions puis dans sa description. Rien n’y est deviné : un montant dont l’annonce ne dit pas s’il est mensuel ou annuel n’est pas repris, et read_from dit où chaque valeur a été lue. Il vaut null quand l’annonce n’en dit rien, et pour une location.
Dates et heures : toutes nos dates sont en heure de Paris, avec leur décalage (+02:00 l’été, +01:00 l’hiver), par exemple 2026-10-04T21:35:38+02:00. En paramètre, passez la valeur reçue telle quelle : le décalage est respecté, une date sans décalage se lit en heure de Paris. Auparavant, updated_at et published_at étaient des heures de Paris servies suffixées Z, à tort : une valeur suffixée Z renvoyée dans updated_since ou created_since est donc lue comme avant, aucune annonce n’est sautée. Les dates du suivi des prix (price_history) et des retraits (retired_at) étaient déjà justes : elles désignent le même instant, écrit désormais en heure de Paris.
Pattern veille incrémentale recommandé : à chaque poll, stocker côté client le updated_at de la plus récente annonce reçue. Au poll suivant, requêter avec updated_since=<cette_date>&sort=recent et itérer via next_cursor jusqu'à has_more=false. Pour une fenêtre de 20 000 annonces, page_size=50 donne 400 pages, toutes accessibles en O(1) par page via le cursor. Aucun re-scan complet du catalogue n'est nécessaire.
Pour simplement tout aspirer et rester à jour sans gérer de date, utilisez plutôt le flux complet /annonces/feed : une seule boucle, un curseur, zéro trou.

Recherche interrompue. Une recherche très large (aucun lieu, filtre rare sur toute la France) peut dépasser le délai accordé à une requête. La réponse porte alors "degraded": true et un "message" : la page vide ne veut pas dire qu’aucune annonce ne correspond. Relancez, ou précisez un lieu (ville, cp, dept).

Exemple : itération par cursor
$ # Première page
$ curl -H "X-Api-Key: imk_xxx" \
  "https://cherchertrouver.immo/api/v1/annonces?updated_since=2026-06-09&sort=recent&page_size=50"
# → réponse contient "next_cursor": "eyJ1IjoiMjAyNi0wNi0...="

$ # Page suivante : passer next_cursor reçu
$ curl -H "X-Api-Key: imk_xxx" \
  "https://cherchertrouver.immo/api/v1/annonces?cursor=eyJ1IjoiMjAyNi0wNi0...="
Exemple de requête
$ curl -H "X-Api-Key: imk_xxx" \
  "https://cherchertrouver.immo/api/v1/annonces?ville=Nantes&type=Appartement&prix_max=350000&page_size=5"
Réponse
{
  "page": 1,
  "page_size": 5,
  "has_more": true,
  "next_cursor": "eyJ1IjoiMjAyNi0wNi0wOVQwMjozMjowMC4wMDBaIiwiciI6ImFiYzEyMyIsInMiOiJzZWxvZ2VyIn0",
  "items": [
    {
      "source": "site_a",
      "reference": "abc123",
      "link": "https://cherchertrouver.immo/annonces/appartement-nantes-44000/...",
      "title": "Appartement 3 pièces 68m² Nantes Centre",
      "type": "Appartement",
      "transaction_type": "vente",
      "price": 312000,
      "price_per_m2": 4588,
      "price_per_m2_color": "#f59e0b",
      "price_to_verify": null,
      "price_label": null,
      "nature": null,
      "viager": null,
      "surface": 68,
      "land_surface": null,
      "living_room_surface": 28,
      "rooms": 3,
      "bedrooms": 2,
      "bathrooms": 1,
      "shower_rooms": 1,
      "toilets": 1,
      "kitchen": "équipée",
      "year_built": 1975,
      "elevator": true,
      "parking": false,
      "parking_interior": null,
      "parking_exterior": null,
      "cellar": true,
      "garden": false,
      "seller_type": "Pro",
      "seller_name": "Agence Centre Immo",
      "real_estate_network": "Réseau Exemple",
      "exclusive": true,
      "city": "Nantes",
      "postal_code": "44000",
      "department": "44",
      "region": "Pays de la Loire",
      "street": "Rue Crébillon",
      "latitude": 47.213,
      "longitude": -1.561,
      "position_precision": "adresse",
      "position_source": "annonce",
      "position_coherente": true,
      "insee_code": "44109",
      "dpe": "D",
      "ges": "D",
      "dpe_value": 210,
      "ges_value": 42,
      "dpe_chart_url": null,
      "ges_chart_url": null,
      "energy_to_verify": null,
      "legal_info": "Copropriété : 24 lot(s) Charges prévisionnelles : 1 320 € / an...",
      "copropriete": {
        "lots": 24,
        "annual_charges": 1320,
        "monthly_charges": 110,
        "charges_written_per": "year",
        "procedure": "none",
        "read_from": { "lots": "legal_info", "charges": "legal_info", "procedure": "legal_info" }
      },
      "images": ["https://site-a.example/photos/1.jpg", "https://site-a.example/photos/2.jpg", "..."],
      "images_count": 8,
      "description": "Bel appartement lumineux...",
      "description_truncated": false,
      "external_url": "https://site-a.example/...",
      "video_url": null,
      "virtual_tour_url": "https://...",
      "published_at": "2026-04-15T10:23:00+02:00",
      "updated_at": "2026-09-05T06:12:00+02:00",
      "seller_contact": null,
      "price_history": {
        "current": 312000,
        "previous": 325000,
        "changed_at": "2026-08-14T05:12:00+02:00",
        "changes": 2,
        "tracked_since": "2026-06-23T04:10:00+02:00",
        "depth": 2
      },
      "is_internal": false,
      "dedup_key": "a1b2c3d4e5f60718",
      "dedup_version": 3,
      "sources": [
        { "source": "site_a", "reference": "123456", "url": "https://site-a.example/...", "price": 415000, "is_main": true },
        { "source": "site_b", "reference": "789012", "url": "https://site-b.example/...", "price": 418000, "is_main": false }
      ],
      "sites_count": 2
    }
  ]
}
Suivi de prix (price_history) : les deux derniers prix relevés sur l’annonce, dans toutes les offres. null tant que nous n’avons aucun relevé.
  • current et previous : le dernier prix et celui d’avant. previous vaut null quand le prix n’a pas bougé depuis que nous suivons l’annonce (changes vaut alors 0), ou quand le dernier changement n’est pas une vraie variation : retour à un prix déjà vu, prix d’une autre annonce du même site, ou écart de plus de 60 % (une erreur de saisie, ou sa correction).
  • tracked_since : la date de notre PREMIER relevé. Toute variation se compte à partir de là, jamais depuis la mise en vente : nous ne connaissons pas le prix d’une annonce avant de l’avoir vue. Un relevé par heure, sur les annonces actives, au seuil de 1 %.
  • depth : le nombre de prix réellement renvoyés. Il vaut 2 sans la série, sinon la longueur de points. Lisez-le plutôt que de déduire votre offre de l’absence d’un champ.
  • Profondeur attendue : le suivi a commencé le 23 juin 2026, et aucune annonce n’a donc davantage d’antériorité. Un bien mis en vente avant cette date arrive chez nous avec le prix qu’il avait ce jour-là, pas avec son historique complet.
  • points : la série complète des relevés, réservée aux offres à partir de 118,80 € TTC par mois et servie uniquement sur la fiche d’une annonce (/annonces/{source}/{reference}). Sur /annonces et le flux, aucune offre ne la reçoit.
Déduplication (activée par défaut) : un même bien listé sur plusieurs sites n'est renvoyé qu'une seule fois, sous son annonce de référence (le champ source = le site principal).
  • dedup_key : la clé du groupe de doublons (même bien = même clé sur tous les sites). null si l'annonce n'a pas de doublon connu, ou pas encore : le regroupement se calcule après l’entrée en base. Comment elle se calcule : sur le type de bien, la transaction, le prix (au millier d’euros pour une vente, par tranches de 50 € pour un loyer), la surface (à la dizaine de m²) et le code postal, jamais sur la position ; le groupe est ensuite contrôlé (distance entre publications, taille maximale, deux sites différents). Une publication posée au centre de la commune ne compte pas dans la distance ; le groupe doit alors avoir des surfaces et des prix à 5 % près. Elle peut donc changer : quand un prix change de tranche sur une publication, ou quand un groupe se reforme. Ses changements se suivent par /annonces/doublons.
  • dedup_version : la version de l’algorithme qui a produit la clé, vérifiée : 3 quand le calcul actuel, rejoué sur les publications du groupe, retombe exactement sur cette clé. null : pas de clé, ou clé à rafraîchir (calculée par un algorithme retiré, ou groupe en cours de recalcul). Une clé rangée chez vous sans version vient d’avant ce champ : relisez l’annonce pour la remplacer. Version 3, depuis octobre 2026 : le loyer d’une location se range par tranches de 50 € (au millier en version 2, qui réunissait des loyers de 520 et 1 370 €). Une clé de vente de la version 2 est identique en version 3 : seul le numéro change. Les clés de location changent, et chaque groupe recalculé passe par /annonces/doublons.
  • sources : la liste de TOUS les sites du groupe (le principal + les doublons), chacun avec source, reference, url, price, surface, land_surface et is_main. Les surfaces sont celles que publie chaque site : elles peuvent différer d’un site à l’autre pour un même bien, et nous ne savons pas laquelle vient du mandat. null s'il n'y a pas de doublon (avec dedup_key: null).
  • <champ>_source : quand une info manquante a été complétée depuis un doublon d'un autre site, ce champ en indique la provenance. Exemple : une annonce sans description qui récupère celle d'un doublon publié sur un autre site renvoie "description_source": "site_b". S'applique à la description, au titre, aux photos et aux caractéristiques (ex : bedrooms_source, dpe_source). Absent si le champ provient de l'annonce elle-même.
  • Pour recevoir tous les doublons non regroupés (comportement historique), ajouter ?dedup=0.
Champs null = information non disponible pour cette annonce. seller_type vaut "Pro", "Particulier" ou null. Les booléens d'équipement (elevator, parking, parking_interior, parking_exterior, cellar, garden, exclusive) valent true, false ou null (inconnu). dpe_value / ges_value sont les valeurs numériques exactes (kWh/m²/an et kg CO₂/m²/an) quand l'annonce les fournit, en complément des lettres dpe / ges.
GET

/api/v1/annonces/feed

Flux complet du catalogue. Conçu pour un seul besoin : récupérer toutes les annonces qui entrent en base, puis rester à jour en continu, sans jamais en manquer ni gérer de pages. Une seule boucle : on rappelle avec le next_cursor reçu, indéfiniment.

Aucun plafond de profondeur. Contrairement au parcours par pages page (limitée à 1000 pages, soit ~100 000 annonces), le flux parcourt l’intégralité du catalogue puis suit les nouveautés sans limite.
Paramètres de requête
ParamètreTypeDescription
cursor string opaque Position de reprise. Absent au 1er appel (démarre au plus ancien). Ensuite, réinjecter la valeur next_cursor de la réponse précédente. À conserver côté client pour reprendre après un redémarrage.
page_sizeintegerAnnonces par page (défaut : 100, max : 100)
Filtres divers Tous les filtres de /annonces (type, ville, dept, prix_max…) sont acceptés pour ne suivre qu’un sous-flux. Sans filtre : tout le catalogue.
La boucle (une seule) :
  1. Appeler sans cursor au démarrage.
  2. Traiter items, enregistrer next_cursor, rappeler avec ?cursor=<next_cursor>.
  3. Répéter tant que items n’est pas vide : vous parcourez tout le catalogue (rattrapage initial).
  4. items vide (has_more=false) : vous êtes à jour. Attendre (1 à 5 min) puis rappeler avec le même cursor  : les nouvelles annonces arrivent au fil de l’eau.
Zéro trou garanti. Une annonce apparaît dans le flux ~2 min après son entrée en base (délai de sécurité qui garantit qu’aucune n’est sautée pendant les insertions). Négligeable pour une synchronisation.
Dédupliquez par (source, reference). Le flux est un change-feed : une annonce ré-analysée réapparaît (données à jour). Les sites d’un même bien sont livrés séparément : reliez-les par dedup_key, présent ici comme sur /annonces, avec sources. Le regroupement se calcule après l’entrée en base : une publication peut arriver une première fois avec dedup_key à null. Sa clé, et l’arrivée d’un nouveau site dans un groupe déjà reçu, vous parviennent par /annonces/doublons : le flux ne relivre une annonce que lorsqu’elle est de nouveau collectée.
Exemple
$ # Premier appel (sans cursor)
$ curl -H "X-Api-Key: imk_xxx" \
  "https://cherchertrouver.immo/api/v1/annonces/feed?page_size=100"
# → réponse : "next_cursor": "eyJ1IjoiMjAyNi0wNy0wMS...="

$ # Appels suivants : réinjecter next_cursor. Page vide = à jour → re-poller plus tard.
$ curl -H "X-Api-Key: imk_xxx" \
  "https://cherchertrouver.immo/api/v1/annonces/feed?cursor=eyJ1IjoiMjAyNi0wNy0wMS...="
Réponse
{
  "count": 100,
  "has_more": true,
  "next_cursor": "eyJ1IjoiMjAyNi0wNy0wMVQxMDowMDowMFoiLCJyIjoiYWJjMTIzIiwicyI6ImxlYm9uY29pbiJ9",
  "items": [
    {
      "source": "site_b",
      "reference": "abc123",
      "title": "Studio 30m² Paris 15e",
      "price": 385000,
      "surface": 30,
      "city": "Paris",
      "postal_code": "75015",
      // ... mêmes champs que /annonces
    }
  ]
}
next_cursor est toujours présent (même quand items est vide) pour permettre la reprise. Il vaut null uniquement si la base est momentanément indisponible. Le quota ne compte que les annonces nouvelles : un renvoi ne consomme rien.
GET

/api/v1/annonces/doublons

Les groupes de doublons qui ont changé depuis une date. Quand un site publie un bien que nous connaissons déjà, sa publication rejoint le groupe (même dedup_key). Mais les publications déjà reçues ne vous sont pas renvoyées : leur updated_at est leur date de collecte, et le regroupement ne la change pas. Ce point d’accès vous donne, pour chaque groupe modifié, la liste actuelle de ses publications.

Paramètres de requête
ParamètreTypeDescription
changed_sincedate ISO 8601Premier appel : depuis quand. 24 h par défaut, 30 jours au plus (les modifications plus anciennes ne sont pas gardées).
departementtexteSeulement les groupes de ce département (33, 2A, 974). Le curseur ne le porte pas : repassez-le à chaque appel.
cursorstring opaqueLes appels suivants : le next_cursor reçu, tel quel. Il est toujours présent, page vide comprise.
page_sizeintegerGroupes par page (défaut : 100, max : 200)
$ curl -H "X-Api-Key: ..." "https://cherchertrouver.immo/api/v1/annonces/doublons?changed_since=2026-10-01"
{
  "items": [
    {
      "dedup_key": "a1b2c3d4e5f60718",
      "dedup_version": 3,
      "changed_at": "2026-10-01T15:40:12+02:00",
      "sources": [
        { "source": "site_a", "reference": "123", "url": "https://…", "price": 290000, "surface": 156, "land_surface": 80, "is_main": true },
        { "source": "site_b", "reference": "456", "url": "https://…", "price": 290000, "surface": 156, "land_surface": 171, "is_main": false }
      ]
    }
  ],
  "count": 1,
  "has_more": false,
  "history_since": "2026-10-07T18:05:12+02:00",
  "complete": true,
  "dedup_version_current": 3,
  "next_cursor": "MjAyNi0xMC0wMVQx…"
}
  • sources vide : le groupe n’existe plus (ses publications ont été séparées) ; retirez la clé de vos fiches. Sa dedup_key reste présente (c’est elle qu’il faut retirer) et sa dedup_version vaut null : il n’y a plus rien à vérifier. Sur /annonces, une annonce sans groupe porte au contraire dedup_key: null et sources: null.
  • history_since : la plus ancienne modification gardée. Le journal existe depuis octobre 2026 et garde 30 jours. complete: false (avec un message) : votre date de départ remonte plus loin, et les modifications antérieures n’y figurent pas ; pour un état complet, relisez dedup_key et sources sur /annonces.
  • dedup_version_current : la version de clé en vigueur (3 depuis le 06/10/2026). La dedup_version d’un groupe vaut ce nombre quand le calcul actuel retombe sur sa clé. Elle se vérifie à la lecture : son changement seul n’est pas une modification du groupe. Un groupe recalculé sous une autre clé apparaît deux fois : l’ancienne clé (vide ou réduite) et la nouvelle.
  • Une clé peut revenir plusieurs fois au fil des jours : la dernière réponse fait foi.
  • Quota : comme sur /annonces, une annonce par groupe, sa principale (is_main), si votre clé ne l’a jamais reçue ; les autres publications du groupe sont comprises, comme sources sur une liste. Une principale déjà reçue ne coûte rien, et un groupe qui n’existe plus non plus. Depuis le 13/10/2026 ; avant, chaque publication jamais reçue comptait.
  • Le journal est écrit au fil de chaque passage du regroupement, plusieurs fois par heure, groupe par groupe.
GET

/api/v1/annonces/map

Retourne les points géographiques d’une zone définie par son rectangle englobant. Format léger, taillé pour alimenter une carte Leaflet ou Mapbox. Accepte les mêmes filtres que /annonces.

Paramètre spécifique
ParamètreRequisDescription
bbox Oui Rectangle englobant, au format sud,ouest,nord,est (degrés décimaux)
Exemple
$ curl -H "X-Api-Key: imk_xxx" \
  "https://cherchertrouver.immo/api/v1/annonces/map?bbox=47.15,-1.65,47.30,-1.45&type=Maison"
Réponse
{
  "bbox": { "south": 47.15, "west": -1.65, "north": 47.30, "east": -1.45 },
  "total": 142,
  "capped": false,
  "items": [
    {
      "source": "site_b",
      "reference": "xyz789",
      "latitude": 47.218,
      "longitude": -1.554,
      "position_precision": "adresse",
      "price": 285000,
      "price_per_m2": 2375,
      "price_per_m2_color": "#16a34a",
      "price_to_verify": null,
      "surface": 120,
      "type": "Maison",
      "title": "Maison 5 pièces 120m²",
      "city": "Nantes",
      "postal_code": "44000",
      "rooms": 5,
      "image": null,
      "link": "https://cherchertrouver.immo/annonces/maison-nantes-44000/…",
      "is_internal": false
    }
  ]
}
capped: true indique que la bbox contient plus d’annonces que la limite retournée (5 000 points au plus par requête, les plus récents). Zoomez ou affinez les filtres. Chaque point est placé à la position de l’annonce, arrondie à environ 100 m ; sans position publiée, au centre de sa commune, et position_precision vaut alors "commune". La carte place chaque publication à sa position : celle qu’un autre site publie pour le même bien (position_source: "groupe") n’est servie que par /annonces, le flux et la fiche. Une lecture interrompue (délai dépassé) répond degraded: true avec un message : une liste vide ne veut alors pas dire « aucune annonce ». Quota : chaque point est une annonce, comptée comme sur une liste si votre clé ne l’a jamais reçue ; une zone relue ne coûte rien de plus. La réponse s’arrête au reste de votre quota.
GET

/api/v1/annonces/:source/:reference

Retourne le détail complet d’une annonce identifiée par sa source et sa référence. Compte 1 annonce dans vos quotas (le jour et la période) si votre clé ne l’a jamais reçue ; une fiche déjà reçue, par cette route ou par une liste, ne coûte rien, et une fiche introuvable (404) non plus.

Paramètres de chemin
ParamètreDescription
:sourceIdentifiant de la source, tel que servi par /annonces/sources
:referenceRéférence unique de l’annonce sur la source (max 128 chars)
Exemple
$ curl -H "X-Api-Key: imk_xxx" \
  "https://cherchertrouver.immo/api/v1/annonces/site_a/abc123"
Réponse
{
  "annonce": {
    "source": "site_a",
    "reference": "abc123",
    // ... tous les champs (même structure que /annonces)
  }
}
GET

/api/v1/ptz/zone

Lookup officiel d’une commune française vers sa zone du prêt à taux zéro (PTZ) (A bis / A / B1 / B2 / C) selon l’arrêté du 5 septembre 2025 du Ministère du Logement. Zonage utilisé par tous les dispositifs aidés : Prêt à Taux Zéro, Pinel, Logement Locatif Intermédiaire, Bail Réel Solidaire, PSLA. Coût : 1 token par lookup réussi (200). Les erreurs (400/404) ne consomment pas.

Paramètres de requête
ParamètreDescription
cp requisCode postal français à 5 chiffres (ex : 75011)
Exemple
$ curl -H "X-Api-Key: imk_xxx" \
  "https://cherchertrouver.immo/api/v1/ptz/zone?cp=75011"
Réponse (200 OK)
{
  "zone": "Abis",
  "codePostal": "75011",
  "source": "Arrêté du 5 septembre 2025, Ministère du Logement"
}
Réponse (404, code postal inconnu)
{
  "error": "Code postal inconnu"
}
Les valeurs de zone possibles sont : "Abis" (Paris, ultra-tendu), "A" (grandes métropoles), "B1" (agglos tendues), "B2" (villes moyennes), "C" (rural & petites villes, ~81 % du territoire). Couvre 34 875 communes françaises. Cache HTTP : 24h (la zone d’une commune ne change que par arrêté ministériel, ~1× par an).
POST

Connecteur pour assistants

Le même catalogue et les mêmes outils, présentés à un assistant (Claude, ChatGPT, Cursor, VS Code…) par le protocole MCP. Ouvert à tous les comptes : présentation.

POST https://cherchertrouver.immo/api/v1/mcp
Authorization: Bearer VOTRE_CLE_API
Content-Type: application/json
Accept: application/json, text/event-stream
  • Tout ce que l’API sert en lecture, avec les mêmes droits : annonces (recherche, fiche, carte, flux, retraits), zone PTZ, état de l’accès, les outils de calcul (compris selon l’offre, comme /tools) et la veille des SCI (sur option). La liste tools/list ne montre que ce que la clé peut utiliser.
  • Transport HTTP du protocole, sans session : chaque requête porte un seul message JSON-RPC (les lots sont refusés) et reçoit une réponse JSON.
  • Même clé et même en-tête que le reste de l’API (Authorization: Bearer ou X-Api-Key). Un compte sans accès reçoit 404.
  • Ou une connexion au compte (OAuth 2.1, pour ChatGPT et l’application Claude, qui n’envoient pas de clé) : sans authentification, la réponse est un 401 dont l’en-tête WWW-Authenticate désigne /.well-known/oauth-protected-resource/api/v1/mcp. Enregistrement dynamique du client, code d’autorisation avec PKCE (S256), jeton d’accès d’une heure, renouvellement avec rotation. Le jeton ne vaut que pour cette adresse et applique la clé du compte (mêmes quotas, mêmes droits). La personne coupe la connexion dans Mon compte › API et webhooks.
  • Les annonces renvoyées comptent dans le quota de votre offre, comme par /annonces ; les calculs n’en consomment pas.
  • Débit : 10 requêtes par seconde et 60 par minute, 600 par jour et par clé (ou ceux de votre offre s’ils sont plus élevés) ; appels d’outils par jour et par compte selon l’offre (30 avec l’offre gratuite, 300 avec Secteur (12 € par mois), 1 000 avec Ville (30 € par mois), 2 000 avec Département (70,80 € par mois), 5 000 à partir de Région) ; 10 annonces au plus par recherche.
  • Un GET sur cette adresse répond 405, comme le prévoit le protocole pour un serveur qui n’ouvre pas de flux : ce n’est pas une erreur, et il n’est pas compté.