Technique

Corriger automatiquement un XML CII Factur-X invalide : API de réparation

9 min de lecture Par FacturX API

Réparer un XML CII (Factur-X / EN16931) invalide automatiquement : dates au mauvais format, décimaux avec virgule, namespaces manquants, schemeID absents. Validation avant/après avec diff détaillé.

Vous validez un XML CII et le rapport Schematron remonte 5, 10, 20 erreurs. Vous ouvrez le fichier, vous localisez la première, vous corrigez, vous revalidez. Une erreur de moins, quatre nouvelles — parce que la correction manuelle a cassé un namespace ou introduit un caractère invalide. Le cycle reprend.

Cet article traite de la réparation du XML structuré (le fichier CII embarqué dans un Factur-X). Il ne couvre pas la correction du conteneur PDF/A-3 ni des métadonnées XMP — ces aspects sont traités dans PDF/A-3 pour Factur-X : conformité et pièges.

Une part significative des erreurs rencontrées en pratique ne sont pas des problèmes de fond. Ce ne sont pas des montants incorrects ou des TVA mal calculées. Ce sont des problèmes de format : une date en ISO au lieu de AAAAMMJJ, une virgule au lieu d’un point décimal, un namespace oublié, un attribut schemeID absent.

L’API Repair corrige automatiquement ces erreurs de format, avec un diff complet de chaque modification et une validation avant/après pour prouver que la correction améliore la conformité. Chaque règle de réparation est documentée, chaque changement est traçable.

Pour le catalogue complet des erreurs BR-* et leurs causes racines, voir Catalogue des erreurs BR-* EN16931.

Quelles erreurs sont réparables automatiquement ?

Le moteur de réparation applique six règles, chacune identifiée par un code R0xx. Pour la plupart, la correction est déterministe (même entrée, même sortie). Quand la correction implique un choix (comme l’ajout d’un schemeID), le moteur applique une valeur par défaut documentée — que vous pouvez ajuster via les contraintes. Si la correction est ambiguë et qu’aucune valeur par défaut n’est applicable, le moteur ne touche pas au champ.

Les règles R003 et R004 existent en interne mais ne sont pas exposées publiquement dans cette version de l’API.

R001 — Format de date (2024-01-15 vers 20240115)

CII exige le format format="102" pour les dates, ce qui correspond à AAAAMMJJ sur 8 chiffres sans séparateur. Les ERP exportent presque toujours en ISO 8601 (2024-01-15), en format européen slash (15/01/2024), ou en format européen point (15.01.2024).

XPath concernés : ram:IssueDateTime, ram:DueDateDateTime, ram:OccurrenceDateTime, ram:BillingSpecifiedPeriod/ram:StartDateTime, ram:BillingSpecifiedPeriod/ram:EndDateTime.

Avant :

<ram:IssueDateTime>
  <udt:DateTimeString format="102">2024-01-15</udt:DateTimeString>
</ram:IssueDateTime>

Après :

<ram:IssueDateTime>
  <udt:DateTimeString format="102">20240115</udt:DateTimeString>
</ram:IssueDateTime>

Le format format="102" déclare explicitement que la valeur est au format AAAAMMJJ. Un validateur Schematron qui trouve 2024-01-15 avec format="102" va rejeter le document : la valeur ne correspond pas au format déclaré. R001 supprime les tirets, les slashs et les points, et réordonne les composants si nécessaire (DD/MM/YYYY vers YYYYMMDD).

R002 — Séparateur décimal (100,50 vers 100.50)

CII utilise le point comme séparateur décimal, conformément à la représentation XML standard des nombres. Les ERP français et allemands utilisent la virgule. C’est l’une des erreurs les plus fréquentes dans les exports XML depuis des systèmes localisés.

Avant :

<ram:GrandTotalAmount>1 234,56</ram:GrandTotalAmount>

Après :

<ram:GrandTotalAmount>1234.56</ram:GrandTotalAmount>

