Générer un Factur-X en Python
Une facture Factur-X n'est pas un PDF avec un logo : c'est un PDF/A-3 qui embarque un XML CII conforme au profil EN 16931, et qui doit passer les contrôles XSD puis Schematron. Voici comment en produire une depuis du Python, et surtout comment savoir qu'elle est réellement valide.
Ce que « conforme » veut dire, concrètement
Trois couches se superposent, et un document peut échouer à n'importe laquelle sans que le PDF ait l'air cassé :
| Couche | Ce qui est vérifié | Symptôme si ça casse |
|---|---|---|
| PDF/A-3 | Le PDF est archivable et porte une pièce jointe factur-x.xml | Le lecteur du destinataire ne trouve aucune donnée structurée |
| XSD | Le XML CII respecte le schéma (types, ordre, cardinalités) | Rejet au dépôt, message technique illisible |
| Schematron | Les règles métier : totaux cohérents, TVA ventilée, mentions légales | Rejet avec un code de règle, ex. BR-CO-13 |
C'est la troisième couche qui pose problème en pratique. Un PDF/A-3 bien formé avec un XML syntaxiquement valide peut très bien annoncer un total TTC qui ne correspond pas à la somme de ses lignes — et sera refusé.
Écrire le XML soi-même, ou appeler une API ?
Les deux approches sont légitimes ; elles n'ont simplement pas le même coût dans le temps.
| Bibliothèque locale | API (Confactura) | |
|---|---|---|
| Mise en route | Construire le CII, gérer le PDF/A-3, brancher un moteur Schematron | Un POST de votre JSON de facture |
| Validation | À câbler et à maintenir vous-même (Saxon requis pour Schematron) | Systématique : un document invalide n'est jamais livré |
| Évolutions réglementaires | Votre veille, vos correctifs | Côté serveur |
| Données | Restent chez vous | Traitées en Europe, aucune facture stockée |
| Runtime | Une JVM ou Saxon à opérer en production | Aucun |
Si votre produit émet quelques factures par an, la bibliothèque suffit. S'il en émet pour vos clients, c'est la maintenance de la conformité qui coûte, pas la première génération.
Générer une facture en Python
Aucune dépendance exotique : un POST JSON, une réponse en octets. Cet
exemple utilise httpx, mais requests ou
urllib fonctionnent à l'identique.
import httpx
API = "https://confactura.fr"
KEY = "df_live_…" # POST /v1/signup pour en obtenir une (gratuit)
invoice = {
"number": "2026-0042",
"issue_date": "2026-09-01",
"due_date": "2026-09-30",
"seller": {
"name": "Ma Société",
"address_line": "1 rue X",
"postcode": "75001",
"city": "Paris",
"siren": "123456789",
"vat_id": "FR32123456789",
"electronic_address": "[email protected]",
},
"buyer": {
"name": "Client SARL",
"address_line": "2 av. Y",
"postcode": "69001",
"city": "Lyon",
"electronic_address": "[email protected]",
},
"lines": [
{"description": "Presta dev", "quantity": 5, "unit_price": "450.00"},
],
}
r = httpx.post(
f"{API}/v1/invoices/facturx",
headers={"X-API-Key": KEY},
json=invoice,
timeout=60,
)
if r.status_code == 422:
# Facture non conforme : rapport détaillé, aucun PDF produit.
for err in r.json()["errors"]:
print("règle en échec :", err)
else:
r.raise_for_status()
with open("facture-2026-0042.pdf", "wb") as f:
f.write(r.content) # PDF/A-3b, XML CII embarqué
Le 422 est une réponse métier, pas une panne
C'est le point le plus important de cette intégration : une facture qui ne passe pas XSD + Schematron n'est jamais livrée. Votre code doit traiter le 422 comme un cas normal — typiquement en le remontant à l'utilisateur qui a saisi la facture — et non comme une erreur serveur à retenter en boucle.
Les champs que la réforme française rend obligatoires
Le profil EN 16931 seul ne suffit pas : le Schematron CTC français ajoute ses propres règles. Trois oublis expliquent la quasi-totalité des 422 au premier essai.
electronic_addresssur le vendeur et l'acheteur (règlesBR-FR-12/BR-FR-13) — c'est l'adresse de routage, pas un contact commercial ;vat_idcôté vendeur, dès qu'une ligne est en TVA standard (BR-S-02) ;- un
sirenvalide côté vendeur.
Les mentions légales 2026 — frais de recouvrement, pénalités de retard,
escompte, catégorie d'opération — sont ajoutées automatiquement côté serveur.
C'est l'objet de la règle
BR-FR-05, la plus
fréquemment rencontrée par ceux qui construisent leur XML à la main.
Vérifier — sans se croire sur parole
Un PDF qui s'ouvre correctement ne prouve rien du tout. Deux contrôles utiles :
- Déposez le fichier produit sur le validateur Factur-X en ligne — gratuit, sans inscription : il ré-extrait le XML embarqué et rejoue XSD + Schematron ;
- En intégration continue, faites-le par API :
with open("facture-2026-0042.pdf", "rb") as f:
report = httpx.post(f"{API}/v1/validate", files={"file": f}).json()
assert report["valid"], report["errors"]
/v1/validate est gratuit, anonyme et ne consomme aucun quota :
rien n'empêche de l'appeler à chaque exécution de vos tests.
Récupérer le XML seul
Si vous gardez votre propre mise en page PDF, ou si votre Plateforme
Agréée attend du CII brut, POST /v1/invoices/xml renvoie le XML
sans le PDF — même corps de requête, mêmes validations.
Essayer maintenant
20 documents par mois gratuits, sans carte bancaire. La clé API est créée en une requête.
Obtenir ma clé API Valider un PDF existant