Valider manuellement chaque facture n’est pas tenable en production. Quand votre ERP génère des centaines de documents Factur-X par jour, une erreur de format qui passe inaperçue se propage sur toute une série avant d’être détectée — par le destinataire ou la PDP. Le coût de la correction manuelle a posteriori est sans commune mesure avec celui d’une validation automatisée en amont.
Cet article couvre l’automatisation de la validation et de la correction XML CII dans un pipeline CI/CD. Pour la conversion d’un PDF classique en Factur-X PDF/A-3, voir l’article dédié.
L’automatisation via CI/CD garantit que chaque document généré est conforme avant sa transmission. Si une mise à jour de votre ERP introduit une régression dans le XML CII — un format de date qui change, un namespace oublié, un champ conditionnellement obligatoire qui disparaît — le pipeline bloque la livraison immédiatement. Les erreurs de format réparables sont corrigées automatiquement, les erreurs de fond déclenchent une alerte à l’équipe.
Pour comprendre les deux niveaux de validation (XSD structurel et Schematron sémantique) et la logique des codes d’erreur BR-*, voir Valider EN16931 / Factur-X en pratique : XSD vs Schematron, erreurs BR-*.
Le pipeline type
Le flux de validation automatisé suit une logique en trois étapes : valider, réparer si possible, alerter sinon.
ERP génère XML/PDF → Validation API → Conforme ?
│
Oui ─────────┼──────────→ Transmission PDP
│
Non ─────────┼──→ Erreur réparable ?
│
Oui ─────────┼──→ Repair API → Re-validation → Conforme ?
│ │
Non ─────────┤ Oui ─────────┼──→ Transmission
│ │
└──→ Alerte équipe Non ─────────┘
(erreur de fond)
La distinction entre erreur réparable et erreur de fond est centrale. Les erreurs réparables sont des problèmes de format : dates en ISO au lieu de AAAAMMJJ, virgules au lieu de points décimaux, namespaces manquants. Le moteur Repair les corrige automatiquement avec un diff traçable. Les erreurs de fond — montant TTC incohérent, TVA mal calculée, champ métier absent — nécessitent une intervention humaine.
Portée du Repair : l’endpoint Repair corrige le XML CII structuré (dates, décimaux, namespaces, schemeID). Il ne corrige pas le conteneur PDF/A-3 ni les métadonnées XMP.
Pour le catalogue complet des erreurs réparables et le fonctionnement du moteur de correction, voir Corriger automatiquement un XML CII Factur-X invalide.
Script bash de validation
Un script réutilisable qui encapsule la logique de validation et retourne les codes de sortie appropriés pour l’intégration CI/CD.
#!/usr/bin/env bash
# validate-facturx.sh — Valide un fichier Factur-X via l'API
# Usage: ./validate-facturx.sh <fichier.pdf|fichier.xml>
# Codes retour: 0 = conforme, 1 = non conforme, 2 = erreur réseau/API
set -euo pipefail
FILE="${1:?Usage: $0 <fichier>}"
API_URL="${FACTURX_API_URL:-https://api.facturxapi.com/api/v1}"
API_KEY="${FACTURX_API_KEY:?Variable FACTURX_API_KEY requise}"
if [[ ! -f "$FILE" ]]; then
echo "ERREUR: fichier '$FILE' introuvable" >&2
exit 2
fi
# Validation
RESPONSE=$(curl -s -w "\n%{http_code}" \
-X POST "${API_URL}/validate" \
-H "X-API-Key: ${API_KEY}" \
-F "file=@${FILE}" \
--max-time 30)
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [[ "$HTTP_CODE" -lt 200 || "$HTTP_CODE" -ge 300 ]]; then
echo "ERREUR API: HTTP ${HTTP_CODE}" >&2
echo "$BODY" >&2
exit 2
fi
IS_VALID=$(echo "$BODY" | jq -r '.valid')
if [[ "$IS_VALID" == "true" ]]; then
echo "CONFORME: $(basename "$FILE")"
exit 0
fi
# Non conforme — afficher les erreurs
ERROR_COUNT=$(echo "$BODY" | jq '.errors | length')
echo "NON CONFORME: $(basename "$FILE") — ${ERROR_COUNT} erreur(s)" >&2
echo "$BODY" | jq -r '.errors[] | " [\(.severity)] \(.ruleId): \(.message)"' >&2
exit 1
Ce script sépare trois situations distinctes : le document conforme (code 0), le document non conforme (code 1), et l’erreur technique qui empêche la validation (code 2). Cette convention permet au pipeline CI/CD de distinguer un rejet métier d’un problème d’infrastructure.
Script de validation avec réparation automatique
En production, le pipeline complet tente la réparation avant de déclarer un échec. Rappel : l’endpoint Repair corrige le XML CII structuré (dates, décimaux, namespaces, schemeID). Il ne corrige pas le conteneur PDF/A-3 ni les métadonnées XMP.
#!/usr/bin/env bash
# validate-and-repair.sh — Valide, répare si possible, re-valide
# Usage: ./validate-and-repair.sh <fichier.xml>
# Codes retour: 0 = conforme (ou réparé), 1 = non réparable, 2 = erreur technique
set -euo pipefail
FILE="${1:?Usage: $0 <fichier.xml>}"
API_URL="${FACTURX_API_URL:-https://api.facturxapi.com/api/v1}"
API_KEY="${FACTURX_API_KEY:?Variable FACTURX_API_KEY requise}"
OUTPUT_DIR="${OUTPUT_DIR:-.}"
# Appel API avec capture du code HTTP et gestion d'erreurs
api_call() {
local endpoint="$1"
local file="$2"
local response http_code body
response=$(curl -s -w "\n%{http_code}" \
-X POST "${API_URL}/${endpoint}" \
-H "X-API-Key: ${API_KEY}" \
-F "file=@${file}" \
--max-time 30) || {
echo "ERREUR RÉSEAU: impossible de contacter l'API" >&2
return 2
}
http_code=$(echo "$response" | tail -1)
body=$(echo "$response" | sed '$d')
if [[ "$http_code" -lt 200 || "$http_code" -ge 300 ]]; then
echo "ERREUR API (${endpoint}): HTTP ${http_code}" >&2
echo "$body" >&2
return 2
fi
# Vérifier que la réponse est du JSON valide
if ! echo "$body" | jq empty 2>/dev/null; then
echo "ERREUR: réponse API non JSON (${endpoint})" >&2
return 2
fi
echo "$body"
}
# Étape 1 : validation initiale
RESULT=$(api_call "validate" "$FILE") || exit 2
IS_VALID=$(echo "$RESULT" | jq -r '.valid')
if [[ "$IS_VALID" == "true" ]]; then
echo "CONFORME: $(basename "$FILE")"
exit 0
fi
# Étape 2 : tentative de réparation
echo "Non conforme — tentative de réparation..." >&2
REPAIR_BODY=$(api_call "repair" "$FILE") || exit 2
# Sauvegarder le fichier réparé
REPAIRED_FILE="${OUTPUT_DIR}/$(basename "${FILE%.xml}")-repaired.xml"
echo "$REPAIR_BODY" | jq -r '.repairedXml' > "$REPAIRED_FILE"
FIXES_COUNT=$(echo "$REPAIR_BODY" | jq '.fixes | length')
echo "Réparation: ${FIXES_COUNT} correction(s) appliquée(s)" >&2
# Étape 3 : re-validation du fichier réparé
REVALIDATION=$(api_call "validate" "$REPAIRED_FILE") || exit 2
IS_VALID_AFTER=$(echo "$REVALIDATION" | jq -r '.valid')
if [[ "$IS_VALID_AFTER" == "true" ]]; then
echo "CONFORME APRÈS RÉPARATION: $(basename "$FILE")"
echo " Fichier réparé: ${REPAIRED_FILE}"
exit 0
fi
# Erreurs restantes = erreurs de fond, non réparables
REMAINING=$(echo "$REVALIDATION" | jq '.errors | length')
echo "NON RÉPARABLE: ${REMAINING} erreur(s) de fond restante(s)" >&2
echo "$REVALIDATION" | jq -r '.errors[] | " [\(.severity)] \(.ruleId): \(.message)"' >&2
exit 1
GitHub Actions : workflow complet
Ce workflow se déclenche sur chaque push dans le dossier invoices/, valide tous les fichiers, tente la réparation si nécessaire, et fait échouer le build si des erreurs de fond subsistent.
name: Validate Factur-X invoices
on:
push:
paths:
- 'invoices/**'
pull_request:
paths:
- 'invoices/**'
env:
API_URL: https://api.facturxapi.com/api/v1
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install jq
run: sudo apt-get install -y jq
- name: Validate all invoices
env:
FACTURX_API_KEY: ${{ secrets.FACTURX_API_KEY }}
run: |
FAILED=0
REPAIRED=0
PASSED=0
for file in invoices/*.{pdf,xml}; do
[ -f "$file" ] || continue
echo "--- Validation: $(basename "$file") ---"
# Validation initiale
RESULT=$(curl -s -X POST "${API_URL}/validate" \
-H "X-API-Key: ${FACTURX_API_KEY}" \
-F "file=@${file}" \
--max-time 30)
IS_VALID=$(echo "$RESULT" | jq -r '.valid')
if [ "$IS_VALID" = "true" ]; then
echo "✅ Conforme"
PASSED=$((PASSED + 1))
continue
fi
# Tentative de réparation (XML uniquement)
if [[ "$file" == *.xml ]]; then
echo "⚠️ Non conforme — tentative de réparation..."
REPAIR=$(curl -s -X POST "${API_URL}/repair" \
-H "X-API-Key: ${FACTURX_API_KEY}" \
-F "file=@${file}" \
--max-time 30)
FIXES=$(echo "$REPAIR" | jq '.fixes | length')
echo " ${FIXES} correction(s) appliquée(s)"
VALID_AFTER=$(echo "$REPAIR" | jq -r '.validation.valid // false')
if [ "$VALID_AFTER" = "true" ]; then
echo "✅ Conforme après réparation"
REPAIRED=$((REPAIRED + 1))
continue
fi
fi
# Échec définitif
echo "❌ Erreurs non réparables :"
echo "$RESULT" | jq -r '.errors[] | " [\(.severity)] \(.ruleId): \(.message)"'
FAILED=$((FAILED + 1))
done
echo ""
echo "=== Résumé ==="
echo "Conformes: ${PASSED}"
echo "Réparés: ${REPAIRED}"
echo "Échoués: ${FAILED}"
if [ "$FAILED" -gt 0 ]; then
echo "::error::${FAILED} facture(s) non conforme(s)"
exit 1
fi
- name: Notify on failure
if: failure()
uses: slackapi/slack-github-action@v1.27.0
with:
payload: |
{
"text": "🚨 Validation Factur-X échouée sur ${{ github.ref_name }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Validation Factur-X échouée*\nBranche: `${{ github.ref_name }}`\nCommit: `${{ github.sha }}`\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Voir le rapport>"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Le secret FACTURX_API_KEY doit être configuré dans Settings > Secrets and variables > Actions du repository. Ne jamais commiter la clé API en clair dans le workflow.
GitLab CI : équivalent
validate-facturx:
stage: test
image: alpine:3.20
before_script:
- apk add --no-cache curl jq bash
variables:
API_URL: "https://api.facturxapi.com/api/v1"
script:
- |
FAILED=0
for file in invoices/*.pdf invoices/*.xml; do
[ -f "$file" ] || continue
RESULT=$(curl -s -X POST "${API_URL}/validate" \
-H "X-API-Key: ${FACTURX_API_KEY}" \
-F "file=@${file}" \
--max-time 30)
IS_VALID=$(echo "$RESULT" | jq -r '.valid')
if [ "$IS_VALID" != "true" ]; then
echo "ÉCHEC: $(basename "$file")"
echo "$RESULT" | jq -r '.errors[] | " [\(.severity)] \(.ruleId): \(.message)"'
FAILED=$((FAILED + 1))
else
echo "OK: $(basename "$file")"
fi
done
[ "$FAILED" -eq 0 ] || exit 1
rules:
- changes:
- invoices/**
La variable FACTURX_API_KEY est définie dans Settings > CI/CD > Variables avec le flag Masked activé.
Gestion des erreurs dans le pipeline
Un pipeline robuste distingue trois catégories de résultat, chacune avec un comportement différent.
| Code retour | Signification | Action pipeline |
|---|---|---|
0 | Document conforme | Continuer vers la transmission |
1 | Document non conforme (erreur métier) | Bloquer + alerter l’équipe |
2 | Erreur technique (réseau, API indisponible) | Retry avec backoff, puis alerter |
Idempotency et retries
Pour les appels de réparation en contexte de retry automatique, incluez un header Idempotency-Key pour éviter les doubles traitements.
IDEM_KEY="repair-$(sha256sum "$file" | cut -d' ' -f1)"
curl -X POST "${API_URL}/repair" \
-H "X-API-Key: ${FACTURX_API_KEY}" \
-H "Idempotency-Key: ${IDEM_KEY}" \
-F "file=@${file}" \
--max-time 30 \
--retry 2 \
--retry-delay 5
L’Idempotency-Key est construite à partir du hash SHA-256 du fichier. Contrairement à une clé basée sur le nom de fichier ou l’identifiant du run CI, le hash garantit que deux fichiers différents portant le même nom ne partagent pas la même clé, et qu’un même fichier retried produit bien la même clé. Si le même appel est rejoué (timeout réseau, runner interrompu), l’API retourne le résultat déjà calculé sans retraiter le fichier.
Timeout et fallback
Configurez un timeout explicite (--max-time 30) sur chaque appel API. Si l’API ne répond pas dans le délai, curl retourne un code d’erreur non-zéro qui déclenche le chemin d’erreur technique (code 2). Un pipeline qui attend indéfiniment une réponse API bloque tout le workflow de déploiement.
Webhook et alerting en production
Au-delà du CI/CD, les factures générées en production (par un job cron, un webhook ERP, un flux temps réel) nécessitent un circuit d’alerte dédié.
notify_failure() {
local file="$1"
local errors="$2"
# Slack
curl -s -X POST "${SLACK_WEBHOOK_URL}" \
-H "Content-Type: application/json" \
-d "{
\"text\": \"Validation Factur-X échouée : ${file}\",
\"attachments\": [{
\"color\": \"danger\",
\"text\": $(echo "$errors" | jq -Rs .)
}]
}"
# Email via votre service (Brevo, SendGrid, etc.)
curl -s -X POST "https://api.brevo.com/v3/smtp/email" \
-H "api-key: ${BREVO_API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"sender\": {\"email\": \"alerts@votre-domaine.com\"},
\"to\": [{\"email\": \"comptabilite@votre-domaine.com\"}],
\"subject\": \"[ALERTE] Factur-X non conforme : ${file}\",
\"textContent\": \"Erreurs de validation :\\n${errors}\"
}"
}
L’alerte doit contenir le nom du fichier, la liste des erreurs avec leurs codes BR-*, et un lien vers le rapport complet. Sans contexte suffisant, l’équipe comptable ne peut pas diagnostiquer le problème.
Aller plus loin
- Comprendre les erreurs : le catalogue des erreurs BR-* EN16931 détaille chaque code d’erreur, sa cause racine, et la correction attendue.
- Convertir vos PDF : si votre ERP génère des PDF classiques sans XML embarqué, le guide Convertir une facture PDF en Factur-X PDF/A-3 couvre les deux modes de conversion (extraction et données structurées).
- Corriger automatiquement : pour comprendre quelles erreurs sont réparables et comment le moteur Repair fonctionne, voir Corriger automatiquement un XML CII invalide.
- Choisir votre validateur : si vous évaluez entre un validateur self-hosted et une API SaaS, le comparatif KoSIT, Mustangproject ou API SaaS couvre les options avec une matrice de décision.
- Valider XSD et Schematron : pour comprendre pourquoi un XML valide en structure peut échouer en sémantique, voir Valider EN16931 / Factur-X : XSD vs Schematron.