R002 traite aussi les séparateurs de milliers européens : 1.234,56 (point pour les milliers, virgule pour les décimales) devient 1234.56.

Cette règle est contrôlée par la contrainte do_not_modify_amounts. Par défaut, cette contrainte est à true — les montants ne sont pas modifiés. C’est une protection délibérée : changer un séparateur décimal change potentiellement la valeur financière du document. Pour activer R002, vous devez explicitement passer do_not_modify_amounts: false dans les contraintes.

R005 — Préfixes et namespaces XML

Un XML CII Factur-X utilise typiquement quatre namespaces déclarés sur l’élément racine :

<rsm:CrossIndustryInvoice
  xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
  xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
  xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
  xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">

Les générateurs XML maison oublient fréquemment qdt: ou udt:, ou déclarent les URN avec une faute de frappe. R005 vérifie chaque namespace attendu et corrige les déclarations manquantes ou erronées.

R006 — Attributs schemeID manquants

Certains éléments CII exigent un attribut schemeID pour identifier le système d’identification utilisé. Sans cet attribut, le validateur Schematron rejette le document.

Avant :

<ram:SpecifiedTaxRegistration>
  <ram:ID>FR12345678901</ram:ID>
</ram:SpecifiedTaxRegistration>

Après :

<ram:SpecifiedTaxRegistration>
  <ram:ID schemeID="VA">FR12345678901</ram:ID>
</ram:SpecifiedTaxRegistration>

R006 ajoute schemeID="VA" sur les identifiants de registration fiscale et schemeID="0088" sur les identifiants globaux (ram:GlobalID). Ces valeurs par défaut sont des heuristiques courantes, pas des certitudes : VA convient pour un numéro de TVA intracommunautaire, 0088 pour un GLN (Global Location Number). Si votre identifiant utilise un autre schéma (SIRET avec 0002, DUNS, etc.), la valeur ajoutée par R006 sera incorrecte. Dans ce cas, la correction doit se faire dans votre système source. R006 est une normalisation assistée — vérifiez toujours que le schemeID ajouté correspond à la nature réelle de l’identifiant.

R007 — Casse du code devise (eur vers EUR)

ISO 4217 exige des codes devise en majuscules. Un eur minuscule est techniquement invalide et sera rejeté par les règles BR-CL-* du Schematron.

Avant :

<ram:InvoiceCurrencyCode>eur</ram:InvoiceCurrencyCode>

Après :

<ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>

R007 normalise aussi l’attribut currencyID sur les montants de TVA (ram:TaxTotalAmount).

R008 — Eléments obligatoires manquants

Le profil EN16931 exige certains éléments structurels même quand ils sont vides. L’exemple le plus courant : ApplicableHeaderTradeDelivery, requis même pour les factures de services sans livraison physique.

R008 ajoute l’élément structurel si absent. Il ne remplit jamais de données fictives. Cette règle est contrôlée par la contrainte do_not_invent_missing_parties (défaut : true) qui empêche l’ajout de toute information de partie (vendeur, acheteur, noms, adresses).

Attention : dans certains cas, un simple bloc vide ne suffit pas. La FAQ ZUGFeRD/Factur-X indique qu’une structure minimale non vide peut être nécessaire selon le profil et les règles métier (par exemple, un ShipToTradeParty avec PostalTradeAddress et CountryID). R008 ajoute la structure de base — si le validateur exige des sous-éléments supplémentaires, complétez-les dans votre système source.

Le workflow en 3 phases

Le moteur de réparation ne modifie pas le XML “à l’aveugle”. Il suit un workflow en trois phases qui garantit la traçabilité de chaque modification :

Phase 1 — Validation AVANT. Le XML original est validé (XSD + Schematron). Le rapport capture l’état initial : nombre d’erreurs, codes BR-* déclenchés, sévérités.

