{"openapi":"3.0.3","info":{"title":"legifrance.dev API","version":"1.0.0","description":"API REST exposant le droit français EN VIGUEUR (Code général des impôts d'abord) en JSON, avec versioning par article et requêtes « en vigueur à une date » (asOf). Source : Légifrance — Etalab 2.0. Service indépendant, non affilié à la DILA."},"servers":[{"url":"https://api.legifrance.dev","description":"Production"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key"}},"responses":{"RateLimited":{"description":"Trop de requêtes par minute (rate-limit dépassé)","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Secondes avant retry"},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Epoch seconds du prochain reset"}}},"QuotaExceeded":{"description":"Quota mensuel atteint (upgrade requis)"},"Unauthorized":{"description":"En-tête X-API-Key manquant ou clé invalide"},"NotFound":{"description":"Ressource introuvable"},"InvalidRequest":{"description":"Paramètre manquant ou invalide"}}},"paths":{"/v1/article/{num}":{"get":{"summary":"Article de loi à une date (asOf)","description":"Récupère l'unique version de l'article (code + numéro) EN VIGUEUR à la date `asOf` (défaut : aujourd'hui). C'est la valeur ajoutée de legifrance.dev : la bonne version à la bonne date.","parameters":[{"name":"num","in":"path","required":true,"schema":{"type":"string"},"description":"Numéro d'article (ex : 777, '150-0 B')"},{"name":"code","in":"query","schema":{"type":"string","default":"CGI"},"description":"Code (défaut : CGI)"},{"name":"asOf","in":"query","schema":{"type":"string","format":"date"},"description":"Date YYYY-MM-DD ; défaut : aujourd'hui"}],"responses":{"200":{"description":"OK — {legiarti_id, code, num, etat, date_debut, date_fin, asOf, version_label, plan_classement, texte, texte_html, url, source}"},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/article":{"get":{"summary":"Recherche plein-texte (FTS5) sur les articles en vigueur","description":"Recherche plein-texte stemmée français, restreinte aux versions en vigueur à la date `asOf` (défaut : aujourd'hui). Retourne extrait avec balises <mark> et score BM25.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":200},"description":"Requête FTS5"},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0,"maximum":10000}},{"name":"code","in":"query","schema":{"type":"string","example":"CGI"},"description":"Filtre par code"},{"name":"asOf","in":"query","schema":{"type":"string","format":"date"},"description":"Versions en vigueur à cette date"}],"responses":{"200":{"description":"OK — {total, limit, offset, asOf, results[], source}"},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/QuotaExceeded"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/article/semantic":{"post":{"summary":"Recherche sémantique (embeddings BGE-M3 + Vectorize)","description":"Recherche par sens (vecteurs cosinus 1024 dims), restreinte aux versions en vigueur à la date `asOf` (post-filtre). À utiliser pour les questions en langage naturel. Réservé aux tiers avec accès sémantique.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"type":"string","minLength":2,"maxLength":500},"topK":{"type":"integer","default":5,"maximum":20},"code":{"type":"string","example":"CGI"},"asOf":{"type":"string","format":"date"}}}}}},"responses":{"200":{"description":"OK — {asOf, results: [{legiarti_id, code, num, etat, date_debut, date_fin, url, score}], source}"},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/QuotaExceeded"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/search":{"post":{"summary":"Recherche FÉDÉRÉE — loi + doctrine fiscale (une seule requête)","description":"Recherche sémantique UNIQUE sur tous les corpus : articles de loi (Légifrance, versionnés asOf) + doctrine fiscale (BOFiP). La question est embeddée une seule fois (BGE-M3), chaque corpus est interrogé en parallèle sur son index propriétaire, les classements sont fusionnés par rang (RRF) puis re-classés par un reranker (échelle commune). Filtres TYPÉS PAR SOURCE : `asOf` ne s'applique qu'aux articles. Profils : `ai` (compact, top-K pur) / `expert` (chaque corpus pertinent représenté). Réservé aux tiers avec accès sémantique ; compté au quota sémantique.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"type":"string","minLength":2,"maxLength":500,"example":"abattement donation entre parents et enfants"},"topK":{"type":"integer","default":10,"minimum":1,"maximum":20},"profile":{"type":"string","enum":["ai","expert"],"default":"ai"},"sources":{"type":"array","items":{"type":"string","enum":["articles","doctrine_fiscale","jurisprudence_administrative","jurisprudence_judiciaire"]},"description":"Corpus interrogés (défaut : tous)"},"filters":{"type":"object","properties":{"articles":{"type":"object","properties":{"asOf":{"type":"string","format":"date","description":"Versions en vigueur à cette date (articles uniquement)"},"code":{"type":"string","example":"CGI"}}},"doctrine_fiscale":{"type":"object","properties":{"domaine":{"type":"string","example":"IS"}}},"jurisprudence_administrative":{"type":"object","properties":{"juridiction":{"type":"string","example":"CONSEIL_ETAT","description":"Clé machine (CONSEIL_ETAT, COURS_APPEL…)"},"dateMin":{"type":"string","format":"date"},"dateMax":{"type":"string","format":"date"}}},"jurisprudence_judiciaire":{"type":"object","properties":{"juridiction":{"type":"string","example":"COUR_DE_CASSATION"},"dateMin":{"type":"string","format":"date"},"dateMax":{"type":"string","format":"date"}}}}},"rerank":{"type":"boolean","description":"Forcer/couper le reranker (défaut : config serveur)"}}}}}},"responses":{"200":{"description":"OK — {profile, asOf, ranking: 'reranker'|'rrf', results: [{type: 'article'|'doctrine_fiscale'|'decision', score, semantic_score, article?|boi?|decision?, source}], sources: {articles: {status, candidates}, doctrine_fiscale: {…}, jurisprudence_administrative: {…}}, source}"},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/QuotaExceeded"},"403":{"description":"Tier sans accès sémantique (upgrade requis)"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Tous les moteurs fédérés momentanément indisponibles"}}}},"/v1/decision/{id}":{"get":{"summary":"Décision de justice par identifiant (+ textes appliqués + citations)","description":"Décision (CETATEXT…/JURITEXT…) avec ses métadonnées d'autorité (juridiction, formation, niveau de publication — A = recueil Lebon), son texte intégral, les TEXTES APPLIQUÉS extraits et résolus vers les articles (`textes_appliques`, resolution: cid | code_num | unresolved — jamais inventée) et son réseau de citations (`citations`, socle du citator).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Identifiant (ex : CETATEXT000041569386)"}],"responses":{"200":{"description":"OK — {decision, textes_appliques[], citator: {status: 'abandoned'|'contradicted'|'followed'|'cited'|'standalone', authority: {score, publication, publication_label, formation, juridiction_key}, cited_by[], coverage_note}, citations[], source}. Règle : un signal négatif n'est JAMAIS masqué — il est renvoyé avec son drapeau."},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/article/{num}/decisions":{"get":{"summary":"Décisions appliquant un article (pépite article↔jurisprudence)","description":"Les décisions de justice qui APPLIQUENT cet article (références extraites des textes/sommaires et résolues par cid — jamais inventées). Chaque décision porte son signal d'autorité (publication Lebon, formation, juridiction). Tri par date (défaut) ou par autorité.","parameters":[{"name":"num","in":"path","required":true,"schema":{"type":"string"},"description":"Numéro d'article (ex : L64, 150-0 B)"},{"name":"code","in":"query","schema":{"type":"string","default":"CGI"}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0,"maximum":10000}},{"name":"sort","in":"query","schema":{"type":"string","enum":["date","authority"],"default":"date"}}],"responses":{"200":{"description":"OK — {code, num, total, limit, offset, sort, decisions: [{decision_id, juridiction, …, authority, ref_count}], source}"},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/verify/citation":{"post":{"summary":"Vérification de citation (anti-hallucination)","description":"Pour une référence (article ou décision), répond : EXISTE / N'EXISTE PAS + état de validité. Article : version applicable à `asOf`, drapeaux (etat_abroge, no_version_at_date, unknown_reference) — un signal négatif n'est JAMAIS masqué. Décision : correspondances par ECLI ou numéro(+date), chacune avec son statut citator ; une ambiguïté n'est jamais tranchée au hasard (tous les candidats sont renvoyés).","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["type","code","num"],"properties":{"type":{"type":"string","enum":["article"]},"code":{"type":"string","example":"CGI"},"num":{"type":"string","example":"777"},"asOf":{"type":"string","format":"date"}}},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["decision"]},"ecli":{"type":"string"},"numero":{"type":"string","example":"421444"},"date":{"type":"string","format":"date"}}}]}}}},"responses":{"200":{"description":"OK — article : {exists, en_vigueur, version, flags[], coverage_note} ; décision : {exists, ambiguous, matches[{…, citator}], coverage_note}"},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/analytics/decisions":{"get":{"summary":"Analytics jurisprudentielle (conforme art. 33)","description":"Agrégats par juridiction, par mois ou par article appliqué (top contentieux). JAMAIS par magistrat (art. 33, loi 2019-222 — le modèle de données ne stocke aucun nom de magistrat).","parameters":[{"name":"groupBy","in":"query","schema":{"type":"string","enum":["juridiction","month","article"],"default":"juridiction"}},{"name":"code","in":"query","schema":{"type":"string","example":"CGI"},"description":"Filtre (groupBy=article)"},{"name":"dateMin","in":"query","schema":{"type":"string","format":"date"}},{"name":"dateMax","in":"query","schema":{"type":"string","format":"date"}},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":200}}],"responses":{"200":{"description":"OK — {groupBy, filters, rows[], conformite, source}"},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/webhooks":{"post":{"summary":"Créer un webhook (alerte de changement)","description":"« Préviens-moi quand l'article X change » (article_changed) ou « quand une décision cite cet article » (article_cited). Livraisons quotidiennes signées HMAC-SHA256 (header X-Legifrance-Signature). Le secret n'est renvoyé qu'À LA CRÉATION. URL https publique uniquement ; 20 webhooks max par compte.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","kind","code","num"],"properties":{"url":{"type":"string","format":"uri","example":"https://example.com/hooks/legifrance"},"kind":{"type":"string","enum":["article_changed","article_cited"]},"code":{"type":"string","example":"CGI"},"num":{"type":"string","example":"787 B"}}}}}},"responses":{"201":{"description":"Créé — {id, url, kind, code, num, secret (UNE fois), note}"},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Plafond de webhooks atteint"}}},"get":{"summary":"Lister ses webhooks (sans secret)","responses":{"200":{"description":"OK — {webhooks[]}"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/webhooks/{id}":{"delete":{"summary":"Supprimer un webhook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Supprimé"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/health":{"get":{"summary":"Health check (public)","security":[],"responses":{"200":{"description":"OK — {status, timestamp, service, version, source}"}}}},"/v1/stats":{"get":{"summary":"Statistiques publiques (public)","security":[],"responses":{"200":{"description":"OK — {total_versions, versions_en_vigueur, codes[], source}"}}}},"/v1/status":{"get":{"summary":"Status détaillé des bindings (D1/KV/R2/Vectorize)","description":"Endpoint diagnostic pour monitoring externe (UptimeRobot, etc.). Renvoie 503 si un binding est dégradé.","security":[],"responses":{"200":{"description":"Tous bindings opérationnels"},"503":{"description":"Au moins un binding dégradé — voir checks.{d1,kv,r2,vectorize}.ok"}}}}}}