Technique

Valider EN16931 / Factur-X dans GitHub Actions

Mis à jour le 5 min de lecture Par FacturX API

Bloquer une PR dès qu'un XML CII ou UBL déclenche une règle EN16931. Action facturxapi/validate-einvoice@v1, codes BR-* et fixtures publiques.

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 tag v1 pointe aujourd’hui le commit 8457406078fee1807c6e6604852b1764fe537c62. 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 le svrl:text officiel.
  • Fixtures : le dépôt public facturxapi/en16931-oracles publie 10 exemples CEN qui doivent passer, et 10 mutants qui doivent échouer — dont un qui doit tirer BR-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 attendu dffb88780654fb4861df84bbd6df18aae5d89b0a5b8f4fd12ce5fb5f9a7f0dab.

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 :

CoucheQuestionExemple d’échec
XSDLa 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 :

SyntaxeFichier vendoredSHA256
CIIvendor/en16931-1.3.16/xslt/EN16931-CII-validation.xslt0b234dea2bbfee739b7761e607a992c17fab88773014ef56355b6158cfb1cc53
UBLvendor/en16931-1.3.16/xslt/EN16931-UBL-validation.xslt39f9d282867f1a49e7708d9e29a53da89643e1ee56f10cec1ebcf1277595fcbd

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 :

FichierMutationIds SVRL attendus
mutants/CII_example1.xmlGrandTotalAmount 250.33 → 250.34BR-CO-15, BR-CO-16
mutants/ubl-tc434-creditnote1.xmlPayableAmount 100.11 → 101.11BR-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é.

BesoinDans l’Action ?Où le traiter
Conteneur PDF/A-3, XMP, pièce jointe Factur-XNon — l’Action ne lit pas les PDFPOST /api/v1/validate
Réparation automatique (dates, décimaux, namespaces)Non/repair
Profil France / règles BR-FR-Flux2Non — hors ConnectingEurope 1.3.16documentation API, artefacts FNFE-MPE
Rapport HTML partageable avec un non-devNon — JSON + annotations + job summaryAPI / scan
Peppol BIS, XRechnung CIUS, certificationNonhors 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

Continuer la lecture

É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
#CI/CD #GitHub Actions #EN16931 #factur-x #validation #schematron #BR-*