Phase 2 — Application des règles. Les règles R001 à R008 sont appliquées dans un ordre déterministe. Chaque modification est enregistrée dans un DiffItem avec le XPath de l’élément modifié, la valeur avant, la valeur après, et le code de la règle appliquée.

Phase 3 — Validation APRES. Le XML réparé est revalidé avec les mêmes artefacts. Le rapport final permet de comparer : combien d’erreurs avant, combien après, lesquelles ont été corrigées.

Le résultat inclut les deux rapports de validation et le diff complet. Vous pouvez vérifier exactement ce qui a changé, pourquoi, et si la conformité a progressé.

Utilisation de l’API

Appel simple

curl -X POST https://api.facturxapi.com/api/v1/repair \
  -H "X-API-Key: votre-cle-api" \
  -F "file=@facture.xml"

Avec contraintes

curl -X POST https://api.facturxapi.com/api/v1/repair \
  -H "X-API-Key: votre-cle-api" \
  -F "file=@facture.xml" \
  -F 'constraints={"max_changes_allowed":10,"do_not_modify_amounts":false}'

Réponse type

{
  "durationMs": 142,
  "repaired_xml": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4...",
  "diff_summary": [
    {
      "code": "format_corrected",
      "path": "/rsm:CrossIndustryInvoice/rsm:ExchangedDocument/ram:IssueDateTime/udt:DateTimeString",
      "message": "Format de date corrige de '2024-01-15' vers '20240115'",
      "beforeValue": "2024-01-15",
      "afterValue": "20240115",
      "ruleReference": "R001",
      "changeCategory": "correction"
    },
    {
      "code": "attribute_added",
      "path": "/rsm:CrossIndustryInvoice/.../ram:SpecifiedTaxRegistration/ram:ID/@schemeID",
      "message": "Attribut schemeID='VA' manquant ajoute",
      "afterValue": "VA",
      "ruleReference": "R006",
      "changeCategory": "correction"
    },
    {
      "code": "format_corrected",
      "path": "/rsm:CrossIndustryInvoice/.../ram:InvoiceCurrencyCode",
      "message": "Casse du code devise corrigee de 'eur' vers 'EUR'",
      "beforeValue": "eur",
      "afterValue": "EUR",
      "ruleReference": "R007",
      "changeCategory": "normalization"
    }
  ],
  "validation_before": {
    "valid": false,
    "summary": { "errorCount": 3, "warningCount": 0 }
  },
  "validation_after": {
    "valid": true,
    "summary": { "errorCount": 0, "warningCount": 0 }
  }
}

Le champ repaired_xml contient le XML réparé encodé en base64. Décodez-le pour obtenir le XML corrigé prêt à être ré-embarqué dans un PDF/A-3.

Exemple Python

import base64
import requests

API_URL = "https://api.facturxapi.com/api/v1/repair"
API_KEY = "votre-cle-api"

with open("facture.xml", "rb") as f:
    response = requests.post(
        API_URL,
        headers={"X-API-Key": API_KEY},
        files={"file": ("facture.xml", f, "application/xml")},
        data={"constraints": '{"do_not_modify_amounts": false}'},
    )

result = response.json()

# Afficher les corrections appliquées
for diff in result["diff_summary"]:
    print(f"[{diff['ruleReference']}] {diff['message']}")

# Sauvegarder le XML repare
if result["validation_after"]["valid"]:
    repaired_xml = base64.b64decode(result["repaired_xml"])
    with open("facture_réparée.xml", "wb") as out:
        out.write(repaired_xml)
    print(f"XML réparé sauvegardé ({len(repaired_xml)} octets)")
else:
    remaining = result["validation_after"]["summary"]["errorCount"]
    print(f"Reparation partielle : {remaining} erreur(s) restante(s)")

Les contraintes de sécurité

L’API Repair accepte un objet JSON constraints qui contrôle le comportement du moteur :

