Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: CarHunt Pro Recherche API
description: 'API de sourcing automobile CarHunt Pro : recherche multi-critères dans
plus d’un million d''annonces actives agrégées depuis 6
sources. Un cycle complet d''actualisation du catalogue est lancé toutes les
4 heures. Chaque annonce dispose d''un score de
sous-cote et de médianes de marché. Les critères des watchlists sont évalués toutes les
10 minutes sur le catalogue déjà collecté, puis les
webhooks sont notifiés après détection. ### Authentification
Créez une clé API dans votre espace app.carhunt.fr
(Réglages → API, offre Business ou API Analyste attribuée), puis transmettez-la sur chaque requête :
Authorization: Bearer chp_live_…
### Quotas
Le plan Business inclut un quota mensuel de **véhicules**. L''offre API Analyste,
attribuée individuellement, inclut 1 000 **analyses** et 5 000 **véhicules**
par mois calendaire UTC. `GET /v1/usage` expose les deux soldes. Chaque véhicule renvoyé par la recherche ou livré par webhook est décompté
une fois. `POST /v1/estimate` et `GET /v1/listings/{listing_id}/analysis`
consomment chacun une analyse si une réponse exploitable est produite ; les
trois comparables inclus ne débitent pas de véhicules. Une cohorte de moins
de cinq annonces rend 422 sans débit. Les crédits d''analyse prépayés prennent
le relais après le quota mensuel et n''expirent pas. Pour proposer 3 à 5 alternatives, appelez `GET /v1/listings/search` avec
`page_size=3` à `5`, budget (`price_max`) et critères du client ; le nombre
de véhicules effectivement renvoyés est débité. Aucun classement personnalisé
ni garantie de disponibilité chez l''annonceur n''est implicite. Rate-limit : 60 req/min par clé. ### Synchronisation des annonces importées
`GET /v1/listings/changes` accepte jusqu''à 100 identifiants CarHunt dans `ids`. La recherche fournit `sync_until` pour initialiser le suivi sans nouvel import. Sans date, le flux peut réinitialiser un lot (véhicules décomptés). Transmettez
`updated_since` avec le `sync_until` du dernier lot traité : seuls les véhicules
réellement modifiés sont renvoyés et décomptés. Une réponse vide ne consomme rien. Chaque élément expose `last_modified_at`, `status` et la fiche `listing`. Les statuts `inactive` et `deleted` permettent de retirer les annonces du catalogue
client ; `deleted` a une fiche nulle et ne signifie pas vendu. `unknown_ids` ne
prouve pas un retrait. Revisites et recalculs de score ne déclenchent pas d''édition. Le suivi commence à `tracking_started_at` : une date antérieure rend 409 et exige
une réinitialisation sans date. Un lot dépassant le solde rend 429 sans décompte. Le paramètre `since` de la recherche continue à servir aux **nouvelles** annonces. ### Estimation et historique
`POST /v1/estimate` accepte marque, modèle, année, kilométrage et des filtres
facultatifs (énergie, boîte, motorisation, finition), sans annonce à résoudre. Les quartiles et la médiane portent sur les **prix demandés** d''annonces actives
comparables, pas sur les transactions. La cohorte est échantillonnée à 2 000
annonces récentes au maximum ; `sample_truncated` le signale. Les repères de
négociation sont statistiques : ils ne tiennent pas compte de l''état réel. L''historique et la volatilité de `GET /v1/listings/{listing_id}/analysis`
concernent **cette annonce**, pas l''ensemble du marché. L''historique peut être vide. `history_status` distingue `unavailable` (aucune observation sur la période demandée),
`no_change_recorded` (aucune variation enregistrée depuis la première observation)
et `price_changed`. `source_url` et `current_price` concernent l''annonce analysée. La collecte d''historique ayant débuté en juin 2026, une absence de variation
enregistrée ne permet pas de conclure sur les prix antérieurs. Exemple d''estimation (prix en euros, kilométrage en km) :
```json
{"make":"RENAULT","model":"CLIO","year":2020,"mileage":50000,"energy":"Essence","asking_price":13500}
```
### Couverture
Le catalogue cible les annonces automobiles d''occasion en France.'
contact:
name: CarHunt Pro
url: https://app.carhunt.fr/
email: contact@carhunt.fr
version: 1.0.0
servers:
- url: /public-api
- url: https://api-pro.carhunt.fr
description: Production
- url: https://api-pro-stage.carhunt.fr
description: Staging
tags:
- name: Recherche
paths:
/v1/listings/search:
get:
tags:
- Recherche
summary: Recherche d'annonces multi-critères
description: Recherche dans plus d’un million d'annonces actives agrégées. Au moins un critère de ciblage est requis. Utilisez `since` pour ne récupérer que les nouvelles annonces depuis votre dernier appel.
operationId: search_listings_v1_listings_search_get
parameters:
- name: min_deal_score
in: query
required: false
schema:
anyOf:
- type: number
maximum: 100.0
minimum: 0.0
- type: 'null'
description: Plancher de score de sous-cote
title: Min Deal Score
description: Plancher de score de sous-cote
- name: sort
in: query
required: false
schema:
type: string
pattern: ^(date|created_at|updated_at|price_asc|price_desc|deal_score)$
description: date/created_at = découverte la plus récente ; updated_at = re-vue la plus récente (fraîcheur) ; price_asc/desc ; deal_score = sous-cote
default: date
title: Sort
description: date/created_at = découverte la plus récente ; updated_at = re-vue la plus récente (fraîcheur) ; price_asc/desc ; deal_score = sous-cote
- name: make
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Marque (ex. VOLKSWAGEN)
title: Make
description: Marque (ex. VOLKSWAGEN)
- name: model
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Modèle commercial — les familles sont expansées (« GOLF » matche GOLF, GOLF SW…)
title: Model
description: Modèle commercial — les familles sont expansées (« GOLF » matche GOLF, GOLF SW…)
- name: region
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Région française (ex. ile-de-france, paca) — exclusif avec `departments`
title: Region
description: Région française (ex. ile-de-france, paca) — exclusif avec `departments`
- name: departments
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Départements séparés par des virgules (ex. 33,24,40)
title: Departments
description: Départements séparés par des virgules (ex. 33,24,40)
- name: department
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Département unique (équivalent à departments=XX)
title: Department
description: Département unique (équivalent à departments=XX)
- name: year_min
in: query
required: false
schema:
anyOf:
- type: integer
minimum: 1900
- type: 'null'
title: Year Min
- name: year_max
in: query
required: false
schema:
anyOf:
- type: integer
maximum: 2030
- type: 'null'
title: Year Max
- name: mileage_min
in: query
required: false
schema:
anyOf:
- type: integer
minimum: 0
- type: 'null'
title: Mileage Min
- name: mileage_max
in: query
required: false
schema:
anyOf:
- type: integer
minimum: 0
- type: 'null'
title: Mileage Max
- name: price_min
in: query
required: false
schema:
anyOf:
- type: integer
minimum: 0
- type: 'null'
title: Price Min
- name: price_max
in: query
required: false
schema:
anyOf:
- type: integer
minimum: 0
- type: 'null'
title: Price Max
- name: energy
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Énergies séparées par des virgules (Essence,Diesel,Électrique,Hybride…)
title: Energy
description: Énergies séparées par des virgules (Essence,Diesel,Électrique,Hybride…)
- name: gearbox
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Boîtes séparées par des virgules (Manuelle,Automatique)
title: Gearbox
description: Boîtes séparées par des virgules (Manuelle,Automatique)
- name: motorization
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Code moteur (ex. « 2.0 TDI ») — match souple
title: Motorization
description: Code moteur (ex. « 2.0 TDI ») — match souple
- name: finition
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Finition normalisée (ex. R-Line) — cf. champ `finition` des réponses
title: Finition
description: Finition normalisée (ex. R-Line) — cf. champ `finition` des réponses
- name: seller_type
in: query
required: false
schema:
anyOf:
- type: string
pattern: ^(pro|private)$
- type: 'null'
description: 'Vendeur professionnel ou particulier (couverture partielle : capturé depuis juillet 2026, proxy : Autosphere = 100 % pros)'
title: Seller Type
description: 'Vendeur professionnel ou particulier (couverture partielle : capturé depuis juillet 2026, proxy : Autosphere = 100 % pros)'
- name: since
in: query
required: false
schema:
anyOf:
- type: string
format: date-time
- type: 'null'
description: Ne renvoyer que les annonces découvertes après ce timestamp ISO (polling incrémental)
title: Since
description: Ne renvoyer que les annonces découvertes après ce timestamp ISO (polling incrémental)
- name: seen_within_days
in: query
required: false
schema:
anyOf:
- type: integer
maximum: 60
minimum: 1
- type: 'null'
description: 'Ne renvoyer que les annonces re-vues par nos scrapers dans les N derniers jours (filtre anti-annonces retirées ; recommandé : 7)'
title: Seen Within Days
description: 'Ne renvoyer que les annonces re-vues par nos scrapers dans les N derniers jours (filtre anti-annonces retirées ; recommandé : 7)'
- name: page
in: query
required: false
schema:
type: integer
minimum: 1
default: 1
title: Page
- name: page_size
in: query
required: false
schema:
type: integer
maximum: 50
minimum: 1
default: 25
title: Page Size
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/SearchResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/v1/listings/resolve:
get:
tags:
- Recherche
summary: Retrouver une annonce par URL ou identifiant source
description: 'Fournissez exclusivement `source_url` OU le couple `source` + `external_id`. Extraction d’identifiant pour les URLs Leboncoin (/ad/voitures/ID ou /voitures/ID.htm) et La Centrale (/auto-occasion-annonce-ID.html), avec ou sans www ; paramètres et fragment ignorés. Les autres URLs sont recherchées par correspondance exacte avec les liens stockés, avec extraction d’identifiant pour les formats L’Argus et ParuVendu reconnus. Sources acceptées par identifiant : leboncoin, lacentrale, largus, autosphere, paruvendu, leparking. La Centrale accepte la référence (W103472273) et son ID d''URL (87103472273). Recherche dans le catalogue et les sources secondaires des véhicules dédoublonnés, sans appel au site d''origine. Une absence du catalogue ne prouve pas une suppression chez la source. La fiche canonique est dans `listing`, le lien, prix et photo de la source recherchée dans `matched_source`. `/v1/listings/{id}/market` utilise le prix canonique de `listing`. Chaque succès consomme 1 véhicule, y compris en cas d''appels répétés ; les erreurs n''en consomment aucun.'
operationId: resolve_listing_v1_listings_resolve_get
parameters:
- name: source_url
in: query
required: false
schema:
anyOf:
- type: string
minLength: 1
maxLength: 2048
- type: 'null'
title: Source Url
- name: source
in: query
required: false
schema:
anyOf:
- type: string
minLength: 1
maxLength: 50
- type: 'null'
title: Source
- name: external_id
in: query
required: false
schema:
anyOf:
- type: string
minLength: 1
maxLength: 255
- type: 'null'
title: External Id
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ResolveResponse'
'401':
description: Clé API absente ou invalide
'402':
description: Abonnement requis ou essai expiré
'403':
description: Plan sans accès API
'404':
description: Annonce absente du catalogue
'409':
description: Identifiant associé à plusieurs véhicules
'410':
description: Annonce inactive
'422':
description: Paramètres ou format d'URL invalides
'429':
description: Quota ou limite de débit atteint
/v1/deals:
get:
tags:
- Recherche
summary: Flux d'annonces sous-cotées (deal-score)
description: 'Comme /v1/listings/search, mais borné aux bonnes affaires (deal-score ≥ 60
par défaut) et trié des plus sous-cotées aux moins sous-cotées.'
operationId: deals_v1_deals_get
parameters:
- name: min_deal_score
in: query
required: false
schema:
type: number
maximum: 100.0
minimum: 0.0
description: Plancher de score (60 = nettement sous la cote)
default: 60
title: Min Deal Score
description: Plancher de score (60 = nettement sous la cote)
- name: make
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Marque (ex. VOLKSWAGEN)
title: Make
description: Marque (ex. VOLKSWAGEN)
- name: model
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Modèle commercial — les familles sont expansées (« GOLF » matche GOLF, GOLF SW…)
title: Model
description: Modèle commercial — les familles sont expansées (« GOLF » matche GOLF, GOLF SW…)
- name: region
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Région française (ex. ile-de-france, paca) — exclusif avec `departments`
title: Region
description: Région française (ex. ile-de-france, paca) — exclusif avec `departments`
- name: departments
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Départements séparés par des virgules (ex. 33,24,40)
title: Departments
description: Départements séparés par des virgules (ex. 33,24,40)
- name: department
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Département unique (équivalent à departments=XX)
title: Department
description: Département unique (équivalent à departments=XX)
- name: year_min
in: query
required: false
schema:
anyOf:
- type: integer
minimum: 1900
- type: 'null'
title: Year Min
- name: year_max
in: query
required: false
schema:
anyOf:
- type: integer
maximum: 2030
- type: 'null'
title: Year Max
- name: mileage_min
in: query
required: false
schema:
anyOf:
- type: integer
minimum: 0
- type: 'null'
title: Mileage Min
- name: mileage_max
in: query
required: false
schema:
anyOf:
- type: integer
minimum: 0
- type: 'null'
title: Mileage Max
- name: price_min
in: query
required: false
schema:
anyOf:
- type: integer
minimum: 0
- type: 'null'
title: Price Min
- name: price_max
in: query
required: false
schema:
anyOf:
- type: integer
minimum: 0
- type: 'null'
title: Price Max
- name: energy
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Énergies séparées par des virgules (Essence,Diesel,Électrique,Hybride…)
title: Energy
description: Énergies séparées par des virgules (Essence,Diesel,Électrique,Hybride…)
- name: gearbox
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Boîtes séparées par des virgules (Manuelle,Automatique)
title: Gearbox
description: Boîtes séparées par des virgules (Manuelle,Automatique)
- name: motorization
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Code moteur (ex. « 2.0 TDI ») — match souple
title: Motorization
description: Code moteur (ex. « 2.0 TDI ») — match souple
- name: finition
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Finition normalisée (ex. R-Line) — cf. champ `finition` des réponses
title: Finition
description: Finition normalisée (ex. R-Line) — cf. champ `finition` des réponses
- name: seller_type
in: query
required: false
schema:
anyOf:
- type: string
pattern: ^(pro|private)$
- type: 'null'
description: 'Vendeur professionnel ou particulier (couverture partielle : capturé depuis juillet 2026, proxy : Autosphere = 100 % pros)'
title: Seller Type
description: 'Vendeur professionnel ou particulier (couverture partielle : capturé depuis juillet 2026, proxy : Autosphere = 100 % pros)'
- name: since
in: query
required: false
schema:
anyOf:
- type: string
format: date-time
- type: 'null'
description: Ne renvoyer que les annonces découvertes après ce timestamp ISO (polling incrémental)
title: Since
description: Ne renvoyer que les annonces découvertes après ce timestamp ISO (polling incrémental)
- name: seen_within_days
in: query
required: false
schema:
anyOf:
- type: integer
maximum: 60
minimum: 1
- type: 'null'
description: 'Ne renvoyer que les annonces re-vues par nos scrapers dans les N derniers jours (filtre anti-annonces retirées ; recommandé : 7)'
title: Seen Within Days
description: 'Ne renvoyer que les annonces re-vues par nos scrapers dans les N derniers jours (filtre anti-annonces retirées ; recommandé : 7)'
- name: page
in: query
required: false
schema:
type: integer
minimum: 1
default: 1
title: Page
- name: page_size
in: query
required: false
schema:
type: integer
maximum: 50
minimum: 1
default: 25
title: Page Size
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/SearchResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
components:
schemas:
SearchResponse:
properties:
listings:
items:
$ref: '#/components/schemas/ListingOut'
type: array
title: Listings
total:
type: integer
title: Total
description: Total de résultats, plafonné à 10000 (voir total_capped)
total_capped:
type: boolean
title: Total Capped
description: true si le total réel dépasse 10000 — affinez vos critères
default: false
page:
type: integer
title: Page
page_size:
type: integer
title: Page Size
sync_until:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Sync Until
description: Checkpoint UTC pris avant cette page ; utilisable comme updated_since pour synchroniser les identifiants retournés
type: object
required:
- listings
- total
- page
- page_size
title: SearchResponse
ValidationError:
properties:
loc:
items:
anyOf:
- type: string
- type: integer
type: array
title: Location
msg:
type: string
title: Message
type:
type: string
title: Error Type
type: object
required:
- loc
- msg
- type
title: ValidationError
HTTPValidationError:
properties:
detail:
items:
$ref: '#/components/schemas/ValidationError'
type: array
title: Detail
type: object
title: HTTPValidationError
ListingOut:
properties:
id:
type: string
title: Id
make:
anyOf:
- type: string
- type: 'null'
title: Make
model:
anyOf:
- type: string
- type: 'null'
title: Model
version:
anyOf:
- type: string
- type: 'null'
title: Version
description: Libellé de version brut de l'annonce
finition:
anyOf:
- type: string
- type: 'null'
title: Finition
description: Finition normalisée (ex. « R-Line ») — disponible sur ~2/3 des annonces
year:
anyOf:
- type: integer
- type: 'null'
title: Year
mileage:
anyOf:
- type: integer
- type: 'null'
title: Mileage
price:
anyOf:
- type: integer
- type: 'null'
title: Price
energy:
anyOf:
- type: string
- type: 'null'
title: Energy
gearbox:
anyOf:
- type: string
- type: 'null'
title: Gearbox
horsepower:
anyOf:
- type: integer
- type: 'null'
title: Horsepower
seller_type:
anyOf:
- type: string
- type: 'null'
title: Seller Type
description: '''pro'' | ''private'' | null (inconnu) — capturé depuis le 2026-07-17, couverture croissante au fil des re-scrapes'
department:
anyOf:
- type: string
- type: 'null'
title: Department
city:
anyOf:
- type: string
- type: 'null'
title: City
deal_score:
anyOf:
- type: number
- type: 'null'
title: Deal Score
description: Score de sous-cote 0-100 (50 = prix médian marché, plus haut = meilleure affaire)
photo_url:
anyOf:
- type: string
- type: 'null'
title: Photo Url
source:
anyOf:
- type: string
- type: 'null'
title: Source
description: Site annonceur d'origine
source_url:
anyOf:
- type: string
- type: 'null'
title: Source Url
description: Lien direct vers l'annonce d'origine
first_seen:
anyOf:
- type: string
format: date-time
- type: 'null'
title: First Seen
description: Découverte de l'annonce par CarHunt (base du paramètre `since`)
updated_at:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Updated At
type: object
required:
- id
title: ListingOut
description: Une annonce. `source_url` pointe vers l'annonce chez l'annonceur d'origine.
ResolveResponse:
properties:
listing:
allOf:
- $ref: '#/components/schemas/ListingOut'
description: Véhicule canonique dédoublonné ; sa source et son prix peuvent différer de la source recherchée
matched_source:
$ref: '#/components/schemas/MatchedSourceOut'
type: object
required:
- listing
- matched_source
title: ResolveResponse
MatchedSourceOut:
properties:
source:
type: string
title: Source
external_id:
type: string
title: External Id
description: Identifiant tel que stocké pour la source retrouvée
source_url:
type: string
title: Source Url
description: Lien stocké de la source recherchée, sans garantie de disponibilité chez l'éditeur
price:
anyOf:
- type: integer
- type: 'null'
title: Price
photo_url:
anyOf:
- type: string
- type: 'null'
title: Photo Url
updated_at:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Updated At
description: Dernière actualisation connue de cette source
type: object
required:
- source
- external_id
- source_url
title: MatchedSourceOut