{"openapi":"3.1.0","info":{"title":"ConvertChester","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\nConvertChester 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":"ConvertChester","url":"https://convertchester.com","email":"contact@convertchester.com"},"license":{"name":"Propriétaire — Arcchester"},"version":"0.1.0"},"externalDocs":{"description":"Documentation, catalogue des codes d'erreur et validateur en ligne","url":"https://convertchester.com/docs"},"servers":[{"url":"https://api.convertchester.com","description":"Production"}],"paths":{"/v1/convert":{"post":{"tags":["conversion-controller"],"summary":"Convertir un fichier vers le format demande","description":"Le format source se lit dans les octets du fichier, jamais dans son nom ni dans son extension. La cible se demande par `to`. Les paires servies sont celles que `GET /v1/formats` enumere — les deux lisent la meme table.\n\nUn refus structurel (paire inconnue, image indecodable) rend `200` avec `status: refused` et un motif : il ne se decompte pas.","operationId":"convert","parameters":[{"name":"to","in":"query","description":"Format cible de la conversion. Les valeurs servies sont celles que `GET /v1/formats` énumère — les deux lisent la même table. Les orthographes courtes (`jpg`, `tif`) désignent le même format que les longues.","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"}},"required":["file"]}}}},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"},"examples":{"Conversion aboutie":{"summary":"Conversion aboutie","description":"Le fichier converti revient en base64, avec l'empreinte SHA-256 du fichier soumis et celle du fichier produit.","value":{"status":"converted","document":{"sha256":"30f35a4b89f071a82f57ed9834fd77df44d1e970acdb9d382dabec0db752fe26","bytes":80},"source":"png","target":"jpeg","result":{"filename":"logo.jpg","media_type":"image/jpeg","sha256":"6a7570d4efd75f9f3d9780e044240c883fc7ba151192162b240e1953905b9d3e","bytes":644,"content":"/9j/4AAQSkZJRgABAgAAAQAB… (860 caractères base64, tronqués pour la lisibilité)"}}},"Conversion refusée":{"summary":"Conversion refusée","description":"**Même code HTTP 200.** Un refus structurel n'est pas une erreur de requête : `status` porte le verdict, `refusal.code` est stable, et rien n'est décompté.","value":{"status":"refused","document":{"sha256":"30f35a4b89f071a82f57ed9834fd77df44d1e970acdb9d382dabec0db752fe26","bytes":80},"source":"png","target":"png","refusal":{"code":"unsupported_conversion","message":"cette paire de formats n'est pas dans la table — voir GET /v1/formats"}}}}}}},"400":{"description":"Requête malformée : champ absent, valeur invalide, document illisible ou format non reconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"Cible inconnue":{"summary":"Cible inconnue","description":"La forme des erreurs de requête est la même partout. `error` ne se traduit jamais — c'est sur lui qu'un client branche son test ; `message` suit `Accept-Language`.","value":{"error":"unknown_target","message":"cible inconnue : docx — voir GET /v1/formats"}}}}}},"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/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/formats":{"get":{"tags":["conversion-controller"],"summary":"Les paires de formats servies","description":"La liste est calculee depuis la table des moteurs, jamais redigee : ce que cette reponse enumere est exactement ce que `POST /v1/convert` accepte.","operationId":"formats","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/FormatsResponse"},"examples":{"La table des paires":{"summary":"La table des paires","description":"Calculée depuis la table des moteurs, jamais rédigée : ce que cette réponse énumère est exactement ce que `POST /v1/convert` accepte.","value":{"conversions":[{"source":"png","target":"jpeg"},{"source":"png","target":"gif"},{"source":"png","target":"bmp"},{"source":"png","target":"tiff"},{"source":"jpeg","target":"png"},{"source":"jpeg","target":"gif"},{"source":"jpeg","target":"bmp"},{"source":"jpeg","target":"tiff"},{"source":"gif","target":"png"},{"source":"gif","target":"jpeg"},{"source":"gif","target":"bmp"},{"source":"gif","target":"tiff"},{"source":"bmp","target":"png"},{"source":"bmp","target":"jpeg"},{"source":"bmp","target":"gif"},{"source":"bmp","target":"tiff"},{"source":"tiff","target":"png"},{"source":"tiff","target":"jpeg"},{"source":"tiff","target":"gif"},{"source":"tiff","target":"bmp"}]}}}}}},"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":{"FormatsResponse":{"type":"object","properties":{"conversions":{"type":"array","items":{"$ref":"#/components/schemas/Pair"}}}},"Pair":{"type":"object","properties":{"source":{"type":"string"},"target":{"type":"string"}}}},"securitySchemes":{"apiKey":{"type":"http","description":"Clé API `flk_live_…`, transmise en `Authorization: Bearer`. La validation sur `/v1/validate` n'en demande aucune.","scheme":"bearer"}}}}