ContrainteDéfautRôle
max_changes_allowed50Nombre maximum de modifications autorisées. Au-delà, l’API retourne une erreur 422.
do_not_modify_amountstrueProtège les montants financiers. Aucun ram:*Amount n’est modifié tant que ce flag est actif.
do_not_invent_missing_partiestrueEmpêche l’ajout de données de parties (noms, adresses, identifiants de vendeur/acheteur).
target_profileauto-détectéProfil Factur-X cible (MINIMUM, BASIC_WL, BASIC, EN16931, EXTENDED).

Les défauts sont conservateurs par conception. En production, gardez do_not_modify_amounts: true sauf si vous avez vérifié que votre ERP exporte bien les montants corrects avec un séparateur incorrect. Modifier un montant financier dans un XML de facturation est une opération sensible — la contrainte existe pour forcer cette décision consciente.

Ce que Repair ne corrige PAS

Le moteur de réparation corrige les erreurs de format, pas les erreurs de fond. La distinction est nette :

Erreurs de format (réparables) : la donnée est correcte, sa représentation XML est incorrecte. Une date 2024-01-15 contient la bonne information, mais le format ne correspond pas à ce que CII attend.

Erreurs de fond (non réparables) : la donnée elle-même est incorrecte ou absente. Le total TTC ne correspond pas à la somme HT + TVA. Le numéro de TVA est invalide. Le vendeur est absent du document.

Exemples concrets d’erreurs que Repair ne peut pas corriger :

  • BR-CO-10, BR-CO-15 — incohérences arithmétiques entre les totaux. Si GrandTotalAmount ne correspond pas à TaxBasisTotalAmount + TaxTotalAmount, c’est un bug dans le calcul de l’ERP, pas un problème de format.
  • BR-S-08, BR-S-09 — taux de TVA incohérent avec le montant. Le moteur ne recalcule pas la TVA.
  • BR-02, BR-05, BR-06 — champs obligatoires manquants (numéro de facture, nom du vendeur, nom de l’acheteur). Ce sont des données métier que seul le système source peut fournir.

Pour ces erreurs, la correction doit se faire dans le système source (ERP, logiciel de facturation, générateur XML). Le moteur Repair ne remplace pas la qualité des données en entrée — il corrige les artefacts de formatage qui polluent le rapport de validation.

Pour le détail des deux niveaux de validation (XSD vs Schematron), voir Valider EN16931/Factur-X : XSD vs Schematron, erreurs BR-*.

Intégration dans un pipeline

Le pattern recommandé pour intégrer Repair dans un pipeline de traitement automatisé :

XML source → Validate → erreurs réparables ? → Repair → Validate → suite du pipeline

Étape 1 : Validation initiale. Appelez /api/v1/validate pour obtenir le rapport complet. Si le XML est valide, passez directement à la suite.

Étape 2 : Réparation conditionnelle. Si des erreurs sont détectées, appelez /api/v1/repair. L’API inclut la validation avant et après dans sa réponse — pas besoin d’un appel validate séparé.

Étape 3 : Vérification. Vérifiez validation_after.valid dans la réponse. Si true, le XML réparé est prêt. Si false, les erreurs restantes sont des erreurs de fond qui nécessitent une intervention humaine ou une correction dans le système source.

Coût en quota : un appel Repair consomme 2 unités de quota (la validation avant et la validation après sont incluses).

Idempotence : pour les retries en cas d’erreur réseau, utilisez le header Idempotency-Key. Le même appel avec la même clé et le même payload retourne le même résultat sans retraitement. Un même Idempotency-Key avec un payload différent retourne une erreur 409 Conflict.

curl -X POST https://api.facturxapi.com/api/v1/repair \
  -H "X-API-Key: votre-cle-api" \
  -H "Idempotency-Key: repair-facture-2024-00142" \
  -F "file=@facture.xml"

Aller plus loin

#factur-x #repair #correction #EN16931 #XML #CII #schematron #API