Documentation API

API ERPplus

Generez des Etats des Risques et Pollutions par API. Authentifiez-vous avec votre cle API pour acceder a tous les endpoints.

Pour explorer la spec OpenAPI 3.0 de manière interactive (Try it out, génération SDK), voir la spec interactive Swagger UI. Le YAML brut est disponible sur /api-docs-ui/openapi.yaml pour les générateurs de SDK.

Authentification

Ajoutez votre cle API dans le header Authorization de chaque requete :

Authorization: Bearer erpplus_votre_cle_api
Votre cle API est generee par l'administrateur depuis le panneau d'administration. Elle n'est affichee qu'une seule fois lors de sa creation.

Versioning

L'API B2B ERPplus est versionnée. L'URL canonique est /api/v1/erp*. Cette URL est figée pour la durée de votre contrat et garantit la stabilité de votre intégration.

Tout breaking change passera par un nouveau préfixe /api/v2/ opt-in. Lors de l'annonce de la v2, l'API v1 recevra les headers Deprecation et Sunset (RFC 8594) avec un préavis minimum de 6 mois avant retrait. Vous aurez le temps de planifier votre migration sereinement.

Idempotency

Les requêtes POST /api/v1/erp et POST /api/v1/erp/batch acceptent un header Idempotency-Key qui permet de rejouer une requête sans risque de double création (panne réseau, retry client, redémarrage de worker).

Idempotency-Key: 5f7e8b9c-3a2d-4f1e-9b8a-c1d2e3f4a5b6

Format : 8 à 255 caractères dans [a-zA-Z0-9_-]. Recommandation : UUID v4 généré côté client. Scope per-user (deux clients distincts peuvent réutiliser la même clé sans collision).

Comportement :

  • Première requête : la réponse est snapshottée (status + body) et stockée pendant 24h.
  • Replay même clé même body : retour exact du snapshot initial avec le header Idempotency-Replay: true. Le pipeline n'est pas relancé.
  • Replay même clé body différent : retour 422 idempotency_key_conflict. Pour soumettre un nouveau body, générer une nouvelle clé.
  • Header invalide ou trop court : retour 400 invalid_idempotency_key.
  • Header absent : comportement standard, aucune persistance.
  • Erreur 5xx : la réponse n'est pas snapshottée, un retry avec la même clé est accepté (erreur transitoire serveur).

Rétention des Idempotency-Key

Les Idempotency-Key sont conservées 24h après leur création (aligné Stripe et standards de marché). Au-delà, la clé est purgée du serveur et toute requête utilisant la même clé sera traitée comme une nouvelle commande, ce qui consommera de nouveaux crédits.

Si vous rejouez une commande plus de 12h après le premier essai, régénérez la clé côté client pour éviter tout double-débit involontaire.

Les requêtes parallèles avec la même Idempotency-Key ne sont pas garanties (race condition possible côté serveur). Sérialisez les retries côté client avec un délai minimal de quelques secondes en cas d'échec réseau.

Exemple :

# Première requête
curl -X POST https://votre-domaine/api/v1/erp \
  -H "Authorization: Bearer erpplus_votre_cle_api" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5f7e8b9c-3a2d-4f1e-9b8a-c1d2e3f4a5b6" \
  -d '{"address": "21 Avenue Jean Moulin 75014 Paris", "vendeur": "SCI Dupont"}'

# Retry réseau après panne, même clé même body : retour du snapshot initial
curl -X POST https://votre-domaine/api/v1/erp \
  -H "Authorization: Bearer erpplus_votre_cle_api" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5f7e8b9c-3a2d-4f1e-9b8a-c1d2e3f4a5b6" \
  -d '{"address": "21 Avenue Jean Moulin 75014 Paris", "vendeur": "SCI Dupont"}'
# Headers de réponse : Idempotency-Replay: true

Endpoints

URL canonique : /api/v1/erp* (cf. section Versioning).

POST /api/v1/erp Lancer une generation ERP
{
  "address": "21 Avenue Jean Moulin 75014 Paris",
  "parcelle": "CH42",         // optionnel
  "vendeur": "SCI Dupont",    // obligatoire (max 200 chars)
  "acquereur": "M Martin",    // optionnel (max 200 chars)
  "transaction_type": "vente" // obligatoire : "vente" ou "location"
}

Retourne { "job_id": "uuid", "status": "queued" }. Erreurs : 400 vendeur_required, vendeur_too_long, transaction_type_required, transaction_type_invalid.

La nature de la transaction est exigee depuis le lot P-10, et c’est une rupture de contrat : le champ etait optionnel et son absence stockait vente. Une commande qui en manque est refusee en 400, aucun credit debite. Un document dont le type n’est pas connu ecrit desormais Vendeur ou Bailleur et Acquereur ou Locataire, au lieu d’affirmer une vente.

GET /api/v1/erp/{id}/status Statut d'un job

Retourne { "job_id", "status", "address", "parcelle", "createdAt", "completedAt", "error", "expiresAt", "daysRemaining", "expiryStatus", "degraded", "unverifiedSections", "freeRenewalUntil" }

