Factur-X

API HTTP

Trois endpoints. Tout est en JSON ou multipart, réponses en PDF binaire (sauf /health). Aucune authentification — limite de débit nginx : 10 req/s par IP.

POST

/generate

Génère une facture Factur-X (PDF/A-3 + XML CII embarqué, profil EN 16931). Le PDF visuel est produit côté serveur à partir des données structurées.

Requête

Content-Type: application/json

{
  "invoice_number": "2025-001",        // requis
  "invoice_date":   "2025-05-21",      // requis, ISO 8601 (YYYY-MM-DD)
  "currency":       "EUR",             // optionnel, défaut EUR
  "due_date":       "2025-06-21",      // optionnel
  "delivery_date":  "2025-05-21",      // optionnel (date de prestation)
  "payment_terms":  "Paiement à 30 jours",  // optionnel
  "iban":           "FR76 1234...",    // optionnel, affiché dans le pied de page
  "legal_notice":   "Texte légal personnalisé",  // optionnel

  "seller": {                          // requis
    "name":       "Entreprise XYZ",    // requis
    "address":    "123 Rue de la Paix\n75001 Paris",  // requis
    "vat_id":     "FR12345678901",     // optionnel, préfixe pays auto-ajouté si absent
    "country":    "FR",                // optionnel, défaut FR
    "siren":      "123456789",         // optionnel
    "siret":      "12345678900012",    // optionnel (prioritaire sur siren)
    "legal_form": "SARL au capital de 10 000 €",  // optionnel
    "email":      "contact@xyz.fr",    // optionnel
    "phone":      "+33 1 23 45 67 89"  // optionnel
  },

  "buyer": {                           // requis
    "name":      "Client ABC",         // requis
    "address":   "456 Avenue Victor Hugo\n69002 Lyon",  // requis
    "vat_id":    "FR98765432109",      // optionnel
    "country":   "FR",                 // optionnel, défaut FR
    "siren":     "987654321",          // optionnel
    "reference": "PO-2025-042"         // optionnel (référence acheteur, BT-10)
  },

  "items": [                           // requis, au moins 1 ligne
    {
      "description": "Service de développement",  // requis
      "quantity":    10,               // requis (nombre)
      "unit_price":  150.00,           // requis (HT)
      "vat_rate":    20.0              // requis (%, ex. 20 / 5.5 / 0)
    }
  ]
}

Réponse

  • 200 · application/pdf — le PDF Factur-X (Content-Disposition: attachment)
  • 400 · application/json — payload absent ou invalide : {"error":"..."}
  • 500 · application/json — erreur de génération (XSD/schematron) : {"error":"...","trace":"..."}

Exemple — curl

curl -X POST https://facture-electronique.chevay.fr/generate \
  -H "Content-Type: application/json" \
  -d @facture.json \
  -o facture.pdf

Exemple — Python

import requests

payload = {
    "invoice_number": "2025-001",
    "invoice_date": "2025-05-21",
    "seller": {"name": "XYZ", "address": "123 Rue...", "vat_id": "FR12345678901"},
    "buyer":  {"name": "ABC", "address": "456 Avenue..."},
    "items":  [{"description": "Dev", "quantity": 10, "unit_price": 150, "vat_rate": 20}],
}
r = requests.post("https://facture-electronique.chevay.fr/generate", json=payload)
r.raise_for_status()
open("facture.pdf", "wb").write(r.content)

Exemple — JavaScript (fetch)

const resp = await fetch("https://facture-electronique.chevay.fr/generate", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(payload),
});
const blob = await resp.blob();
// blob est un PDF, à sauvegarder ou afficher
POST

/merge

Fusionne un PDF existant et un XML Factur-X / CII en un PDF/A-3 conforme (métadonnées XMP, pièce jointe avec relation Alternative).

Requête

Content-Type: multipart/form-data

ChampTypeDescription
pdffilerequis PDF source, sera converti en PDF/A-3
xmlfilerequis XML Factur-X / CII à embarquer
levelstringopt. autodetect (défaut), minimum, basicwl, basic, en16931, extended
langstringopt. Code langue ex. fr-FR
check_xsdstringopt. true (défaut) / false — désactive la validation XSD + schematron

Réponse

  • 200 · application/pdf — PDF/A-3 fusionné
  • 400 — fichiers manquants : {"error":"Both 'pdf' and 'xml' file fields are required"}
  • 500 — erreur de validation ou de fusion

Exemple — curl

curl -X POST https://facture-electronique.chevay.fr/merge \
  -F pdf=@invoice.pdf \
  -F xml=@factur-x.xml \
  -F level=en16931 \
  -o factur-x.pdf

Exemple — Python

import requests

with open("invoice.pdf", "rb") as p, open("factur-x.xml", "rb") as x:
    r = requests.post(
        "https://facture-electronique.chevay.fr/merge",
        files={"pdf": p, "xml": x},
        data={"level": "en16931"},
    )
r.raise_for_status()
open("factur-x.pdf", "wb").write(r.content)
GET

/health

Vérification de l'état du service. À utiliser pour les health checks (uptime monitoring, CI).

Réponse

HTTP/1.1 200 OK
Content-Type: application/json

{"status":"ok"}

Notes

  • La validation Factur-X tourne en deux temps : XSD (structure XML) puis schematron (règles métier EN 16931). Une violation des deux remonte dans le champ error de la réponse 500.
  • Le n° de TVA fourni sans préfixe pays est auto-préfixé avec le country de la partie (défaut FR).
  • Les montants sont calculés côté serveur en Decimal, le unit_price est interprété comme HT.
  • Taille maximale d'upload pour /merge : 25 MB (réglé au niveau nginx).