{"openapi":"3.1.0","info":{"title":"Factlint","description":"Validation de factures électroniques Factur-X, CII et UBL 2.1.\n\nLe verdict d'un document n'est jamais un code HTTP : une facture non conforme renvoie 200 avec un statut métier. Les codes 4xx ne concernent que la requête.\n\nDeux champs de la réponse disent ce qu'elle ne prouve pas. `engine.pinned` vaut false tant que le verdict est rendu contre les Schematron d'une bibliothèque tierce plutôt que contre les artefacts épinglés du jeu de règles : il est exact, mais pas rejouable. `national_rules.applied` vaut false sur un document UBL, les règles françaises BR-FR n'étant publiées qu'en syntaxe CII.\n\nFactlint est une couche technique. Générer un fichier conforme ne remplit aucune obligation légale de transmission : celle-ci passe par une plateforme agréée que vous restez tenu de désigner.","contact":{"name":"Factlint","url":"https://factlint.com","email":"contact@factlint.com"},"license":{"name":"Propriétaire — Arcchester"},"version":"0.1.0"},"externalDocs":{"description":"Documentation, catalogue des codes d'erreur et validateur en ligne","url":"https://factlint.com/docs"},"servers":[{"url":"https://api.factlint.com","description":"Production"}],"paths":{"/v1/validate":{"post":{"tags":["validate-controller"],"summary":"Valider une facture","description":"Rend le verdict d'un document contre un jeu de règles, sans rien écrire et sans rien conserver. Gratuit, sans clé, sans compte et sans limite de volume.\n\n**Le verdict n'est pas un code HTTP.** Un document non conforme renvoie 200 et un `status` métier : `passed`, `needs_input` (une donnée obligatoire manque, que seul l'émetteur possède), `blocked` (incohérence sur un champ financier — la valeur correcte est suggérée, jamais écrite) ou `not_evaluated` (le contrôle n'a pas pu avoir lieu ; mieux vaut une absence de verdict qu'un faux verdict, et ce cas n'est jamais décompté).\n\nDeux champs disent ce que la réponse ne prouve pas. `engine.pinned` à false signale un verdict exact mais non rejouable, rendu contre des Schematron embarqués plutôt que contre les artefacts épinglés. `national_rules.applied` à false signale un socle national non appliqué — sur un document UBL par exemple, les règles françaises BR-FR n'étant publiées qu'en syntaxe CII.\n\nAccepte un Factur-X ou ZUGFeRD (PDF/A-3), un CII ou un UBL 2.1, jusqu'à 10 Mo. Le document est traité en mémoire et n'est jamais écrit sur disque.","operationId":"validate","parameters":[{"name":"ruleset","in":"query","description":"Jeu de règles du verdict. `latest` prend celui en vigueur aujourd'hui ; `as_of_issue_date` prend celui qui l'était à la date d'émission du document (BT-2), ce qui est le seul moyen de rejuger une facture ancienne comme elle l'avait été ; un identifiant épinglé — `eu-en16931-2026-08` — force ce jeu-là. Sans BT-2 lisible, `as_of_issue_date` ne se rabat pas en silence : le verdict sort en `not_evaluated`.","required":false,"schema":{"type":"string","default":"latest"}},{"name":"jurisdiction","in":"query","description":"Socle national demandé, en code ISO 3166-1 alpha-2, ou `EU` pour l'EN 16931 seule. Un code inconnu est une erreur de requête, jamais un repli sur le défaut. Ce qui a réellement été appliqué est dit par `national_rules` dans la réponse.","required":false,"schema":{"type":"string","default":"FR"}},{"name":"pinned","in":"query","description":"Exige un verdict rejouable, rendu contre les artefacts épinglés du jeu de règles plutôt que contre les Schematron embarqués. Couvre moins de règles aujourd'hui : à demander quand la reproductibilité prime sur l'étendue.","required":false,"schema":{"type":"boolean","default":false}},{"name":"Accept-Language","in":"header","description":"Langue des messages lisibles de la réponse : `fr` ou `en`. Les codes, eux, ne sont jamais traduits — c'est sur eux qu'un client branche son test.","required":false}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}},"400":{"description":"Requête malformée : champ absent, valeur invalide, document illisible ou format non reconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"Document au-delà de 10 Mo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"Type de contenu inattendu pour cet endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Trop de requêtes. La limite porte sur l'adresse IP pour les endpoints libres, sur la clé sinon.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Erreur interne. Le traitement n'est pas décompté et la réponse porte un identifiant d'incident à nous communiquer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/process":{"post":{"tags":["process-controller"],"summary":"Enchaîner le pipeline en un appel","description":"Orchestre les étapes du pipeline sur un même document : identification, validation, correction, enrichissement, conversion, scellement. Le corps JSON déclare les étapes voulues et leurs options ; chacune reçoit la sortie de la précédente.\n\n**Une étape qui échoue arrête la suite** et la réponse dit laquelle et pourquoi. Les étapes déjà exécutées gardent leur résultat : un appel interrompu reste lisible, il ne rend pas une réponse vide.\n\nLe décompte porte sur le document distinct, pas sur le nombre d'étapes : un pipeline de cinq étapes coûte le même document qu'un `/v1/fix` seul.","operationId":"process","parameters":[{"name":"Accept-Language","in":"header","description":"Langue des messages lisibles de la réponse : `fr` ou `en`. Les codes, eux, ne sont jamais traduits — c'est sur eux qu'un client branche son test.","required":false},{"name":"Idempotency-Key","in":"header","description":"Rejoue sans redécompter. La clé est liée à l'empreinte du document : la même clé sur un autre document renvoie 409.","required":false}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}},"400":{"description":"Requête malformée : champ absent, valeur invalide, document illisible ou format non reconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"Document au-delà de 10 Mo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"Type de contenu inattendu pour cet endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Trop de requêtes. La limite porte sur l'adresse IP pour les endpoints libres, sur la clé sinon.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Erreur interne. Le traitement n'est pas décompté et la réponse porte un identifiant d'incident à nous communiquer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Clé API absente (`missing_api_key`) ou invalide (`invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Aucun abonnement actif (`subscription_required`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Palier insuffisant (`plan_required`) : cet endpoint demande le plan PRO.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"`Idempotency-Key` déjà utilisée avec un autre document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Facturation injoignable (`billing_unavailable`). Nous refusons plutôt que de servir un traitement payant sans pouvoir le décompter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"apiKey":[]}]}},"/v1/fix":{"post":{"tags":["fix-controller"],"summary":"Corriger les anomalies structurelles","description":"Corrige ce qui se corrige sans rien inventer, revalide le résultat et rend le document modifié avec le détail de chaque écriture : code de règle, XPath, valeur avant, valeur après. Toute correction est réversible et tracée.\n\n**Un champ financier n'est jamais écrit.** Montants, taux et bases de TVA, IBAN et coordonnées de paiement sont refusés à l'écriture même demandés explicitement : la valeur calculée sort en `suggested_value` et le constat en `blocked`. C'est une règle produit, pas une limite technique — écrire un montant à la place de l'émetteur engagerait sa facture.\n\nUne donnée que seul l'émetteur possède ne s'invente pas davantage : le constat sort en `needs_input` avec son emplacement. Pour la fournir, reprenez le diagnostic sur `/v1/fix-with-input` avec le `flow_id` rendu ici.","operationId":"fix","parameters":[{"name":"already_issued","in":"query","description":"Le document a déjà été émis. Coupe toute écriture : les corrections sont rendues en propositions, le document repart intact. Une facture émise ne se corrige que par avoir et facture rectificative.","required":false,"schema":{"type":"boolean","default":false}},{"name":"max_changes","in":"query","description":"Fusible : au-delà de ce nombre de corrections, les suivantes sortent en propositions avec `max_changes_reached` et `fuse_blown` passe à vrai. Le document n'est jamais partiellement écrit.","required":false,"schema":{"type":"integer","format":"int32","default":50}},{"name":"ruleset","in":"query","description":"Jeu de règles du verdict. `latest` prend celui en vigueur aujourd'hui ; `as_of_issue_date` prend celui qui l'était à la date d'émission du document (BT-2), ce qui est le seul moyen de rejuger une facture ancienne comme elle l'avait été ; un identifiant épinglé — `eu-en16931-2026-08` — force ce jeu-là. Sans BT-2 lisible, `as_of_issue_date` ne se rabat pas en silence : le verdict sort en `not_evaluated`.","required":false,"schema":{"type":"string","default":"latest"}},{"name":"jurisdiction","in":"query","description":"Socle national demandé, en code ISO 3166-1 alpha-2, ou `EU` pour l'EN 16931 seule. Un code inconnu est une erreur de requête, jamais un repli sur le défaut. Ce qui a réellement été appliqué est dit par `national_rules` dans la réponse.","required":false,"schema":{"type":"string","default":"FR"}},{"name":"Accept-Language","in":"header","description":"Langue des messages lisibles de la réponse : `fr` ou `en`. Les codes, eux, ne sont jamais traduits — c'est sur eux qu'un client branche son test.","required":false},{"name":"Idempotency-Key","in":"header","description":"Rejoue sans redécompter. La clé est liée à l'empreinte du document : la même clé sur un autre document renvoie 409.","required":false}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}},"400":{"description":"Requête malformée : champ absent, valeur invalide, document illisible ou format non reconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"Document au-delà de 10 Mo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"Type de contenu inattendu pour cet endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Trop de requêtes. La limite porte sur l'adresse IP pour les endpoints libres, sur la clé sinon.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Erreur interne. Le traitement n'est pas décompté et la réponse porte un identifiant d'incident à nous communiquer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Clé API absente (`missing_api_key`) ou invalide (`invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Aucun abonnement actif (`subscription_required`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Palier insuffisant (`plan_required`) : cet endpoint demande le plan PRO.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"`Idempotency-Key` déjà utilisée avec un autre document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Facturation injoignable (`billing_unavailable`). Nous refusons plutôt que de servir un traitement payant sans pouvoir le décompter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"apiKey":[]}]}},"/v1/fix-with-input":{"post":{"tags":["fix-controller"],"summary":"Reprendre une correction avec les valeurs de l'émetteur","description":"Reprend un diagnostic `/v1/fix` en y ajoutant les valeurs que seul l'émetteur possède. Le service rejoue les corrections automatiques sur le document d'origine, applique les valeurs autorisées, puis revalide.\n\nTrois verrous, cumulatifs. Le code doit figurer dans le verdict initial avec `resolution: \"input\"` ; il doit appartenir à la liste blanche d'emplacements que le serveur sait écrire sans ambiguïté — aujourd'hui `BR-02` (BT-1, numéro de facture) et `PEPPOL-EN16931-R001` (BT-23, processus métier sous forme d'URN), tout autre code recevant `input_not_supported` plutôt qu'une écriture approximative ; et l'emplacement visé repasse par la politique des champs financiers.\n\n**Rien du document n'est conservé entre les deux appels.** Le `flow_id` ne retient que l'empreinte : renvoyez le fichier d'origine octet pour octet. Un autre fichier reçoit 409, un flux expiré 410.\n\n`inputs` est une partie multipart JSON : un tableau de `{\"code\",\"value\"}`, vingt au maximum.","operationId":"fixWithInput","parameters":[{"name":"flow_id","in":"query","description":"Identifiant rendu par le diagnostic `/v1/fix` initial. Il est lié à l'empreinte du document : un autre fichier reçoit 409, un flux expiré 410.","required":true,"schema":{"type":"string"}},{"name":"already_issued","in":"query","description":"Le document a déjà été émis. Coupe toute écriture : les corrections sont rendues en propositions, le document repart intact. Une facture émise ne se corrige que par avoir et facture rectificative.","required":false,"schema":{"type":"boolean","default":false}},{"name":"max_changes","in":"query","description":"Fusible : au-delà de ce nombre de corrections, les suivantes sortent en propositions avec `max_changes_reached` et `fuse_blown` passe à vrai. Le document n'est jamais partiellement écrit.","required":false,"schema":{"type":"integer","format":"int32","default":50}},{"name":"ruleset","in":"query","description":"Jeu de règles du verdict. `latest` prend celui en vigueur aujourd'hui ; `as_of_issue_date` prend celui qui l'était à la date d'émission du document (BT-2), ce qui est le seul moyen de rejuger une facture ancienne comme elle l'avait été ; un identifiant épinglé — `eu-en16931-2026-08` — force ce jeu-là. Sans BT-2 lisible, `as_of_issue_date` ne se rabat pas en silence : le verdict sort en `not_evaluated`.","required":false,"schema":{"type":"string","default":"latest"}},{"name":"jurisdiction","in":"query","description":"Socle national demandé, en code ISO 3166-1 alpha-2, ou `EU` pour l'EN 16931 seule. Un code inconnu est une erreur de requête, jamais un repli sur le défaut. Ce qui a réellement été appliqué est dit par `national_rules` dans la réponse.","required":false,"schema":{"type":"string","default":"FR"}},{"name":"Accept-Language","in":"header","description":"Langue des messages lisibles de la réponse : `fr` ou `en`. Les codes, eux, ne sont jamais traduits — c'est sur eux qu'un client branche son test.","required":false},{"name":"Idempotency-Key","in":"header","description":"Rejoue sans redécompter. La clé est liée à l'empreinte du document : la même clé sur un autre document renvoie 409.","required":false}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"},"inputs":{"type":"array","items":{"$ref":"#/components/schemas/RequestedInput"}}},"required":["file","inputs"]}}}},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}},"400":{"description":"Requête malformée : champ absent, valeur invalide, document illisible ou format non reconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"Document au-delà de 10 Mo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"Type de contenu inattendu pour cet endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Trop de requêtes. La limite porte sur l'adresse IP pour les endpoints libres, sur la clé sinon.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Erreur interne. Le traitement n'est pas décompté et la réponse porte un identifiant d'incident à nous communiquer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Clé API absente (`missing_api_key`) ou invalide (`invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Aucun abonnement actif (`subscription_required`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Palier insuffisant (`plan_required`) : cet endpoint demande le plan PRO.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"`Idempotency-Key` déjà utilisée avec un autre document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Facturation injoignable (`billing_unavailable`). Nous refusons plutôt que de servir un traitement payant sans pouvoir le décompter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"apiKey":[]}]}},"/v1/evidence":{"post":{"tags":["evidence-controller"],"summary":"Sceller un pack de preuve","description":"Valide le document et scelle le tout — verdict, jeu de règles, empreintes des artefacts, horodatage — dans un pack signé en Ed25519. Le pack est autoportant : il contient de quoi rejouer le raisonnement sans nous interroger.\n\n**La facture elle-même n'est pas dans le pack**, seulement son empreinte. Le document n'est pas conservé, ici pas plus qu'ailleurs.\n\n`pinned` vaut vrai par défaut sur cet endpoint, à l'inverse de la validation : un pack de preuve rendu contre des Schematron non épinglés ne prouverait pas grand-chose.","operationId":"seal","parameters":[{"name":"ruleset","in":"query","description":"Jeu de règles du verdict. `latest` prend celui en vigueur aujourd'hui ; `as_of_issue_date` prend celui qui l'était à la date d'émission du document (BT-2), ce qui est le seul moyen de rejuger une facture ancienne comme elle l'avait été ; un identifiant épinglé — `eu-en16931-2026-08` — force ce jeu-là. Sans BT-2 lisible, `as_of_issue_date` ne se rabat pas en silence : le verdict sort en `not_evaluated`.","required":false,"schema":{"type":"string","default":"latest"}},{"name":"jurisdiction","in":"query","description":"Socle national demandé, en code ISO 3166-1 alpha-2, ou `EU` pour l'EN 16931 seule. Un code inconnu est une erreur de requête, jamais un repli sur le défaut. Ce qui a réellement été appliqué est dit par `national_rules` dans la réponse.","required":false,"schema":{"type":"string","default":"FR"}},{"name":"pinned","in":"query","description":"Exige un verdict rejouable, rendu contre les artefacts épinglés du jeu de règles plutôt que contre les Schematron embarqués. Couvre moins de règles aujourd'hui : à demander quand la reproductibilité prime sur l'étendue.","required":false,"schema":{"type":"boolean","default":true}},{"name":"Accept-Language","in":"header","description":"Langue des messages lisibles de la réponse : `fr` ou `en`. Les codes, eux, ne sont jamais traduits — c'est sur eux qu'un client branche son test.","required":false},{"name":"Idempotency-Key","in":"header","description":"Rejoue sans redécompter. La clé est liée à l'empreinte du document : la même clé sur un autre document renvoie 409.","required":false}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}},"400":{"description":"Requête malformée : champ absent, valeur invalide, document illisible ou format non reconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"Document au-delà de 10 Mo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"Type de contenu inattendu pour cet endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Trop de requêtes. La limite porte sur l'adresse IP pour les endpoints libres, sur la clé sinon.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Erreur interne. Le traitement n'est pas décompté et la réponse porte un identifiant d'incident à nous communiquer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Clé API absente (`missing_api_key`) ou invalide (`invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Aucun abonnement actif (`subscription_required`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Palier insuffisant (`plan_required`) : cet endpoint demande le plan ENTERPRISE.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"`Idempotency-Key` déjà utilisée avec un autre document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Facturation injoignable (`billing_unavailable`). Nous refusons plutôt que de servir un traitement payant sans pouvoir le décompter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"apiKey":[]}]}},"/v1/evidence/verify":{"post":{"tags":["evidence-controller"],"summary":"Vérifier un sceau","description":"Confronte un pack à la clé publique qui l'a signé et dit s'il est intact. Gratuit et sans clé : la vérification ne nous transmet aucun document et ne demande aucune confiance, donc elle ne se paye pas.\n\nLes clés publiques, actives et révoquées, sont publiées en JWKS sur `/.well-known/evidence-jwks.json` : un destinataire peut vérifier un pack sans nous appeler du tout. Une clé révoquée reste publiée aussi longtemps que les packs qu'elle a signés — la retirer rendrait invérifiable ce qu'elle a scellé.","operationId":"verify","parameters":[{"name":"Accept-Language","in":"header","description":"Langue des messages lisibles de la réponse : `fr` ou `en`. Les codes, eux, ne sont jamais traduits — c'est sur eux qu'un client branche son test.","required":false}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvidencePack"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/VerifyResponse"}}}},"400":{"description":"Requête malformée : champ absent, valeur invalide, document illisible ou format non reconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"Document au-delà de 10 Mo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"Type de contenu inattendu pour cet endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Trop de requêtes. La limite porte sur l'adresse IP pour les endpoints libres, sur la clé sinon.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Erreur interne. Le traitement n'est pas décompté et la réponse porte un identifiant d'incident à nous communiquer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/enrich":{"post":{"tags":["enrich-controller"],"summary":"Vérifier et compléter les données de tiers","description":"Confronte les tiers de la facture aux référentiels publics : numéro de TVA intracommunautaire auprès de VIES, immatriculation et adresse auprès des annuaires d'entreprises et de la base adresse nationale.\n\n**Tout enrichissement est sourcé.** Chaque valeur ajoutée porte son référentiel, l'horodatage de l'interrogation et la valeur précédente : un enrichissement qu'on ne peut pas retracer ne vaut pas mieux qu'une saisie.\n\nLes référentiels publics tombent. Une interrogation qui échoue n'est pas convertie en absence de donnée : le champ sort avec son drapeau, et le reste du traitement continue.","operationId":"enrich","parameters":[{"name":"lookup_company","in":"query","required":false,"schema":{"type":"boolean","default":false}},{"name":"verify_vat_eu","in":"query","required":false,"schema":{"type":"boolean","default":true}},{"name":"normalize_address","in":"query","required":false,"schema":{"type":"boolean","default":true}},{"name":"Accept-Language","in":"header","description":"Langue des messages lisibles de la réponse : `fr` ou `en`. Les codes, eux, ne sont jamais traduits — c'est sur eux qu'un client branche son test.","required":false},{"name":"Idempotency-Key","in":"header","description":"Rejoue sans redécompter. La clé est liée à l'empreinte du document : la même clé sur un autre document renvoie 409.","required":false}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}},"400":{"description":"Requête malformée : champ absent, valeur invalide, document illisible ou format non reconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"Document au-delà de 10 Mo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"Type de contenu inattendu pour cet endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Trop de requêtes. La limite porte sur l'adresse IP pour les endpoints libres, sur la clé sinon.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Erreur interne. Le traitement n'est pas décompté et la réponse porte un identifiant d'incident à nous communiquer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Clé API absente (`missing_api_key`) ou invalide (`invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Aucun abonnement actif (`subscription_required`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Palier insuffisant (`plan_required`) : cet endpoint demande le plan PRO.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"`Idempotency-Key` déjà utilisée avec un autre document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Facturation injoignable (`billing_unavailable`). Nous refusons plutôt que de servir un traitement payant sans pouvoir le décompter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"apiKey":[]}]}},"/v1/convert":{"post":{"tags":["convert-controller"],"summary":"Convertir entre les formats du socle","description":"Deux cibles, et rien d'autre. `to=cii` extrait le XML d'un conteneur Factur-X ; `to=facturx` attache un XML CII au PDF lisible que vous fournissez dans `visual`.\n\n**Le XML n'est jamais relu ni resérialisé** : il est rattaché octet pour octet, et le conteneur produit est relu après coup pour le vérifier. Si le XML relu ne correspond pas, rien n'est rendu — nous préférons un refus à un document dont nous ne pouvons pas garantir le contenu.\n\nDeux refus assumés. **Depuis ou vers l'UBL** : la correspondance sémantique champ par champ entre les deux vocabulaires n'existe pas vérifiée, et l'approximer produirait des factures syntaxiquement valides et sémantiquement fausses. **Sans `visual`** : un Factur-X est un PDF qu'un humain lit et un XML qu'une machine lit, et la réglementation exige qu'ils disent la même chose. Nous ne mettons pas votre facture en page à votre place.","operationId":"convert","parameters":[{"name":"to","in":"query","required":true,"schema":{"type":"string"}},{"name":"Accept-Language","in":"header","description":"Langue des messages lisibles de la réponse : `fr` ou `en`. Les codes, eux, ne sont jamais traduits — c'est sur eux qu'un client branche son test.","required":false},{"name":"Idempotency-Key","in":"header","description":"Rejoue sans redécompter. La clé est liée à l'empreinte du document : la même clé sur un autre document renvoie 409.","required":false}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"},"visual":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}},"400":{"description":"Requête malformée : champ absent, valeur invalide, document illisible ou format non reconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"Document au-delà de 10 Mo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"Type de contenu inattendu pour cet endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Trop de requêtes. La limite porte sur l'adresse IP pour les endpoints libres, sur la clé sinon.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Erreur interne. Le traitement n'est pas décompté et la réponse porte un identifiant d'incident à nous communiquer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Clé API absente (`missing_api_key`) ou invalide (`invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Aucun abonnement actif (`subscription_required`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Palier insuffisant (`plan_required`) : cet endpoint demande le plan PRO.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"`Idempotency-Key` déjà utilisée avec un autre document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Facturation injoignable (`billing_unavailable`). Nous refusons plutôt que de servir un traitement payant sans pouvoir le décompter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"apiKey":[]}]}},"/v1/usage":{"get":{"tags":["usage-controller"],"summary":"Consommation du compte","description":"Ce qui a été décompté sur la période en cours. L'unité est le **document distinct**, identifié par son empreinte — pas l'appel HTTP ni l'étape de pipeline : facturer à l'appel punirait le re-test itératif, qui est l'usage normal.\n\nUne re-soumission du même document est gratuite pendant sept jours. Un `not_evaluated` et une erreur serveur ne sont jamais décomptés : on ne facture que ce qu'on a produit.","operationId":"consommation","parameters":[{"name":"Accept-Language","in":"header","description":"Langue des messages lisibles de la réponse : `fr` ou `en`. Les codes, eux, ne sont jamais traduits — c'est sur eux qu'un client branche son test.","required":false}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}},"400":{"description":"Requête malformée : champ absent, valeur invalide, document illisible ou format non reconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"Document au-delà de 10 Mo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"Type de contenu inattendu pour cet endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Trop de requêtes. La limite porte sur l'adresse IP pour les endpoints libres, sur la clé sinon.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Erreur interne. Le traitement n'est pas décompté et la réponse porte un identifiant d'incident à nous communiquer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/rulesets":{"get":{"tags":["ruleset-controller"],"summary":"Lister les jeux de règles","description":"Le catalogue des jeux de règles publiés, avec pour chacun son état, sa période d'effet et sa date de conservation. Un jeu scellé est immuable et reste exécutable pendant la durée d'archivage fiscal : c'est ce qui permet de rejouer dans dix ans un verdict rendu aujourd'hui.\n\nUn jeu en préparation apparaît ici avec `sealed: false`. Ses empreintes ne sont pas calculées et aucun verdict rendu contre lui n'est rejouable.","operationId":"list","parameters":[{"name":"Accept-Language","in":"header","description":"Langue des messages lisibles de la réponse : `fr` ou `en`. Les codes, eux, ne sont jamais traduits — c'est sur eux qu'un client branche son test.","required":false}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CatalogResponse"}}}},"400":{"description":"Requête malformée : champ absent, valeur invalide, document illisible ou format non reconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"Document au-delà de 10 Mo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"Type de contenu inattendu pour cet endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Trop de requêtes. La limite porte sur l'adresse IP pour les endpoints libres, sur la clé sinon.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Erreur interne. Le traitement n'est pas décompté et la réponse porte un identifiant d'incident à nous communiquer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/rulesets/{id}":{"get":{"tags":["ruleset-controller"],"summary":"Détailler un jeu de règles","description":"Le manifeste d'un jeu : ses artefacts, leurs versions, leurs licences et leurs empreintes SHA-256. C'est ce qu'il faut pour vérifier soi-même qu'un verdict a été rendu contre ce qu'on annonce.\n\nLes artefacts eux-mêmes ne sont pas redistribués — certaines licences ne le permettent pas. Le manifeste et les empreintes suffisent à les identifier et à les récupérer à la source.","operationId":"byId","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Accept-Language","in":"header","description":"Langue des messages lisibles de la réponse : `fr` ou `en`. Les codes, eux, ne sont jamais traduits — c'est sur eux qu'un client branche son test.","required":false}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RulesetView"}}}},"400":{"description":"Requête malformée : champ absent, valeur invalide, document illisible ou format non reconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"Document au-delà de 10 Mo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"Type de contenu inattendu pour cet endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Trop de requêtes. La limite porte sur l'adresse IP pour les endpoints libres, sur la clé sinon.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Erreur interne. Le traitement n'est pas décompté et la réponse porte un identifiant d'incident à nous communiquer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/evidence/{id}":{"get":{"tags":["evidence-controller"],"summary":"Relire un pack scellé","description":"Rend un pack déjà scellé, tel qu'il a été signé. Ce que nous conservons est le pack, jamais la facture qui l'a produit.","operationId":"byId_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Accept-Language","in":"header","description":"Langue des messages lisibles de la réponse : `fr` ou `en`. Les codes, eux, ne sont jamais traduits — c'est sur eux qu'un client branche son test.","required":false},{"name":"Idempotency-Key","in":"header","description":"Rejoue sans redécompter. La clé est liée à l'empreinte du document : la même clé sur un autre document renvoie 409.","required":false}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}},"400":{"description":"Requête malformée : champ absent, valeur invalide, document illisible ou format non reconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"Document au-delà de 10 Mo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"Type de contenu inattendu pour cet endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Trop de requêtes. La limite porte sur l'adresse IP pour les endpoints libres, sur la clé sinon.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Erreur interne. Le traitement n'est pas décompté et la réponse porte un identifiant d'incident à nous communiquer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Clé API absente (`missing_api_key`) ou invalide (`invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Aucun abonnement actif (`subscription_required`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Palier insuffisant (`plan_required`) : cet endpoint demande le plan ENTERPRISE.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"`Idempotency-Key` déjà utilisée avec un autre document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Facturation injoignable (`billing_unavailable`). Nous refusons plutôt que de servir un traitement payant sans pouvoir le décompter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"apiKey":[]}]}},"/.well-known/evidence-jwks.json":{"get":{"tags":["evidence-keys-controller"],"summary":"Clés publiques de vérification des packs","description":"Les clés publiques Ed25519 au format JWKS, actives et révoquées. Elles permettent de vérifier un pack de preuve hors ligne, sans nous appeler.\n\nUne clé révoquée n'est jamais retirée d'ici : elle vit aussi longtemps que les packs qu'elle a signés, sans quoi ce qu'elle a scellé deviendrait invérifiable.","operationId":"jwks","parameters":[{"name":"Accept-Language","in":"header","description":"Langue des messages lisibles de la réponse : `fr` ou `en`. Les codes, eux, ne sont jamais traduits — c'est sur eux qu'un client branche son test.","required":false}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/Jwks"}}}},"400":{"description":"Requête malformée : champ absent, valeur invalide, document illisible ou format non reconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"Document au-delà de 10 Mo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"Type de contenu inattendu pour cet endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Trop de requêtes. La limite porte sur l'adresse IP pour les endpoints libres, sur la clé sinon.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Erreur interne. Le traitement n'est pas décompté et la réponse porte un identifiant d'incident à nous communiquer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}}},"components":{"schemas":{"Autofix":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxChanges":{"type":"integer","format":"int32","maximum":50,"minimum":1}}},"Delivery":{"type":"object","properties":{"mode":{"type":"string"}}},"Document":{"type":"object","properties":{"content":{"type":"string"},"filename":{"type":"string"},"alreadyIssued":{"type":"boolean"}},"required":["content"]},"Enrich":{"type":"object","properties":{"lookupCompany":{"type":"boolean"},"verifyVatEu":{"type":"boolean"},"normalizeAddress":{"type":"boolean"}}},"Pipeline":{"type":"object","properties":{"enrich":{"$ref":"#/components/schemas/Enrich"},"validate":{"$ref":"#/components/schemas/Validate"},"autofix":{"$ref":"#/components/schemas/Autofix"},"transform":{"$ref":"#/components/schemas/Transform"},"evidencePack":{"type":"boolean"}}},"ProcessRequest":{"type":"object","properties":{"document":{"$ref":"#/components/schemas/Document"},"pipeline":{"$ref":"#/components/schemas/Pipeline"},"delivery":{"$ref":"#/components/schemas/Delivery"}},"required":["document"]},"Transform":{"type":"object","properties":{"outputs":{"type":"array","items":{"type":"string"}}}},"Validate":{"type":"object","properties":{"ruleset":{"type":"string"},"jurisdiction":{"type":"string"},"nationalRules":{"type":"boolean"},"includePasses":{"type":"boolean"},"pinned":{"type":"boolean"}}},"RequestedInput":{"type":"object","properties":{"code":{"type":"string","maxLength":120,"minLength":0},"value":{"type":"string","maxLength":200,"minLength":0}},"required":["code","value"]},"Artifact":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"string"},"sha256":{"type":"string"}}},"Engine":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"string"},"pinned":{"type":"boolean"}}},"EvidencePack":{"type":"object","properties":{"version":{"type":"string"},"sealedAt":{"type":"string","format":"date-time"},"document":{"$ref":"#/components/schemas/Document"},"ruleset":{"$ref":"#/components/schemas/Ruleset"},"engine":{"$ref":"#/components/schemas/Engine"},"verdict":{"$ref":"#/components/schemas/Verdict"},"seal":{"type":"string"},"integrity":{"$ref":"#/components/schemas/Integrity"},"signature":{"$ref":"#/components/schemas/PackSignature"}}},"Integrity":{"type":"object","properties":{"alg":{"type":"string"},"hash":{"type":"string"}}},"PackSignature":{"type":"object","properties":{"alg":{"type":"string"},"keyId":{"type":"string"},"value":{"type":"string"}}},"Ruleset":{"type":"object","properties":{"id":{"type":"string"},"sha256":{"type":"string"},"sealed":{"type":"boolean"},"effectiveFrom":{"type":"string"},"artifacts":{"type":"array","items":{"$ref":"#/components/schemas/Artifact"}}}},"Verdict":{"type":"object","properties":{"status":{"type":"string"},"errors":{"type":"integer","format":"int32"},"warnings":{"type":"integer","format":"int32"},"passes":{"type":"integer","format":"int32"}}},"VerifyResponse":{"type":"object","properties":{"sealValid":{"type":"boolean"},"integrityValid":{"type":"boolean"},"signatureValid":{"type":"boolean"},"authentic":{"type":"boolean"},"version":{"type":"string"},"id":{"type":"string"},"reason":{"type":"string"}}},"ArtifactView":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"string"},"sha256":{"type":"string"},"license":{"type":"string"}}},"CatalogResponse":{"type":"object","properties":{"count":{"type":"integer","format":"int32"},"rulesets":{"type":"array","items":{"$ref":"#/components/schemas/RulesetView"}}}},"RulesetView":{"type":"object","properties":{"id":{"type":"string"},"jurisdiction":{"type":"string"},"status":{"type":"string"},"sealed":{"type":"boolean"},"sha256":{"type":"string"},"effectiveFrom":{"type":"string","format":"date"},"effectiveUntil":{"type":"string","format":"date"},"retainedUntil":{"type":"string","format":"date"},"profilesSupported":{"type":"array","items":{"type":"string"}},"profilesRejected":{"type":"array","items":{"type":"string"}},"fixPolicyVersion":{"type":"string"},"changelogUrl":{"type":"string"},"artifacts":{"type":"array","items":{"$ref":"#/components/schemas/ArtifactView"}}}},"Jwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"use":{"type":"string"},"alg":{"type":"string"},"kid":{"type":"string"}}},"Jwks":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/Jwk"}}}}},"securitySchemes":{"apiKey":{"type":"http","description":"Clé API `flk_live_…`, transmise en `Authorization: Bearer`. La validation sur `/v1/validate` n'en demande aucune.","scheme":"bearer"}}}}