Comment convertir un PDF de facture classique en Factur-X PDF/A-3 contrôlé selon EN16931 ? Deux modes avec l’endpoint /convert : mode ERP (JSON structuré + PDF, déterministe) ou mode extraction (lecture automatique du PDF, OCR possible). Le résultat passe les contrôles XSD, Schematron et veraPDF exposés dans la réponse.
En bref
- Endpoint :
POST /api/v1/convert— input PDF (+ éventuellementinvoice_dataJSON), output PDF/A-3 Factur-X. - Mode ERP recommandé : vous fournissez les données comptables en JSON, le PDF reste le support visuel. Déterministe, sans OCR.
- Mode extraction : le moteur lit le texte/OCR du PDF et en déduit les champs. Pratique pour numériser l’existant.
- Contexte : Factur-X 1.08 / ZUGFeRD 2.4 (basé sur UN/CEFACT CII D22B), applicable depuis le 15 janvier 2026.
- Quatre niveaux de contrôle exécutés et détaillés : PDF/A-3 ISO 19005-3, structure Factur-X, XSD CII, Schematron EN16931.
Quand utiliser cet endpoint
| Besoin | Mode recommandé |
|---|---|
| Flux B2B production depuis ERP (Sage, SAP, Odoo, Dolibarr…) | Mode ERP — données structurées en JSON |
| Numériser un stock de factures fournisseurs PDF existantes | Mode extraction — OCR activé automatiquement si nécessaire |
| Prototypage rapide sans intégration ERP | Mode extraction — faible friction |
| PDF généré par votre propre SI (layouts standards) | Mode ERP — idéal, pas d’OCR à charger |
Un PDF de facture classique ne suffit plus. La réforme de facturation électronique impose que les factures B2B échangées via les PA soient au format normé — Factur-X, UBL ou CII. Pour les millions de factures PDF qui circulent encore sans XML structuré, la question technique est concrète : comment convertir un PDF existant en document Factur-X conforme ?
Ce guide se place dans le contexte de Factur-X 1.08 / ZUGFeRD 2.4, version applicable depuis le 15 janvier 2026, basée sur UN/CEFACT CII D22B. Il couvre les deux approches de conversion, leurs cas d’usage, et leur implémentation via l’API FacturX. Le contexte réglementaire complet est traité dans Facturation électronique 2026 : le guide technique complet pour développeurs.
Pourquoi un PDF classique ne suffit pas
Un PDF “normal” est un document visuel. Il contient du texte rendu dans une mise en page, mais aucune donnée structurée exploitable par une machine. Un destinataire humain peut le lire — un ERP ne peut pas l’intégrer automatiquement sans ressaisie ou OCR.
Factur-X résout ce problème avec un format hybride : un PDF lisible par l’humain et un XML CII embarqué lisible par la machine, le tout dans un conteneur PDF/A-3 conforme à l’archivage long terme.
Les trois éléments manquants dans un PDF classique :
- Le XML CII (
factur-x.xml) — les données structurées de la facture au format UN/CEFACT Cross-Industry Invoice - Le conteneur PDF/A-3 — conformité ISO 19005-3 avec polices embarquées, profil ICC, métadonnées XMP
- Les métadonnées Factur-X —
AFRelationshipcohérent avec la relation PDF/XML (Data,SourceouAlternativeselon le cas —Alternativeétant le plus courant dans le contexte Factur-X), namespacefx:dans le XMP, GuidelineID correspondant au profil
Construire ce pipeline soi-même — Ghostscript pour le PDF/A-3, une bibliothèque XML pour le CII, l’embedding avec les bons attributs, la validation veraPDF — est faisable mais fragile. Les pièges sont documentés dans PDF/A-3 pour Factur-X : checklist de conformité et pièges courants.
Anatomie d’un PDF Factur-X conforme
Avant de convertir, il faut comprendre ce qu’on produit. Un PDF Factur-X valide est un fichier qui passe quatre niveaux de validation :
1. PDF/A-3 (ISO 19005-3) — Le conteneur est conforme : polices embarquées, profil ICC dans OutputIntents, pas de JavaScript ni d’actions Launch, métadonnées XMP avec pdfaid:part=3.
2. Structure Factur-X — Un fichier factur-x.xml est présent dans le name tree EmbeddedFiles du catalogue PDF, avec un AFRelationship approprié (typiquement /Alternative pour Factur-X) et le type MIME text/xml.
3. XSD — Le XML embarqué est structurellement valide par rapport au schéma CII D22B (CrossIndustryInvoice_100pD22B.xsd).
4. Schematron — Le XML respecte les règles métier EN16931 : totaux arithmétiquement cohérents, codes devise ISO 4217, champs obligatoires présents selon le profil déclaré.
Le GuidelineID dans le XML doit correspondre au profil déclaré. Pour le profil EN16931 (recommandé) : urn:cen.eu:en16931:2017. Pour la liste complète des profils et leurs GuidelineIDs, voir Profils Factur-X : MINIMUM, BASIC, EN16931, EXTENDED.
Les deux modes de conversion
L’API FacturX propose deux approches distinctes pour convertir un PDF en Factur-X, selon la source des données.
Mode 1 : extraction automatique depuis le PDF
Le moteur lit le texte natif du PDF et identifie les champs de la facture : numéro, date d’émission, montants HT/TVA/TTC, informations vendeur et acheteur, lignes de facturation.
Si le PDF est scanné (image sans couche texte), un moteur OCR prend le relais automatiquement. Un score de confiance est calculé pour chaque champ extrait — le seuil par défaut est 70% en mode strict.
Cas d’usage :
- Factures fournisseurs reçues en PDF classique
- Numérisation de l’existant (archives PDF)
- Prototypage rapide sans intégration ERP
Limites :
- La qualité de l’extraction dépend de la mise en page du PDF
- Les layouts exotiques (tableaux imbriqués, multi-colonnes) peuvent produire des résultats incomplets
- Le mode OCR peut prendre plus de temps selon le nombre de pages
Mode 2 : données structurées depuis l’ERP (recommandé)
L’ERP fournit les données comptables au format JSON via le champ invoice_data. Le PDF reste le support visuel — les données structurées JSON sont la source de vérité pour générer le XML CII.
Cas d’usage :
- Intégration ERP (Sage, SAP, Odoo, Dolibarr)
- Flux B2B automatisés en production
- Toute situation où les données sont déjà structurées dans le système source
Avantages :
- Déterministe : pas d’extraction aléatoire, pas d’OCR
- Source structurée : les données viennent directement du système comptable
Exemple pratique : mode ERP
Le mode ERP est l’approche recommandée pour la production. L’appel API envoie le PDF et les données structurées en parallèle.
curl
curl -X POST https://api.facturxapi.com/api/v1/convert \
-H "Authorization: Bearer votre-cle-api" \
-F "file=@./facture.pdf" \
-F 'invoice_data={
"invoice_number": "FA-2026-042",
"issue_date": "2026-04-01",
"invoice_type": "380",
"currency": "EUR",
"seller": {
"name": "Ma Société SAS",
"siret": "12345678900012",
"vat_id": "FR12345678901",
"address": {
"street": "10 Rue de Rivoli",
"city": "Paris",
"postal_code": "75001",
"country": "FR"
}
},
"buyer": {
"name": "Client SA",
"address": { "country": "FR" }
},
"line_items": [
{
"number": "1",
"description": "Prestation conseil",
"quantity": 1,
"unit": "C62",
"unit_price": 1000,
"net_amount": 1000,
"vat_rate": 20,
"vat_category": "S"
}
],
"tax_breakdown": [
{
"rate": 20,
"category": "S",
"base": 1000,
"amount": 200
}
],
"totals": {
"net": 1000,
"tax": 200,
"gross": 1200,
"due": 1200
}
}'
La réponse enveloppe le résultat dans success, target et result. Les artefacts sont encodés en base64 dans result.pdf et result.xml :
{
"success": true,
"target": {
"requested": "en16931",
"executed": "en16931",
"status": "verified",
"missing_inputs": [],
"not_run_reason": null
},
"result": {
"durationMs": 2500,
"targetProfile": "EN16931",
"conversionSuccessful": true,
"packagingPerformed": true,
"xml": "PD94bWwg...",
"xmlSize": 4096,
"pdf": "JVBERi0x...",
"pdfSize": 102400,
"extraction": {
"sourceType": "structured_data",
"pageCount": 1
},
"validation": {
"valid": true,
"profile": "EN16931",
"summary": { "errorCount": 0, "warningCount": 0 }
},
"processedInvoicesCharged": 1,
"remainingInvoices": 999
}
}
Python
import requests
import json
import base64
import os
from pathlib import Path
API_KEY = "votre-cle-api"
API_URL = "https://api.facturxapi.com/api/v1/convert"
invoice_data = {
"invoice_number": "FA-2026-042",
"issue_date": "2026-04-01",
"invoice_type": "380",
"currency": "EUR",
"seller": {
"name": "Ma Société SAS",
"siret": "12345678900012",
"vat_id": "FR12345678901",
"address": {
"street": "10 Rue de Rivoli",
"city": "Paris",
"postal_code": "75001",
"country": "FR",
},
},
"buyer": {
"name": "Client SA",
"address": {"country": "FR"},
},
"line_items": [
{
"number": "1",
"description": "Prestation conseil",
"quantity": 1,
"unit": "C62",
"unit_price": 1000,
"net_amount": 1000,
"vat_rate": 20,
"vat_category": "S",
}
],
"tax_breakdown": [
{
"rate": 20,
"category": "S",
"base": 1000,
"amount": 200,
}
],
"totals": {
"net": 1000,
"tax": 200,
"gross": 1200,
"due": 1200,
},
}
with open("facture.pdf", "rb") as f:
response = requests.post(
API_URL,
headers={"Authorization": f"Bearer {API_KEY}"},
files={"file": ("facture.pdf", f, "application/pdf")},
data={"invoice_data": json.dumps(invoice_data)},
timeout=90,
)
response.raise_for_status()
payload = response.json()
target = payload.get("target") or {}
result = payload.get("result") or {}
target_status = target.get("status")
if target_status not in {"verified", "incomplete"}:
raise ValueError(f"Conversion sans livrable exploitable: {payload}")
pdf_base64 = result.get("pdf")
xml_base64 = result.get("xml")
if result.get("packagingPerformed") is not True or not pdf_base64 or not xml_base64:
raise ValueError("Livrable PDF/XML absent de la réponse")
def write_base64_atomically(encoded, destination):
destination = Path(destination)
temporary = destination.with_name(f".{destination.name}.{os.getpid()}.tmp")
try:
decoded = base64.b64decode(encoded, validate=True)
if destination.suffix == ".pdf" and not decoded.startswith(b"%PDF-"):
raise ValueError("Le livrable décodé n'est pas un PDF")
temporary.write_bytes(decoded)
temporary.replace(destination)
finally:
temporary.unlink(missing_ok=True)
write_base64_atomically(pdf_base64, "facture-facturx.pdf")
write_base64_atomically(xml_base64, "factur-x.xml")
charged = result.get("processedInvoicesCharged")
remaining = result.get("remainingInvoices")
if target_status == "incomplete":
readiness = result.get("validationReadiness") or {}
reservations = (readiness.get("frCtc") or {}).get("advisories") or payload.get("warnings") or []
print(f"Fichier généré avec réserves — à traiter avant transmission: {reservations}")
else:
profile = result["validation"]["profile"]
print(f"Conversion vérifiée pour la cible demandée — profil {profile}")
print(f"Quota: {charged} facture traitée, {remaining} restante(s)")
Exemple pratique : mode extraction PDF
Quand les données structurées ne sont pas disponibles, l’API extrait les informations directement depuis le texte du PDF.
curl -X POST https://api.facturxapi.com/api/v1/convert \
-H "Authorization: Bearer votre-cle-api" \
-F "file=@./facture.pdf"
La réponse indique la méthode d’extraction utilisée :
{
"success": true,
"target": {
"requested": "en16931",
"executed": "en16931",
"status": "verified",
"missing_inputs": [],
"not_run_reason": null
},
"result": {
"durationMs": 4200,
"targetProfile": "EN16931",
"conversionSuccessful": true,
"packagingPerformed": true,
"xml": "PD94bWwg...",
"xmlSize": 3584,
"pdf": "JVBERi0x...",
"extraction": {
"sourceType": "native_text",
"pageCount": 1,
"extractionQualityScore": 0.92,
"extractionFieldsCount": 18
},
"validation": {
"valid": true,
"profile": "EN16931"
},
"processedInvoicesCharged": 1,
"remainingInvoices": 999
}
}
Le champ extraction.sourceType indique comment les données ont été obtenues :
native_text— le PDF contient une couche texte exploitableocr— le PDF est une image, le moteur OCR a été utiliséstructured_data— les données viennent du champinvoice_data(mode ERP)
Le champ extractionQualityScore (0 à 1) donne une indication globale de la fiabilité de l’extraction. En mode strict (défaut), une confiance inférieure à 0.70 provoque un rejet 422 avec le code ocr_confidence_too_low.
Validation intégrée
Chaque conversion exécute la séquence de contrôles documentaires du profil demandé et en expose le résultat. Les quatre étapes sont exécutées dans l’ordre :
- PDF/A-3 — conformité ISO 19005-3 via veraPDF
- Structure Factur-X — présence du XML embarqué avec les bons attributs
- XSD — validation structurelle du XML CII
- Schematron — validation des règles métier EN16931
La réponse de conversion expose le détail des contrôles exécutés dans le champ validation. Un livrable ne doit être présenté comme vérifié que si le profil demandé est effectivement marqué verified ; sinon, la réponse conserve les erreurs ou l’état non évalué. Cette vérification reste bornée aux profils exécutés et ne vaut ni transmission ni acceptation par une PA ou une solution compatible (SC).
Pour comprendre les erreurs Schematron et les déboguer, voir Valider EN16931/Factur-X : XSD vs Schematron, erreurs BR-*.
Quotas
Un appel abouti à /validate, /extract ou /repair décompte une facture traitée. Une génération /convert réserve une facture, décomptée si un livrable final est produit et remboursée sinon. Une simulation /convert avec dry_run=true ne consomme pas le quota et ne produit aucun fichier final.
Pour tester avec le plan Sandbox, consultez les quotas et conditions à jour sur la page pricing.
Gestion des erreurs
Les erreurs de conversion retournent un code HTTP avec un payload JSON structuré :
| Code HTTP | Erreur | Cause |
|---|---|---|
415 | Unsupported Media Type | Le fichier envoyé n’est pas un PDF |
422 | insufficient_data | Le texte extrait ne contient pas assez d’informations pour construire un XML CII valide |
422 | ocr_confidence_too_low | Le score de confiance OCR est inférieur au seuil (mode strict) |
402 | quota_exceeded | Quota mensuel épuisé |
429 | rate_limit_exceeded | Trop de requêtes simultanées |
503 | Service Unavailable | Service OCR temporairement indisponible |
Exemple de réponse 422 :
{
"error": "insufficient_data",
"message": "Cannot extract enough invoice fields from PDF text",
"missing_fields": ["seller.vat_id", "buyer.name"],
"diagnostics": [
{
"field": "seller.vat_id",
"reason": "No VAT number pattern found",
"suggestion": "Provide invoice_data with seller.vat_id"
}
]
}
La réponse diagnostics indique exactement quels champs manquent et suggère d’utiliser le mode ERP (invoice_data) pour les fournir explicitement. C’est le cas le plus fréquent : un PDF dont la mise en page ne permet pas d’extraire tous les champs obligatoires EN16931.
Idempotence et retries
En production, les appels réseau échouent. Un timeout, une déconnexion, une erreur 503 transitoire — le client doit pouvoir réessayer sans risque de double traitement.
L’API supporte le header Idempotency-Key :
curl -X POST https://api.facturxapi.com/api/v1/convert \
-H "Authorization: Bearer votre-cle-api" \
-H "Idempotency-Key: fa-2026-042-convert-v1" \
-F "file=@./facture.pdf" \
-F 'invoice_data={...}'
Si le serveur a déjà traité une requête avec la même clé d’idempotence, il retourne le résultat en cache au lieu de relancer la conversion. La clé doit être unique par opération logique — typiquement le numéro de facture combiné avec un suffixe de version.
Le pattern recommandé en production — la clé doit être stable pour la même opération logique (même facture, même payload) afin que les retries retournent le résultat en cache :
# Clé stable par facture — même clé à chaque retry = idempotence garantie
idempotency_key = f"convert-{invoice_number}-v1"
response = requests.post(
API_URL,
headers={
"Authorization": f"Bearer {API_KEY}",
"Idempotency-Key": idempotency_key,
},
files={"file": ("facture.pdf", pdf_file, "application/pdf")},
data={"invoice_data": json.dumps(invoice_data)},
timeout=90,
)
Si le payload métier change réellement (correction de montant, ajout d’une ligne), incrémentez la version : convert-FA-2026-042-v2.
Erreurs fréquentes lors de la conversion
Les rejets Schematron/veraPDF les plus communs sur un output /convert (avec lien vers la fiche dédiée) :
- BT-131 — Montant net de ligne incohérent — formule Peppol avec base quantity souvent oubliée en mode extraction
- BR-CO-10 — Somme des montants de ligne — arrondis ligne-par-ligne qui divergent du total document
- BR-CO-15 — Total avec TVA —
BT-112 = BT-109 + BT-110, souvent cassé après changement de taux TVA - BR-05 — Code devise obligatoire —
BT-5(InvoiceCurrencyCode) absent en mode ERP si le mapping ne l’expose pas - BR-AE-05 — Autoliquidation : taux 0 + VATEX-EU-AE — catégorie TVA
AEmal qualifiée (BTP, intra-UE B2B) - PDF/A-3 conformance incorrecte — le PDF source est PDF/A-1 ou PDF/A-2 au lieu de PDF standard
- « No Factur-X XML found » — survient en re-validation si le PDF output a été ré-imprimé et a perdu la pièce jointe
Le catalogue complet des erreurs BR-* liste les 22 fiches unitaires par famille.
Et maintenant ?
Convertir mon PDF et contrôler le résultat →
set -euo pipefail
response_file="$(mktemp "${TMPDIR:-/tmp}/facturx-response.XXXXXX")"
pdf_tmp="$(mktemp "./facture-facturx.pdf.tmp.XXXXXX")"
trap 'rm -f "$response_file" "$pdf_tmp"' EXIT
curl --fail-with-body --silent --show-error \
-X POST https://api.facturxapi.com/api/v1/convert \
-H "Authorization: Bearer VOTRE_CLE" \
-F "file=@./facture.pdf" \
--output "$response_file"
jq -e '
(.target.status == "verified" or .target.status == "incomplete") and
(.result.packagingPerformed == true) and
(.result.pdf | type == "string" and length > 0)
' "$response_file" > /dev/null
jq -er '.result.pdf' "$response_file" | base64 --decode > "$pdf_tmp"
test "$(head -c 5 "$pdf_tmp")" = "%PDF-"
mv "$pdf_tmp" facture-facturx.pdf
if jq -e '.target.status == "incomplete"' "$response_file" > /dev/null; then
jq -r '.result.validationReadiness.frCtc.advisories // .warnings // []' "$response_file" >&2
printf '%s\n' 'Fichier généré avec réserves : traitez-les avant transmission.' >&2
fi
La clé Sandbox permet de tester l’API sans carte bancaire. Le scanner public reste gratuit et séparé du quota API. Consultez les quotas et conditions à jour, puis obtenez une clé API.
Aller plus loin
La conversion est une étape du pipeline. Pour une intégration complète :
- Validation indépendante — Si vous recevez des Factur-X de tiers, validez-les avec l’endpoint
/validateavant intégration. Voir Valider EN16931/Factur-X : XSD vs Schematron. - Extraction XML — Pour extraire le XML d’un Factur-X reçu et l’intégrer dans votre ERP, voir Extraire l’XML d’un PDF Factur-X reçu.
- Mapping des champs ERP — Pour construire le JSON
invoice_datadepuis les champs de votre ERP, la cartographie des Business Terms est dans Champs obligatoires EN16931 : mapping ERP vers XML. - Documentation API — La documentation complète couvre tous les endpoints, formats de réponse et exemples de code.
La conversion produit un document Factur-X et indique les contrôles exécutés. Le dépôt, la transmission et l’acceptation par une PA restent un parcours séparé, décrit dans le guide technique 2026.