Une facture XML qui ne respecte pas EN16931 se découvre trop souvent au rejet de la plateforme — donc en production, chez le client. Le bon endroit pour l’attraper est le commit.
Cet article montre comment poser, dans un workflow GitHub Actions, la validation officielle ConnectingEurope EN16931 1.3.16 (CII et UBL) sur vos XML. Cinq lignes à coller. Pas de JDK à installer dans votre image, pas de Saxon à compiler, pas de Schematron à transformer vous-même.
Périmètre, tout de suite. L’Action lit un XML CII ou UBL. Elle n’ouvre pas un PDF Factur-X, elle n’inspecte pas le conteneur PDF/A-3, et elle n’applique pas les règles France (BR-FR / CTC). Un job vert signifie : les feuilles XSLT 1.3.16 vendored n’ont produit aucun svrl:failed-assert sur les fichiers passés. Ce n’est pas une certification, ni un feu vert plateforme.
Pour brancher l’API (PDF/A-3, réparation, profil France, rapport JSON unifié) plutôt que l’Action, voir Automatiser la validation Factur-X dans un pipeline CI/CD.
En bref
- Action :
facturxapi/validate-einvoice@v1— le tagv1pointe aujourd’hui le commit8457406078fee1807c6e6604852b1764fe537c62. Pour geler l’arbre, épinglez le SHA plutôt que@v1. - Ce qui tourne : XSLT ConnectingEurope EN16931 1.3.16 (CII + UBL), vendored. Zéro appel réseau, zéro télémétrie. Les octets de facture ne sont pas journalisés.
- Ce que vous voyez en cas d’échec : une annotation GitHub dont le titre est l’identifiant de règle (
BR-CO-16,BR-02…) et le texte est lesvrl:textofficiel. - Fixtures : le dépôt public
facturxapi/en16931-oraclespublie 10 exemples CEN qui doivent passer, et 10 mutants qui doivent échouer — dont un qui doit tirerBR-CO-16. - Preuve CI du corpus : le workflow Verify EN16931 oracle receipts a rejoué les 10 recettes officielles le 17 août 2026 — run 32081271235, conclusion
success, hash attendudffb88780654fb4861df84bbd6df18aae5d89b0a5b8f4fd12ce5fb5f9a7f0dab.
1. Le quickstart
Dans .github/workflows/validate-invoices.yml :
name: Validate EN16931 invoices
on:
pull_request:
paths: ['**/*.xml']
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: facturxapi/validate-einvoice@v1
with:
files: invoices/**/*.xml
C’est tout. L’Action installe Python 3.12 et SaxonC-HE 13.0 sur le runner, charge les XSLT vendored, et échoue le job dès qu’un svrl:failed-assert apparaît (fail-on: failed-assert par défaut).
Exemple plus complet — syntaxe forcée, rapport hashé — dans examples/validate-invoices.yml.
Ce que vous voyez quand le XML est faux
Sur le job action mutants du dépôt de l’Action (ubuntu-latest, run 32080965759 — ce job est conçu pour rester rouge sur les mutants), GitHub a annoté, entre autres :
testdata/mutants/CII_example1.xml
BR-CO-16
[BR-CO-16]-Amount due for payment (BT-115) = Invoice total amount with VAT (BT-112) -Paid amount (BT-113) +Rounding amount (BT-114).
testdata/mutants/CII_example1.xml
BR-CO-15
[BR-CO-15]-Invoice total amount with VAT (BT-112) = Invoice total amount without VAT (BT-109) + Invoice total VAT amount (BT-110).
Même fichier, même mutation (le GrandTotalAmount de CII_example1.xml passé de 250.33 à 250.34) : deux règles, deux messages lisibles, la PR ne merge pas. Le détail des 10 mutants est dans MUTANTS.md.
fail-on accepte aussi never (rapport seulement) ou une liste d’identifiants (BR-CO-16,BR-02) si vous voulez d’abord bloquer une famille de règles.
2. Ce que la validation couvre
EN16931 se contrôle en deux passes indépendantes :
| Couche | Question | Exemple d’échec |
|---|---|---|
| XSD | La forme est-elle celle du schéma CII / UBL ? Types, cardinalités, ordre. | Élément au mauvais niveau, type non numérique |
| Schematron (XSLT 1.3.16) | Les règles métier tiennent-elles ? Totaux, TVA, champs conditionnels, codes. | BR-CO-16 si BT-115 ≠ BT-112 − BT-113 + BT-114 |
L’Action exécute les XSLT officielles ConnectingEurope validation-1.3.16 :
| Syntaxe | Fichier vendored | SHA256 |
|---|---|---|
| CII | vendor/en16931-1.3.16/xslt/EN16931-CII-validation.xslt | 0b234dea2bbfee739b7761e607a992c17fab88773014ef56355b6158cfb1cc53 |
| UBL | vendor/en16931-1.3.16/xslt/EN16931-UBL-validation.xslt | 39f9d282867f1a49e7708d9e29a53da89643e1ee56f10cec1ebcf1277595fcbd |
Chaque fichier du rapport JSON porte : path, syntax, verdict, failed_assert_ids, sha256 du XML, et le SHA256 de la XSLT utilisée. Deux runs consécutifs des mêmes octets produisent le même report-sha256.
Les identifiants BR-* sont ceux du Schematron officiel, pas une taxonomie maison. Pour la cause, le fragment XML et le correctif de chaque code fréquent, voir le catalogue des erreurs BR-* EN16931. Pour le couple XSD / Schematron et le workflow de debug : Valider EN16931 / Factur-X en pratique. Fiche unitaire de la règle du mutant ci-dessus : BR-CO-16.
3. Brancher les fixtures qui doivent échouer
Un job toujours vert ne prouve rien : il peut simplement ne rien exécuter. Le dépôt en16931-oracles sert de corpus de non-régression.
Doit passer — 10 exemples CEN, 0 failed-assert chacun, hash RESULTS.sha256 = dffb8878…9a7f0dab. Rejoué en public le 17 août 2026 (run 32081271235).
Doit échouer — 10 mutants documentés. Pour BR-CO-16 vous avez deux fichiers :
| Fichier | Mutation | Ids SVRL attendus |
|---|---|---|
mutants/CII_example1.xml | GrandTotalAmount 250.33 → 250.34 | BR-CO-15, BR-CO-16 |
mutants/ubl-tc434-creditnote1.xml | PayableAmount 100.11 → 101.11 | BR-CO-16 seul |
Dans votre workflow, un second job peut vérifier qu’un mutant reste rouge :
must-fail-br-co-16:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: facturxapi/validate-einvoice@v1
id: mutant
continue-on-error: true
with:
files: fixtures/mutants/ubl-tc434-creditnote1.xml
fail-on: BR-CO-16
- name: This file must fail on BR-CO-16
if: steps.mutant.outputs.verdict != 'fail'
run: |
echo "expected verdict=fail on BR-CO-16, got ${{ steps.mutant.outputs.verdict }}"
exit 1
Si demain une mise à jour de XSLT rend ce mutant silencieux, c’est votre CI qui le dit — pas un rejet chez le client.
Pourquoi deux validateurs peuvent malgré tout afficher des verdicts différents sur le même XML, et comment trancher : Pourquoi deux validateurs donnent des verdicts différents sur la même facture.
4. Ce que l’Action ne fait pas
Ces limites sont le périmètre, pas un défaut caché.
| Besoin | Dans l’Action ? | Où le traiter |
|---|---|---|
| Conteneur PDF/A-3, XMP, pièce jointe Factur-X | Non — l’Action ne lit pas les PDF | POST /api/v1/validate |
| Réparation automatique (dates, décimaux, namespaces) | Non | /repair |
| Profil France / règles BR-FR-Flux2 | Non — hors ConnectingEurope 1.3.16 | documentation API, artefacts FNFE-MPE |
| Rapport HTML partageable avec un non-dev | Non — JSON + annotations + job summary | API / scan |
| Peppol BIS, XRechnung CIUS, certification | Non | hors périmètre déclaré du README |
Le README de l’Action le dit en toutes lettres : « A green job means: the vendored 1.3.16 stylesheets produced zero svrl:failed-assert on the files you passed. The Action does not call a remote API, does not read PDF attachments, and does not certify a document. »
Le runner d’exemple est ubuntu-latest. C’est l’environnement sur lequel les jobs official et mutants du dépôt sont verts (même run 32080965759).
Voir aussi
- en16931-oracles — exemples CEN, mutants, recettes SVRL, badge CI
- validate-einvoice — Action + CLI local (
python3 scripts/selftest.py) - Documentation API — PDF/A-3, profil France, rapport unifié
- Catalogue BR-*
Continuer la lecture
Articles liés
Pourquoi deux validateurs donnent des verdicts différents sur la même facture
Même XML, trois verdicts : versions de Schematron, agrégation des warnings, identifiants différents pour la même règle. Comment trancher avec le validateur ITB de la Commission et un corpus à recettes signées.
Lire l’article →EN16931 validé, France CTC à corriger : ce qu'un validateur générique ne voit pas
Pourquoi une facture Factur-X peut passer une validation EN16931 puis remonter des écarts France CTC, comment lire ce verdict documentaire et quelle action choisir avant transmission officielle.
Lire l’article →Pourquoi valider une facture électronique ne se résume pas à « valide / invalide »
Une facture peut être lisible, réparable ou générable sans être prête à transmettre. Découvrez les neuf niveaux d’un diagnostic documentaire fiable.
Lire l’article →Étape suivante recommandée
Vous générez déjà le XML et vous voulez aussi le conteneur Factur-X, le profil France ou un rapport à transmettre.
Voici ce que vous allez obtenir :
- Valider le PDF/A-3 et le XML embarqué
- Lire un rapport avec code BR-* et XPath
- Enchaîner repair sur les erreurs de format
- Garder l'Action pour le garde-fou CI