openapi: 3.0.3 info: title: API Risqéo version: "1.4.1" description: | Génération d'États des Risques et Pollutions (ERP) depuis une application tierce : logiciel de diagnostic, logiciel de transaction, extranet d'agence ou d'étude notariale. Chaque rapport est conforme au modèle d'état des risques en vigueur (articles L125-5 et R125-23 à R125-27 du Code de l'environnement) et établi à partir des sources officielles Géorisques, actualisées chaque jour. L'état des nuisances sonores aériennes (ENSA) est inclus sans supplément lorsque la commune est couverte par un plan d'exposition au bruit. ## Principes - **Un ERP coûte un crédit**, débité à la création. Le forfait mensuel est consommé en priorité s'il est actif et non expiré. - **Le PDF est toujours à jour** des données officielles du jour. Un ERP acheté se retélécharge autant de fois que voulu pendant 18 mois, sans nouveau débit. - **Aucune visite du bien n'est nécessaire** : l'ERP est une recherche documentaire à partir d'une adresse et de références cadastrales. ## Démarrage 1. Créez une clé dans votre espace client, page « Mes clés d'API » (2 clés actives au maximum). La clé complète n'est affichée qu'une fois. 2. Vérifiez la clé : `GET /solde`. 3. Créez un ERP : `POST /erp`, en fournissant `code_insee`, l'adresse, et si possible la parcelle et les coordonnées. 4. Téléchargez : `GET /erp/{id}/pdf`. ## Bac à sable Une clé dont le jeton commence par `rsqtest_` au lieu de `rsq_` travaille en bac à sable : aucun crédit n'est débité, rien n'est écrit parmi vos ERP réels, et le PDF renvoyé est un rapport d'exemple sans rapport avec l'adresse demandée. **Les contrôles sont exactement ceux de la production** : un appel accepté en test est accepté en réel. Les ERP de test sont effacés au bout de 30 jours. Les réponses portent alors `bac_a_sable: true`, le solde vaut 9999 et `mode` vaut `bac_a_sable`. Créez ce type de clé dans votre espace client, en cochant « clé de test ». ## Ce que l'API ne fait pas encore Pas de webhook, pas de commande groupée, pas de renouvellement par API, pas de synthèse structurée des risques. Ces manques sont connus et priorisés. contact: name: Support Risqéo url: https://risqeo.fr/contact.html email: contact@risqeo.fr servers: - url: https://risqeo.fr/api/v1 description: Production — les crédits sont réellement débités tags: - name: Compte description: Solde de crédits et de forfait. - name: ERP description: Création, consultation et téléchargement des rapports. security: - CleBearer: [] - CleEntete: [] paths: /solde: get: tags: [Compte] summary: Solde du compte description: | Nombre d'ERP encore disponibles, et d'où ils viennent. `solde` est le chiffre à surveiller : c'est le total réellement utilisable. operationId: lireSolde responses: "200": description: Solde du compte rattaché à la clé. content: application/json: schema: type: object required: [success, mode, solde, credit_restant, forfait_mensuel_restant] properties: success: { type: boolean, example: true } mode: type: string enum: [credit, forfait, bac_a_sable] description: Ce qui sera débité au prochain ERP. bac_a_sable: type: boolean description: Présent et à `true` avec une clé `rsqtest_`. solde: type: integer description: ERP encore disponibles, toutes sources confondues. example: 42 credit_restant: type: integer description: Crédits achetés à l'unité ou en pack, sans limite de durée. example: 12 forfait_mensuel_restant: type: integer description: Crédits du forfait mensuel, remis à zéro chaque mois. example: 30 date_fin_validite: type: string nullable: true description: Fin de validité du forfait, au format AAAA-MM-JJ. example: "2026-10-31" "401": { $ref: "#/components/responses/Auth" } "405": { $ref: "#/components/responses/Methode" } "429": { $ref: "#/components/responses/Debit" } "500": { $ref: "#/components/responses/Interne" } /erp: post: tags: [ERP] summary: Créer un ERP description: | Crée le diagnostic et débite un crédit. Le PDF s'obtient ensuite par `GET /erp/{id}/pdf`. **Contrôles effectués avant tout débit** : présence de l'adresse, du code postal et de la ville ; `code_insee` présent, bien formé et existant réellement. Une commune inconnue est refusée en 422 : un ERP sans commune valide n'aurait aucun sens. **Répétition sans risque** : envoyez un en-tête `Idempotency-Key`. Rejouée, la même clé renvoie la réponse mémorisée, avec l'en-tête `Idempotency-Replayed: true`, sans second débit. operationId: creerErp parameters: - name: Idempotency-Key in: header required: false description: | 64 caractères au plus, parmi `A-Z a-z 0-9 . _ : -`. Conseillé sur toute création : en cas de coupure réseau, le rejeu ne facture pas deux fois. schema: { type: string, example: "dossier-4812-tentative-1" } requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/DemandeErp" } examples: complet: summary: Cas nominal, toutes les données utiles value: diagnostic: adresse: "12 rue de la Paix" code_postal: "75002" ville: "Paris" code_insee: "75102" latitude: "48.8687" longitude: "2.3312" prefixe_cadastre: "000" section_cadastre: "AB" numero_parcelle_cadastre: "0012" bailleur_vendeur: "SCI Exemple" acquereur_locataire: "M. Dupont" reference: "DOSSIER-4812" parcelles_sup: - section_cadastre: "AB" numero_parcelle_cadastre: "0013" minimal: summary: Strict minimum accepté value: diagnostic: adresse: "1 rue de la République" code_postal: "84000" ville: "Avignon" code_insee: "84007" responses: "201": description: ERP créé, crédit débité. headers: Idempotency-Replayed: description: Présent et à `true` quand la réponse est un rejeu. schema: { type: string, enum: ["true"] } content: application/json: schema: type: object required: [success, id, pdf_url, debite, solde_restant, mode_solde] properties: success: { type: boolean, example: true } id: type: string pattern: "^[a-f0-9]{32}$" description: Identifiant de l'ERP, à reprendre partout ailleurs. example: "148db122ee5b79052bcdcd75d1ecb9d9" pdf_url: { type: string, format: uri } debite: type: string enum: [credits, forfait, aucun] description: Ce qui a été décompté. `aucun` en bac à sable. solde_restant: { type: integer, example: 41 } mode_solde: { type: string, enum: [credit, forfait] } bac_a_sable: type: boolean description: Présent et à `true` avec une clé `rsqtest_`. avertissements: type: array description: | Présent seulement si le rapport sera appauvri. Le crédit est débité et le document produit : ces messages disent ce qui manque pour l'appel suivant. items: { type: string } example: - "Latitude et longitude absentes : les analyses au point sont désactivées (PPR au droit du bien, sites pollués dans un rayon de 500 m). Le rapport reste communal." "400": { $ref: "#/components/responses/Requete" } "401": { $ref: "#/components/responses/Auth" } "402": description: Ni crédit ni forfait disponible. Rechargement sur le site. content: application/json: schema: { $ref: "#/components/schemas/Erreur" } example: success: false error: { code: solde_insuffisant, message: "Crédits insuffisants." } "409": description: Une requête portant la même clé d'idempotence est en cours. content: application/json: schema: { $ref: "#/components/schemas/Erreur" } "422": description: Données refusées, aucun débit. content: application/json: schema: { $ref: "#/components/schemas/Erreur" } examples: commune: value: success: false error: code: commune_invalide message: "Code INSEE 99999 inconnu : aucune commune française ne porte ce code." champs: value: success: false error: code: donnees_invalides message: "Adresse / code postal / ville obligatoires." "429": { $ref: "#/components/responses/Debit" } "500": { $ref: "#/components/responses/Interne" } get: tags: [ERP] summary: Lister les ERP du compte description: Du plus récent au plus ancien. operationId: listerErp parameters: - name: limit in: query schema: { type: integer, default: 25, minimum: 1, maximum: 100 } description: Valeur hors bornes ramenée dans les bornes, sans erreur. - name: offset in: query schema: { type: integer, default: 0, minimum: 0 } responses: "200": description: Page de résultats. content: application/json: schema: type: object properties: success: { type: boolean } erps: type: array items: { $ref: "#/components/schemas/ErpResume" } limit: { type: integer } offset: { type: integer } count: type: integer description: Nombre d'éléments dans cette page, pas le total du compte. "401": { $ref: "#/components/responses/Auth" } "429": { $ref: "#/components/responses/Debit" } /erp/{id}: get: tags: [ERP] summary: Détail d'un ERP operationId: lireErp parameters: [{ $ref: "#/components/parameters/IdErp" }] responses: "200": description: Détail de l'ERP. content: application/json: schema: type: object properties: success: { type: boolean } erp: { $ref: "#/components/schemas/ErpDetail" } "401": { $ref: "#/components/responses/Auth" } "404": { $ref: "#/components/responses/Introuvable" } "405": { $ref: "#/components/responses/Methode" } "429": { $ref: "#/components/responses/Debit" } /erp/{id}/pdf: get: tags: [ERP] summary: Télécharger le PDF description: | Comptez quelques secondes, et jusqu'à une dizaine sur une commune très exposée. Le document sort toujours avec les données du jour. Aucun crédit n'est débité ici, quel que soit le nombre d'appels. Deux limites s'appliquent, par clé : 5 appels par minute, et 2 téléchargements simultanés. Les appels en cours comptent dans les deux : n'enchaînez pas les téléchargements en parallèle, attendez la réponse. operationId: telechargerPdf parameters: [{ $ref: "#/components/parameters/IdErp" }] responses: "200": description: Le PDF. content: application/pdf: schema: { type: string, format: binary } "400": { $ref: "#/components/responses/Requete" } "401": { $ref: "#/components/responses/Auth" } "404": { $ref: "#/components/responses/Introuvable" } "405": { $ref: "#/components/responses/Methode" } "429": { $ref: "#/components/responses/Debit" } "500": description: | Le rapport n'a pas pu être produit (`generation_echouee`), ou erreur de notre côté (`erreur_interne`). Le crédit reste débité : réessayez, le document se régénère. Si l'échec persiste, le support régularise. content: application/json: schema: { $ref: "#/components/schemas/Erreur" } components: securitySchemes: CleBearer: type: http scheme: bearer bearerFormat: rsq_<40 hexadécimaux> description: | `Authorization: Bearer rsq_…`. Forme recommandée. Sur un hébergement qui retire cet en-tête, utilisez `X-Api-Key`. Un jeton commençant par `rsqtest_` est une clé de bac à sable : mêmes contrôles, aucun débit, rapport d'exemple. CleEntete: type: apiKey in: header name: X-Api-Key description: Équivalent à l'en-tête `Authorization`. parameters: IdErp: name: id in: path required: true description: Identifiant renvoyé à la création, 32 caractères hexadécimaux. schema: { type: string, pattern: "^[a-f0-9]{32}$" } schemas: DemandeErp: type: object required: [diagnostic] properties: diagnostic: type: object required: [adresse, code_postal, ville, code_insee] properties: adresse: type: string description: Numéro et voie, sans la commune. example: "12 rue de la Paix" code_postal: { type: string, example: "75002" } ville: { type: string, example: "Paris" } code_insee: type: string description: | **Obligatoire et vérifié.** C'est lui qui détermine la commune du rapport : plans de prévention, arrêtés de catastrophe naturelle, radon, sismicité. Cinq caractères, `2A`/`2B` admis pour la Corse. Un code inconnu est refusé avant tout débit. Les codes globaux de Paris (75056), Lyon (69123) et Marseille (13055) sont refusés eux aussi : utilisez le code de l'arrondissement, 75101-75120, 69381-69389 ou 13201-13216. pattern: "^(\\d{5}|2[AB]\\d{3})$" example: "75102" latitude: type: string description: | Sans coordonnées, les analyses au droit du bien sont désactivées : plans de prévention au point et sites pollués dans un rayon de 500 m. Le rapport reste communal, ce qui est moins précis. Un avertissement le signale dans la réponse. example: "48.8687" longitude: { type: string, example: "2.3312" } prefixe_cadastre: type: string description: Trois chiffres. Utile sur les communes fusionnées. example: "000" section_cadastre: { type: string, example: "AB" } numero_parcelle_cadastre: { type: string, example: "0012" } parcel_id: type: string description: Identifiant cadastral unique à 14 caractères, si vous l'avez. example: "751020000AB0012" bailleur_vendeur: { type: string, example: "SCI Exemple" } acquereur_locataire: { type: string } reference: type: string description: Votre référence de dossier, reprise telle quelle dans le rapport. example: "DOSSIER-4812" parcelles_sup: type: array maxItems: 20 description: | Parcelles additionnelles du même bien, toutes sur la même commune. Chacune est analysée, et le rapport les mentionne toutes. items: type: object properties: prefixe_cadastre: { type: string } section_cadastre: { type: string } numero_parcelle_cadastre: { type: string } ErpResume: type: object properties: id: { type: string, pattern: "^[a-f0-9]{32}$" } adresse: { type: string } code_postal: { type: string } ville: { type: string } code_insee: { type: string } reference: { type: string, nullable: true } date_commande: { type: string, nullable: true } date_ernmt: { type: string, nullable: true } pdf_url: { type: string, format: uri } ErpDetail: allOf: - $ref: "#/components/schemas/ErpResume" - type: object properties: latitude: { type: string, nullable: true } longitude: { type: string, nullable: true } cadastre: type: object properties: prefixe: { type: string, nullable: true } section: { type: string, nullable: true } numero: { type: string, nullable: true } idu: { type: string, nullable: true } parcelles_sup: type: array items: type: object properties: rang: { type: integer } idu: { type: string, nullable: true } prefixe_cadastre: { type: string, nullable: true } section_cadastre: { type: string, nullable: true } numero_parcelle_cadastre: { type: string, nullable: true } code_insee: { type: string, nullable: true } bailleur_vendeur: { type: string, nullable: true } acquereur_locataire: { type: string, nullable: true } conclusions: type: string nullable: true description: | Synthèse textuelle des risques. Elle est renseignée après le premier téléchargement du PDF : juste après la création, elle est donc nulle. Erreur: type: object required: [success, error] properties: success: { type: boolean, enum: [false] } error: type: object required: [code, message] properties: code: type: string description: Code stable, à tester dans votre programme. enum: - auth_manquante - auth_invalide - trop_d_essais - json_invalide - idempotence_invalide - idempotence_en_cours - parametre_manquant - commune_invalide - donnees_invalides - solde_insuffisant - introuvable - route_inconnue - methode_invalide - limite_debit - generation_echouee - erreur_interne message: type: string description: Phrase lisible par un humain, susceptible d'évoluer. responses: Auth: description: Clé absente, inconnue ou révoquée. content: application/json: schema: { $ref: "#/components/schemas/Erreur" } Requete: description: Requête mal formée. content: application/json: schema: { $ref: "#/components/schemas/Erreur" } Introuvable: description: | Aucun ERP de ce compte sous cet identifiant. Le cloisonnement est strict : l'ERP d'un autre compte répond 404, jamais 403. content: application/json: schema: { $ref: "#/components/schemas/Erreur" } Methode: description: Verbe HTTP non accepté sur cette adresse. content: application/json: schema: { $ref: "#/components/schemas/Erreur" } Debit: description: | Quota dépassé. Par clé et par minute : 30 créations, 120 lectures, 5 téléchargements de PDF, 60 consultations de solde. Le téléchargement est en outre plafonné à 2 téléchargements simultanés par clé, et les appels en cours comptent dans les deux plafonds. Des refus d'authentification répétés depuis une même adresse sont freinés de la même façon. headers: Retry-After: description: Secondes à attendre. schema: { type: integer } content: application/json: schema: { $ref: "#/components/schemas/Erreur" } Interne: description: | Défaillance de notre côté, toujours décrite en JSON. content: application/json: schema: { $ref: "#/components/schemas/Erreur" }