Statuts possibles : queued (en file), processing (en cours), completed (terminé), failed (échec).

degraded est vrai quand le document a été livré avec au moins une rubrique du formulaire non vérifiée, une source publique étant restée muette malgré les reprises ; unverifiedSections en porte les codes, dans l’ordre où le document présente ces rubriques (ppr_naturels_liste, ppr_naturels_exposition, ppr_miniers_liste, ppr_miniers_exposition, ppr_technologiques_liste, ppr_technologiques_exposition, sismicite, radon, rtc, sis, old, peb), vide sinon ; freeRenewalUntil est la date jusqu’à laquelle POST /api/v1/erp/{id}/renew est gratuit pour ce document (fin de sa validité), null quand le renouvellement se débite ou n’a pas d’objet. Les trois champs figurent aussi sur chaque élément de GET /api/v1/erp/list. Un client qui les ignore ne voit aucun changement.

GET /api/v1/erp/{id}/download Telecharger le PDF

Retourne le fichier PDF (Content-Type: application/pdf). Disponible uniquement quand status = "completed".

POST /api/v1/erp/batch Generation multi-parcellaire
{
  "commune": "Etampes",
  "insee": "91223",
  "parcelles": ["BL370", "BL312"],
  "vendeur": "SCI Dupont",    // obligatoire (max 200 chars)
  "acquereur": "M Martin",    // optionnel (max 200 chars)
  "transaction_type": "vente" // obligatoire : "vente" ou "location", partage par tout le lot
}

Retourne { "batch_id": "uuid", "jobs": [{ "job_id", "parcelle", "status" }, ...], "credits" }. Maximum 20 parcelles par batch. Le vendeur s'applique a tous les jobs du batch.

GET /api/v1/erp/batch/{batch_id}/status Statut d'un batch

Retourne { "batch_id", "total", "completed", "failed", "processing", "queued", "status", "jobs": [{ "job_id", ... }] }. Les compteurs completed, failed, processing, queued partitionnent les jobs du batch. Le status agrégé prend l'une des valeurs processing, completed, failed.

GET /api/v1/erp/list Historique des generations (pagine)

Retourne l'historique des jobs de l'utilisateur, au format pagine uniforme. Voir section Pagination pour le format de reponse, les query params et les bornes.

DELETE /api/v1/erp/{id} Supprimer un job

Supprime le job et son PDF. Retourne 409 si le job est en cours.

GET /api/auth/me Informations du compte

Retourne les informations de l'utilisateur, dont le solde de credits.

Pagination

Les endpoints de listing renvoient un format paginé uniforme. Query params ?limit=N&offset=M, valeurs par défaut limit=500, plafond limit=500. Une valeur non entière, négative ou supérieure au plafond renvoie une erreur 400 invalid_pagination.

Endpoints concernés :

  • GET /api/v1/erp/list
  • GET /api/auth/orders
  • GET /api/account/invoices
  • GET /api/admin/users
  • GET /api/admin/users/{id}/jobs

Format de réponse :

{
  "data": [ /* tableau d'éléments */ ],
  "pagination": {
    "total": 1234,
    "limit": 500,
    "offset": 0,
    "has_more": true
  }
}

Le header X-Total-Count est également émis et reflète pagination.total. Pour itérer sur toute la collection, incrémenter offset de limit tant que has_more est true.

Erreur 400 :

{
  "error": "invalid_pagination",
  "message": "limit must not exceed 500",
  "field": "limit"
}
L'endpoint GET /api/admin/users/{id}/jobs retourne le format étendu {user, data, pagination} qui combine la fiche utilisateur et la pagination des jobs.

Rate limiting

Les routes /api/v1/erp* sont protégées par 3 fenêtres en cascade par rôle. Le compteur le plus bas qui dépasse renvoie 429.

RôleSoft (par sec)Hard (par min)Daily
api_client10100100 000
diagnostiqueur510050 000
agence510050 000
particulier1301 000
adminexempt sur les 3 fenêtres

Headers émis sur toutes les requêtes /api/v1/erp* :

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1730000060

X-RateLimit-Limit et X-RateLimit-Remaining reflètent la fenêtre hard (par minute). X-RateLimit-Reset est un timestamp Unix indiquant le reset de cette fenêtre.

Réponse 429 sur dépassement :

HTTP/1.1 429 Too Many Requests
Retry-After: 42

{
  "error": "rate_limit_exceeded",
  "message": "Rate limit exceeded for window soft",
  "retry_after_seconds": 42,
  "limit_type": "soft"
}

Un compte formateur reçoit aussi un 429 quand son quota mensuel de générations est atteint (POST /api/v1/erp et renouvellement). Les deux cas se distinguent par le corps : le quota répond "code": "formation_quota_exhausted" avec un objet quota (restant, plafond, periode) et sans en-tête Retry-After ; il se rouvre au premier jour du mois suivant. Le limiteur répond "error": "rate_limit_exceeded" avec retry_after_seconds et limit_type.

Bonnes pratiques côté client : utiliser un backoff exponentiel respectant Retry-After. Délais recommandés : 1s, 2s, 4s, 8s, 16s, plafonné à 60s. Inutile de réessayer plus tôt que retry_after_seconds, le compteur ne baisse pas avant ce délai.

Exports CSV admin

Les routes GET /api/admin/export/{users,orders,jobs} sont limitées indépendamment à 10 requêtes / minute et 100 requêtes / jour. Cette limite n'est pas exemptée pour les administrateurs (protection contre compte compromis ou script qui boucle).

Export CSV compte client

La route GET /api/account/export/jobs.csv est limitée à 5 requêtes / minute et 30 requêtes / jour par utilisateur. Quota suffisant pour un usage manuel ou une intégration CSV ponctuelle.

Dashboard consommation client

Deux endpoints sont exposés aux comptes api_client, diagnostiqueur, agence et admin. Les comptes particulier reçoivent 403.

GET /api/account/usage-history

Historique de consommation sur les 12 mois calendaires glissants Europe/Paris. Mois courant flaggé is_partial: true. Jobs sandbox exclus.

{
  "period": { "from": "2025-06-01", "to": "2026-05-11", "timezone": "Europe/Paris" },
  "months": [
    { "month": "2025-06", "jobs_completed": 0,  "jobs_failed": 0, "is_partial": false },
    ...
    { "month": "2026-05", "jobs_completed": 12, "jobs_failed": 1, "is_partial": true  }
  ],
  "totals": { "jobs_completed": 142, "jobs_failed": 3 },
  "scope":  { "includes_sandbox": false, "includes_admin_jobs": false }
}
GET /api/account/export/jobs.csv

Export CSV (UTF-8 BOM, séparateur ;, 17 colonnes) des jobs du compte. Période max 36 mois, défaut 12 derniers mois glissants. Rate-limit dédié 5 req/min, 30 req/jour.

Paramètres optionnels :

  • since (YYYY-MM-DD) borne basse incluse
  • until (YYYY-MM-DD) borne haute incluse
  • status parmi completed, failed, processing, queued
  • include_sandbox booléen (true, false, 1, 0), défaut false

Colonnes (dans l'ordre) :

  • job_id : UUID v4 du job
  • batch_id : UUID du batch parent (vide si job unitaire)
  • order_number : numéro séquentiel global, commun à tous les comptes (référence client, sans portée comptable)
  • created_at_paris, completed_at_paris : YYYY-MM-DD HH:mm:ss Europe/Paris
  • duration_ms : durée de pipeline en millisecondes
  • status : statut public (completed, failed, processing, queued)
  • address : adresse postale saisie
  • parcelle : saisie utilisateur brute
  • parcelle_canonical : forme canonique commune-prefix-section-numero
  • parcelles_sup : parcelles supplémentaires pipe-séparées (ex BA12|BA13)
  • insee : code INSEE 5 chiffres
  • transaction_type : vente, location, ou vide pour les travaux anterieurs au lot P-10 dont le client n’a jamais exprime le type
  • is_sandbox : 0 ou 1
  • is_formation : 0 ou 1 (job pédagogique, jamais un document commercial)
  • error : message d'erreur tronqué à 200 caractères (uniquement si status=failed)
  • rubriques_non_verifiees : codes des rubriques du formulaire non vérifiées à la livraison, séparés par |, dans l’ordre de unverifiedSections de l’interface JSON (ex rtc|sis) ; vide pour un document non dégradé ou non achevé
# Historique consommation JSON
curl -H "Authorization: Bearer $API_KEY" \
  https://votre-domaine/api/account/usage-history

# Export CSV 12 derniers mois (défaut)
curl -H "Authorization: Bearer $API_KEY" \
  https://votre-domaine/api/account/export/jobs.csv -o jobs.csv

# Export CSV période et statut custom
curl -H "Authorization: Bearer $API_KEY" \
  "https://votre-domaine/api/account/export/jobs.csv?since=2025-01-01&until=2025-12-31&status=completed" \
  -o jobs_2025.csv

Exemples de requête

# Lancer une generation (URL canonique /api/v1/)
curl -X POST https://votre-domaine/api/v1/erp \
  -H "Authorization: Bearer erpplus_votre_cle_api" \
  -H "Content-Type: application/json" \
  -d '{"address": "21 Avenue Jean Moulin 75014 Paris", "vendeur": "SCI Dupont"}'

# Verifier le statut
curl https://votre-domaine/api/v1/erp/{id}/status \
  -H "Authorization: Bearer erpplus_votre_cle_api"

# Telecharger le PDF
curl -o erp.pdf https://votre-domaine/api/v1/erp/{id}/download \
  -H "Authorization: Bearer erpplus_votre_cle_api"

# Consulter les credits
curl https://votre-domaine/api/auth/me \
  -H "Authorization: Bearer erpplus_votre_cle_api"

Credits

Chaque generation ERP consomme 1 credit. Chaque parcelle d'un batch consomme 1 credit.

Consultez votre solde via GET /api/auth/me (champ credits).

Contactez l'administrateur pour recharger vos credits ou obtenir votre cle API.