Intégration ERP / GED

Générez vos factures Factur-X depuis votre ERP — sans moteur PDF/XML à maintenir

Envoyez votre JSON de facture. L’API génère soit un PDF/A-3 Factur-X avec son XML CII embarqué, soit le XML CII seul. Elle distingue le livrable obtenu du niveau de validation réellement exécuté.

Le test en direct utilise le moteur réel et affiche le niveau obtenu. Créez une clé API seulement lorsque vous êtes prêt à intégrer l’appel dans votre environnement.

Depuis Python ou Node.js : des clients publiés

Vous n'avez pas à écrire le client HTTP : pip install facturx-api (Python) et npm install @facturxapi/sdk (Node.js) font la simulation, la conversion et la validation. Licence MIT ; PyPI, npm, code source. PHP, Laravel, .NET et Dolibarr : l'appel REST est décrit dans les guides PHP, Laravel, .NET et Dolibarr.

Versions 0.1.2, statut alpha. Les exemples ci-dessous appellent l'API directement : curl et Node.js. Depuis la 0.1.2, les clients appellent par défaut la production (https://api.facturxapi.com) ; l'adresse se change avec base_url ou FACTURX_BASE_URL, en https. Python 3.10 ou plus, avec la commande facturx (dry-run, convert, validate) ; Node.js en modules ESM, avec les types TypeScript.

Votre ERP reste la source

Réutilisez les données structurées déjà présentes dans votre système, sans ressaisie dans un éditeur de facture séparé.

Deux livrables au choix

Demandez le PDF/A-3 Factur-X avec son XML embarqué, ou seulement le XML CII quand votre flux n’a pas besoin de PDF.

Des contrôles nommés

La réponse sépare la production du fichier, la validation EN16931 et les contrôles des règles françaises réellement exécutés.

Une limite sans ambiguïté

L’API produit et contrôle le document. Elle ne le transmet pas et ne prétend pas prouver son acceptation par une Plateforme Agréée.

Référence d’intégration

Cette page suit le contrat public /api/v1. Pour vérifier un champ avant intégration, ouvrez le contrat d’intégration interactif ou consultez le JSON /api/v1/integration-contract. Si une valeur invoice_data est refusée avec 422 invalid_invoice_data, corrigez le champ nommé et vérifiez la version exposée par GET https://api.facturxapi.com/meta (à la racine de l’hôte : /api/v1/meta n’existe pas). Les autres codes 422 ont leur remédiation dans la section Erreurs fréquentes. En cas de doute, transmettez au support le champ, le code retourné et la version API appelée.

Formats à ne pas confondre

  • XML CII seul : fichier XML sans conteneur PDF, demandé avec output=xml_cii. Avec un invoice_data complet, le PDF source est optionnel. Ce livrable ne doit pas être présenté comme un Factur-X.
  • Factur-X PDF/A-3 : PDF lisible avec XML CII embarqué. Demandez-le avec output=facturx_pdfa3, ou omettez output puisque c’est la sortie par défaut.
  • UBL en sortie : non supporté aujourd’hui par /convert (les deux sorties sont le XML CII et le Factur-X PDF/A-3). C’est un chantier séparé, même si UBL est une syntaxe EN16931 dans d’autres contextes.
  • UBL en entrée : un UBL Invoice, ou CreditNote dans les mêmes limites, est accepté en entrée de /convert : avec output=xml_cii, l’API rend un XML CII EN16931 validé, sans PDF. Le mapping UBL → CII est strict : un champ non repris est refusé (422 ubl_source_mapping_unsupported, avec unsupportedFields) et un refus ne consomme aucune facture. Un Factur-X PDF/A-3 depuis un UBL seul n’est pas garanti : il dépend du déploiement (sinon 422 ubl_pdf_carrier_required), à vérifier. invoice_data ne se combine pas avec un UBL. Un diagnostic ubl_source_mapping_unsupported ou un détail unsupportedFields relève d’une source UBL ou de son mapping, pas d’un champ invoice_data de ce guide. Conditions complètes : documentation de l’API.
  • PA/SC : /convert ne réalise ni routage réglementaire, ni dépôt, ni acceptation PA.

Premier appel : XML CII depuis votre JSON ERP

La clé API s'envoie en header X-API-Key (recommandé pour une clé statique ERP) ou, à l'identique, en Authorization: Bearer <clé> — les deux fonctionnent. Définissez votre clé API dans FACTURX_API_KEY, puis gardez la même clé d’idempotence uniquement pour rejouer la même requête complète. Utilisez une nouvelle clé si le fichier, le JSON, la sortie, la cible de validation, les options, un complément inline ou un sélecteur de profil enregistré (profile_reuse, issuer_id/client_id, seller_profile_id/buyer_profile_id) changent. Comportement vérifié en production : une requête différente sous la même clé est refusée en 409 idempotency_conflict sans exécution ni consommation de quota ; une clé rejouée pendant le traitement répond 409 idempotency_in_progress — attendez la fin du premier appel plutôt que de rejouer en boucle.

Ces exemples demandent les règles françaises de la facture électronique (validation_target=france_2026), et le JSON d’exemple porte les mentions document_notes PMT, PMD et AAB et des adresses électroniques du vendeur et de l’acheteur. Choisissez en16931 seulement pour contrôler le socle européen seul : un fichier vérifié en en16931 n’a pas évalué les contrôles des règles françaises.

Pour un flux ERP sans conteneur PDF, demandez explicitement output=xml_cii avec un invoice_data complet. Le résultat est un XML CII, pas un document Factur-X.

curl -X POST https://api.facturxapi.com/api/v1/convert \
  -H "Authorization: Bearer $FACTURX_API_KEY" \
  -H "Idempotency-Key: erp-fa-2026-042-xml-v1" \
  -F "output=xml_cii" \
  -F "validation_target=france_2026" \
  -F 'invoice_data={
  "invoice_number": "FA-2026-042",
  "issue_date": "2026-04-01",
  "invoice_type": "380",
  "currency": "EUR",
  "business_process": "S1",
  "document_notes": [
    {
      "subject_code": "PMT",
      "content": "Indemnite forfaitaire de recouvrement de 40 EUR due en cas de retard de paiement"
    },
    {
      "subject_code": "PMD",
      "content": "Penalites de retard exigibles selon les conditions contractuelles"
    },
    {
      "subject_code": "AAB",
      "content": "Aucun escompte pour paiement anticipe"
    }
  ],
  "seller": {
    "name": "Ma Societe SAS",
    "siret": "99999123500014",
    "vat_id": "FR09999991235",
    "electronic_address": {
      "value": "FRROUTESELLER001",
      "scheme_id": "0225"
    },
    "address": {
      "street": "10 rue de Rivoli",
      "city": "Paris",
      "postal_code": "75001",
      "country": "FR"
    }
  },
  "buyer": {
    "name": "Client SA",
    "siret": "99999245600015",
    "electronic_address": {
      "value": "FRROUTEBUYER001",
      "scheme_id": "0225"
    },
    "address": {
      "street": "20 rue de la Republique",
      "city": "Lyon",
      "postal_code": "69002",
      "country": "FR"
    }
  },
  "payer": {
    "name": "OPCO Formation SA",
    "postal_address": {
      "street": "12 rue de la Formation",
      "city": "Paris",
      "postal_code": "75008",
      "country": "FR"
    },
    "email": "[email protected]",
    "electronic_address": {
      "value": "FRROUTEOPCO001",
      "scheme_id": "0225"
    }
  },
  "references": {
    "buyer_reference": "SERVICE-ACHATS",
    "purchase_order_reference": "PO-2026-0017",
    "contract_reference": "CTR-2026-04"
  },
  "totals": {
    "net": "970.00",
    "tax": "194.00",
    "gross": "1164.00",
    "prepaid_amount": "100.00",
    "rounding_amount": "0.00",
    "due": "1064.00"
  },
  "tax_breakdown": [
    {
      "rate": "20.00",
      "category": "S",
      "base": "970.00",
      "amount": "194.00"
    }
  ],
  "line_items": [
    {
      "number": "1",
      "description": "Prestation conseil",
      "quantity": "10",
      "unit": "C62",
      "unit_price": "100.00",
      "net_amount": "970.00",
      "vat_rate": "20.00",
      "vat_category": "S",
      "purchase_order_line_reference": "10",
      "gross_unit_price": "120.00",
      "price_discount": "20.00",
      "allowances": [
        {
          "amount": "50.00",
          "reason": "Remise ligne",
          "reason_code": "95"
        }
      ],
      "charges": [
        {
          "amount": "20.00",
          "reason": "Supplement urgent"
        }
      ]
    }
  ],
  "payment": {
    "due_date": "2026-05-01",
    "terms": "Paiement a 30 jours",
    "iban": "FR7630006000011234567890189"
  },
  "delivery": {
    "date": "2026-04-01",
    "address": {
      "street": "20 rue de la Republique",
      "city": "Lyon",
      "postal_code": "69002",
      "country": "FR"
    }
  },
  "invoicing_period": {
    "start_date": "2026-04-01",
    "end_date": "2026-04-30"
  }
}'

Factur-X PDF/A-3 depuis le JSON complet

Avec un invoice_data complet, /convert peut générer le Factur-X PDF/A-3 sans fichier PDF fourni : l’API rend le porteur visuel à partir des mêmes données. Si votre ERP dispose déjà d’un PDF, vous pouvez aussi le fournir comme porteur. Le PDF produit contient le XML CII embarqué. Pour un flux avec PDF existant, vous pouvez aussi essayer la conversion depuis l’outil en ligne /convert.

curl -X POST https://api.facturxapi.com/api/v1/convert \
  -H "Authorization: Bearer $FACTURX_API_KEY" \
  -H "Idempotency-Key: erp-fa-2026-042-v1" \
  -F "output=facturx_pdfa3" \
  -F "validation_target=france_2026" \
  -F 'invoice_data={
  "invoice_number": "FA-2026-042",
  "issue_date": "2026-04-01",
  "invoice_type": "380",
  "currency": "EUR",
  "business_process": "S1",
  "document_notes": [
    {
      "subject_code": "PMT",
      "content": "Indemnite forfaitaire de recouvrement de 40 EUR due en cas de retard de paiement"
    },
    {
      "subject_code": "PMD",
      "content": "Penalites de retard exigibles selon les conditions contractuelles"
    },
    {
      "subject_code": "AAB",
      "content": "Aucun escompte pour paiement anticipe"
    }
  ],
  "seller": {
    "name": "Ma Societe SAS",
    "siret": "99999123500014",
    "vat_id": "FR09999991235",
    "electronic_address": {
      "value": "FRROUTESELLER001",
      "scheme_id": "0225"
    },
    "address": {
      "street": "10 rue de Rivoli",
      "city": "Paris",
      "postal_code": "75001",
      "country": "FR"
    }
  },
  "buyer": {
    "name": "Client SA",
    "siret": "99999245600015",
    "electronic_address": {
      "value": "FRROUTEBUYER001",
      "scheme_id": "0225"
    },
    "address": {
      "street": "20 rue de la Republique",
      "city": "Lyon",
      "postal_code": "69002",
      "country": "FR"
    }
  },
  "payer": {
    "name": "OPCO Formation SA",
    "postal_address": {
      "street": "12 rue de la Formation",
      "city": "Paris",
      "postal_code": "75008",
      "country": "FR"
    },
    "email": "[email protected]",
    "electronic_address": {
      "value": "FRROUTEOPCO001",
      "scheme_id": "0225"
    }
  },
  "references": {
    "buyer_reference": "SERVICE-ACHATS",
    "purchase_order_reference": "PO-2026-0017",
    "contract_reference": "CTR-2026-04"
  },
  "totals": {
    "net": "970.00",
    "tax": "194.00",
    "gross": "1164.00",
    "prepaid_amount": "100.00",
    "rounding_amount": "0.00",
    "due": "1064.00"
  },
  "tax_breakdown": [
    {
      "rate": "20.00",
      "category": "S",
      "base": "970.00",
      "amount": "194.00"
    }
  ],
  "line_items": [
    {
      "number": "1",
      "description": "Prestation conseil",
      "quantity": "10",
      "unit": "C62",
      "unit_price": "100.00",
      "net_amount": "970.00",
      "vat_rate": "20.00",
      "vat_category": "S",
      "purchase_order_line_reference": "10",
      "gross_unit_price": "120.00",
      "price_discount": "20.00",
      "allowances": [
        {
          "amount": "50.00",
          "reason": "Remise ligne",
          "reason_code": "95"
        }
      ],
      "charges": [
        {
          "amount": "20.00",
          "reason": "Supplement urgent"
        }
      ]
    }
  ],
  "payment": {
    "due_date": "2026-05-01",
    "terms": "Paiement a 30 jours",
    "iban": "FR7630006000011234567890189"
  },
  "delivery": {
    "date": "2026-04-01",
    "address": {
      "street": "20 rue de la Republique",
      "city": "Lyon",
      "postal_code": "69002",
      "country": "FR"
    }
  },
  "invoicing_period": {
    "start_date": "2026-04-01",
    "end_date": "2026-04-30"
  }
}'

Exemple Node minimal

Le code ci-dessous illustre l’appel HTTP et vérifie, avant le renommage atomique, que la taille reçue correspond à result.pdfSize ou à la taille annoncée par le serveur de téléchargement. Ce contrôle porte sur l’intégrité du transfert, pas sur toute la structure interne du PDF. Votre intégration doit construire invoiceData depuis votre ERP.

import { createHash } from "node:crypto";
import { rename, rm, writeFile } from "node:fs/promises";

// FACTURX_OPERATION_KEY_START
function facturxOperationKey(output, target, invoiceJson, fileDigest = '') {
  const operationIdentity = createHash('sha256')
    .update([output, target, invoiceJson, fileDigest].join('\0'))
    .digest('hex');
  return `convert-${operationIdentity}`;
}
// FACTURX_OPERATION_KEY_END

const MAX_PDF_BYTES = 10 * 1024 * 1024;
const MAX_BASE64_PDF_CHARACTERS = Math.ceil(MAX_PDF_BYTES / 3) * 4;

async function downloadPdf(urlValue) {
  const url = new URL(urlValue);
  if (url.protocol !== "https:" || url.username || url.password) {
    throw new Error("L'URL de téléchargement du PDF doit être une URL HTTPS sans identifiants");
  }

  const downloadResponse = await fetch(url, { redirect: "error" });
  if (!downloadResponse.ok) {
    throw new Error(`Le téléchargement du PDF a échoué (HTTP ${downloadResponse.status})`);
  }

  const contentLength = downloadResponse.headers.get("content-length");
  if (contentLength === null) {
    throw new Error("Le serveur de téléchargement n'annonce pas la taille du PDF");
  }
  const announcedBytes = Number(contentLength);
  if (!Number.isSafeInteger(announcedBytes) || announcedBytes <= 0 || announcedBytes > MAX_PDF_BYTES) {
    throw new Error("La taille annoncée du PDF est invalide ou dépasse 10 Mo");
  }
  if (!downloadResponse.body) {
    throw new Error("Le téléchargement du PDF ne contient aucun corps de réponse");
  }

  const chunks = [];
  let receivedBytes = 0;
  const reader = downloadResponse.body.getReader();
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    receivedBytes += value.byteLength;
    if (receivedBytes > MAX_PDF_BYTES) {
      await reader.cancel();
      throw new Error("Le PDF téléchargé dépasse 10 Mo");
    }
    chunks.push(Buffer.from(value));
  }

  return { pdfBytes: Buffer.concat(chunks, receivedBytes), expectedBytes: announcedBytes };
}

function decodeInlinePdf(encodedPdf) {
  const strictBase64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
  if (encodedPdf.length % 4 !== 0 || !strictBase64.test(encodedPdf)) {
    throw new Error("Le PDF encodé n'est pas un base64 canonique");
  }

  const decodedPdf = Buffer.from(encodedPdf, "base64");
  if (decodedPdf.toString("base64") !== encodedPdf) {
    throw new Error("Le PDF encodé n'est pas un base64 canonique");
  }
  return decodedPdf;
}

function assertPdfIntegrity(pdfBytes, expectedBytes) {
  const header = pdfBytes.subarray(0, 8).toString("ascii");
  if (
    !Number.isSafeInteger(expectedBytes) ||
    expectedBytes <= 0 ||
    expectedBytes > MAX_PDF_BYTES ||
    pdfBytes.length !== expectedBytes ||
    !/^%PDF-(?:1\.[0-7]|2\.0)$/.test(header)
  ) {
    throw new Error("La signature ou la taille du PDF ne correspond pas au livrable annoncé");
  }
}

const invoiceData = {
  "invoice_number": "FA-2026-042",
  "issue_date": "2026-04-01",
  "invoice_type": "380",
  "currency": "EUR",
  "business_process": "S1",
  "document_notes": [
    {
      "subject_code": "PMT",
      "content": "Indemnite forfaitaire de recouvrement de 40 EUR due en cas de retard de paiement"
    },
    {
      "subject_code": "PMD",
      "content": "Penalites de retard exigibles selon les conditions contractuelles"
    },
    {
      "subject_code": "AAB",
      "content": "Aucun escompte pour paiement anticipe"
    }
  ],
  "seller": {
    "name": "Ma Societe SAS",
    "siret": "99999123500014",
    "vat_id": "FR09999991235",
    "electronic_address": {
      "value": "FRROUTESELLER001",
      "scheme_id": "0225"
    },
    "address": {
      "street": "10 rue de Rivoli",
      "city": "Paris",
      "postal_code": "75001",
      "country": "FR"
    }
  },
  "buyer": {
    "name": "Client SA",
    "siret": "99999245600015",
    "electronic_address": {
      "value": "FRROUTEBUYER001",
      "scheme_id": "0225"
    },
    "address": {
      "street": "20 rue de la Republique",
      "city": "Lyon",
      "postal_code": "69002",
      "country": "FR"
    }
  },
  "payer": {
    "name": "OPCO Formation SA",
    "postal_address": {
      "street": "12 rue de la Formation",
      "city": "Paris",
      "postal_code": "75008",
      "country": "FR"
    },
    "email": "[email protected]",
    "electronic_address": {
      "value": "FRROUTEOPCO001",
      "scheme_id": "0225"
    }
  },
  "references": {
    "buyer_reference": "SERVICE-ACHATS",
    "purchase_order_reference": "PO-2026-0017",
    "contract_reference": "CTR-2026-04"
  },
  "totals": {
    "net": "970.00",
    "tax": "194.00",
    "gross": "1164.00",
    "prepaid_amount": "100.00",
    "rounding_amount": "0.00",
    "due": "1064.00"
  },
  "tax_breakdown": [
    {
      "rate": "20.00",
      "category": "S",
      "base": "970.00",
      "amount": "194.00"
    }
  ],
  "line_items": [
    {
      "number": "1",
      "description": "Prestation conseil",
      "quantity": "10",
      "unit": "C62",
      "unit_price": "100.00",
      "net_amount": "970.00",
      "vat_rate": "20.00",
      "vat_category": "S",
      "purchase_order_line_reference": "10",
      "gross_unit_price": "120.00",
      "price_discount": "20.00",
      "allowances": [
        {
          "amount": "50.00",
          "reason": "Remise ligne",
          "reason_code": "95"
        }
      ],
      "charges": [
        {
          "amount": "20.00",
          "reason": "Supplement urgent"
        }
      ]
    }
  ],
  "payment": {
    "due_date": "2026-05-01",
    "terms": "Paiement a 30 jours",
    "iban": "FR7630006000011234567890189"
  },
  "delivery": {
    "date": "2026-04-01",
    "address": {
      "street": "20 rue de la Republique",
      "city": "Lyon",
      "postal_code": "69002",
      "country": "FR"
    }
  },
  "invoicing_period": {
    "start_date": "2026-04-01",
    "end_date": "2026-04-30"
  }
};

const output = "facturx_pdfa3";
const validationTarget = "france_2026";
const invoiceJson = JSON.stringify(invoiceData);
// Une clé par opération : la même requête rejouée garde sa clé ; une autre facture, un avoir ou une correction en a une autre.
const idempotencyKey = facturxOperationKey(output, validationTarget, invoiceJson);

const form = new FormData();
form.set("output", output);
form.set("validation_target", validationTarget);
form.set("invoice_data", invoiceJson);

const response = await fetch("https://api.facturxapi.com/api/v1/convert", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FACTURX_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
  },
  body: form,
});

const responseBody = await response.json();
if (!response.ok) {
  throw new Error(JSON.stringify(responseBody));
}

const targetStatus = responseBody.target?.status;
const result = responseBody.result;
if (!["verified", "incomplete"].includes(targetStatus)) {
  throw new Error(JSON.stringify(responseBody));
}
const hasInlinePdf = typeof result?.pdf === "string" && result.pdf.length > 0;
const hasRemotePdf = typeof result?.pdfUrl === "string" && result.pdfUrl.length > 0;
if (result?.packagingPerformed !== true || hasInlinePdf === hasRemotePdf) {
  throw new Error("Le PDF Factur-X n'est pas disponible dans cette réponse");
}
if (hasInlinePdf && result.pdf.length > MAX_BASE64_PDF_CHARACTERS) {
  throw new Error("Le PDF encodé dépasse 10 Mo");
}

const finalPath = "./facture-facturx.pdf";
const temporaryPath = `${finalPath}.tmp-${process.pid}`;
const { pdfBytes, expectedBytes } = hasInlinePdf
  ? { pdfBytes: decodeInlinePdf(result.pdf), expectedBytes: result.pdfSize }
  : await downloadPdf(result.pdfUrl);
assertPdfIntegrity(pdfBytes, expectedBytes);
try {
  await writeFile(temporaryPath, pdfBytes, { flag: "wx" });
  await rename(temporaryPath, finalPath);
} finally {
  await rm(temporaryPath, { force: true });
}

if (targetStatus === "incomplete") {
  const reservations = result.validationReadiness?.frCtc?.advisories ?? responseBody.warnings ?? [];
  console.warn("Fichier généré avec réserves — à traiter avant transmission", reservations);
}

JSON d’entrée

Exemple synthétique pour invoice_data. Il couvre les zones les plus utiles pour un premier test ERP/GED : parties, références, totaux de paiement, remises/charges, ventilation TVA, livraison, période de facturation et tarification ligne.

{
  "invoice_number": "FA-2026-042",
  "issue_date": "2026-04-01",
  "invoice_type": "380",
  "currency": "EUR",
  "business_process": "S1",
  "document_notes": [
    {
      "subject_code": "PMT",
      "content": "Indemnite forfaitaire de recouvrement de 40 EUR due en cas de retard de paiement"
    },
    {
      "subject_code": "PMD",
      "content": "Penalites de retard exigibles selon les conditions contractuelles"
    },
    {
      "subject_code": "AAB",
      "content": "Aucun escompte pour paiement anticipe"
    }
  ],
  "seller": {
    "name": "Ma Societe SAS",
    "siret": "99999123500014",
    "vat_id": "FR09999991235",
    "electronic_address": {
      "value": "FRROUTESELLER001",
      "scheme_id": "0225"
    },
    "address": {
      "street": "10 rue de Rivoli",
      "city": "Paris",
      "postal_code": "75001",
      "country": "FR"
    }
  },
  "buyer": {
    "name": "Client SA",
    "siret": "99999245600015",
    "electronic_address": {
      "value": "FRROUTEBUYER001",
      "scheme_id": "0225"
    },
    "address": {
      "street": "20 rue de la Republique",
      "city": "Lyon",
      "postal_code": "69002",
      "country": "FR"
    }
  },
  "payer": {
    "name": "OPCO Formation SA",
    "postal_address": {
      "street": "12 rue de la Formation",
      "city": "Paris",
      "postal_code": "75008",
      "country": "FR"
    },
    "email": "[email protected]",
    "electronic_address": {
      "value": "FRROUTEOPCO001",
      "scheme_id": "0225"
    }
  },
  "references": {
    "buyer_reference": "SERVICE-ACHATS",
    "purchase_order_reference": "PO-2026-0017",
    "contract_reference": "CTR-2026-04"
  },
  "totals": {
    "net": "970.00",
    "tax": "194.00",
    "gross": "1164.00",
    "prepaid_amount": "100.00",
    "rounding_amount": "0.00",
    "due": "1064.00"
  },
  "tax_breakdown": [
    {
      "rate": "20.00",
      "category": "S",
      "base": "970.00",
      "amount": "194.00"
    }
  ],
  "line_items": [
    {
      "number": "1",
      "description": "Prestation conseil",
      "quantity": "10",
      "unit": "C62",
      "unit_price": "100.00",
      "net_amount": "970.00",
      "vat_rate": "20.00",
      "vat_category": "S",
      "purchase_order_line_reference": "10",
      "gross_unit_price": "120.00",
      "price_discount": "20.00",
      "allowances": [
        {
          "amount": "50.00",
          "reason": "Remise ligne",
          "reason_code": "95"
        }
      ],
      "charges": [
        {
          "amount": "20.00",
          "reason": "Supplement urgent"
        }
      ]
    }
  ],
  "payment": {
    "due_date": "2026-05-01",
    "terms": "Paiement a 30 jours",
    "iban": "FR7630006000011234567890189"
  },
  "delivery": {
    "date": "2026-04-01",
    "address": {
      "street": "20 rue de la Republique",
      "city": "Lyon",
      "postal_code": "69002",
      "country": "FR"
    }
  },
  "invoicing_period": {
    "start_date": "2026-04-01",
    "end_date": "2026-04-30"
  }
}

Formats attendus

  • Pays : Code ISO 3166-1 alpha-2, deux lettres : « FR », « DE ». Jamais le nom du pays (« France »). Exemple : "country": "FR". Champs : seller.address.country, buyer.address.country, delivery.address.country, payer.postal_address.country.
  • Dates : AAAA-MM-JJ (ISO 8601) : « 2026-01-15 ». Ces champs refusent JJ/MM/AAAA et une heure. payment.due_date n’a pas ce motif à l’entrée : la conversion le refuse pourtant en 422 invalid_invoice_data s’il n’est pas une date calendaire AAAA-MM-JJ (année 1900 à 2100). Exemple : "issue_date": "2026-01-15". Champs : issue_date, delivery.date, invoice_reference_issue_date, vat_point_date, invoicing_period.start_date, invoicing_period.end_date, line_items[].period.start_date, line_items[].period.end_date.
  • Facture d’origine d’un avoir (BT-25) : Le numéro exact de la facture d’origine, tel qu’il y est écrit : lettres, chiffres et + - _ /, sans espace ni phrase ; 35 caractères au plus pour les règles françaises (règles BR-FR-01 et BR-FR-02). L’API accepte jusqu’à 100 caractères en entrée. Exemple : "invoice_reference": "FA-2026-001". Champs : invoice_reference, invoice_patch.preceding_invoice.reference.

Un format refusé répond en erreur : voir Lire un refus 422 dans /docs.

Champs utilisables dans invoice_data

Pour explorer chaque champ avec sa règle EN16931 ou française, son XPath CII et un exemple, et pour valider un JSON en direct, ouvrez le contrat d'intégration interactif.

  • Requis : champ que le schéma d’entrée exige (sans lui, l’appel est refusé en 422).
  • Socle EN16931 : champ que le contrat classe au niveau EN16931 ; le contrat ne l’exige pas pour tout document, et un astérisque signale une exigence qui dépend du cas.
  • Règles françaises : champ que le contrat classe au niveau des règles françaises de la facture électronique.
  • Transmission (règles françaises) : champ que le contrat classe au niveau transmission ; son absence ne bloque pas la génération.
  • Optionnel : hors des niveaux du contrat.
  • Ces niveaux sont lus dans le contrat d’intégration, pas écrits à la main.
invoice_number
Rôle
Numéro facture
BT/BG
BT-1
Requis
Requis
Disponibilité
Disponible
issue_date
Rôle
Date émission
BT/BG
BT-2
Requis
Requis
Disponibilité
Disponible ; format AAAA-MM-JJ
invoice_type
Rôle
Type document
BT/BG
BT-3
Requis
Socle EN16931
Disponibilité
Disponible
currency
Rôle
Devise
BT/BG
BT-5
Requis
Socle EN16931
Disponibilité
Disponible
business_process
Rôle
Cadre de facturation / processus métier
BT/BG
BT-23
Requis
Transmission (règles françaises)
Disponibilité
Disponible ; 20 codes acceptés (contrat d’intégration) ; l’exemple utilise S1, qui décrit des services seuls : choisissez le code du processus réel de votre facture
seller.name
Rôle
Nom vendeur
BT/BG
BT-27
Requis
Requis
Disponibilité
Disponible
seller.siret
Rôle
Identifiant vendeur
BT/BG
BT-29
Requis
Socle EN16931
Disponibilité
Disponible ; le SIREN (BT-30) est calculé pour le vendeur FR
seller.vat_id
Rôle
TVA intracom vendeur
BT/BG
BT-31
Requis
Socle EN16931
Disponibilité
Disponible
seller.tax_registration_id
Rôle
Identifiant fiscal vendeur hors TVA
BT/BG
BT-32
Requis
Socle EN16931
Disponibilité
Disponible pour les cas IGIC/IPSI
seller.address.street / city / postal_code / country
Rôle
Adresse postale vendeur
BT/BG
BT-35 / BT-37 / BT-38 / BT-40
Requis
Requis : country · Socle EN16931 : street, city, postal_code
Disponibilité
Disponible pour EN16931 ; country est requis, code ISO 3166-1 alpha-2 (« FR », jamais « France »)
seller.electronic_address.value / scheme_id
Rôle
Adresse électronique vendeur
BT/BG
BT-34
Requis
Règles françaises : value*, scheme_id*
Disponibilité
Disponible ; utilisez uniquement un identifiant de routage confirmé
buyer.name
Rôle
Nom acheteur
BT/BG
BT-44
Requis
Requis
Disponibilité
Disponible
buyer.siret
Rôle
Identifiant acheteur
BT/BG
BT-46 + BT-47 SIREN calculé
Requis
Socle EN16931
Disponibilité
Disponible ; le SIREN est déduit du SIRET
buyer.address.street / city / postal_code / country
Rôle
Adresse postale acheteur
BT/BG
BT-50 / BT-52 / BT-53 / BT-55
Requis
Requis : country · Socle EN16931 : street, city, postal_code
Disponibilité
Disponible pour EN16931 ; country est requis, code ISO 3166-1 alpha-2 (« FR », jamais « France »)
buyer.electronic_address.value / scheme_id
Rôle
Adresse électronique acheteur
BT/BG
BT-49
Requis
Règles françaises : value*, scheme_id*
Disponibilité
Disponible ; utilisez uniquement un identifiant de routage confirmé
references.buyer_reference / purchase_order_reference / contract_reference / project_reference / despatch_advice_reference / receiving_advice_reference / buyer_accounting_reference
Rôle
Références acheteur, commande, contrat, projet, avis
BT/BG
BT-10 / BT-11 / BT-12 / BT-13 / BT-15 / BT-16 / BT-19
Requis
Optionnel : buyer_reference, purchase_order_reference, contract_reference, project_reference, despatch_advice_reference, receiving_advice_reference, buyer_accounting_reference
Disponibilité
Disponible
invoice_reference / invoice_reference_issue_date
Rôle
Facture précédente pour un avoir ou une correction
BT/BG
BT-25 / BT-26 / BG-3
Requis
Règles françaises : invoice_reference_issue_date · Optionnel : invoice_reference
Disponibilité
Disponible pour les types 381 et 384 ; invoice_reference = le numéro exact de la facture d’origine (lettres, chiffres et + - _ /, sans espace ni phrase ; 35 caractères au plus pour les règles françaises), invoice_reference_issue_date au format AAAA-MM-JJ
document_notes[].content / subject_code
Rôle
Notes de document et mentions PMT, PMD ou AAB
BT/BG
BT-22 / BT-21
Requis
Socle EN16931 : subject_code · Règles françaises : content*
Disponibilité
Disponible ; subject_code est optionnel, mais limité à PMT, PMD ou AAB lorsqu’il est fourni
totals.net / totals.tax / totals.gross
Rôle
Totaux HT, TVA, TTC
BT/BG
BT-109 / BT-110 / BT-112
Requis
Requis : net, tax, gross
Disponibilité
Disponible
totals.prepaid_amount / totals.rounding_amount / totals.due
Rôle
Acompte, arrondi, net à payer
BT/BG
BT-113 / BT-114 / BT-115
Requis
Socle EN16931 : prepaid_amount, rounding_amount, due
Disponibilité
Disponible
allowances[] / charges[]
Rôle
Remises et charges au niveau document
BT/BG
BG-20 / BG-21
Requis
Socle EN16931 : amount*, base_amount, reason, reason_code, vat_category, vat_rate
Disponibilité
Disponible
tax_breakdown[]
Rôle
Ventilation TVA fournie par votre ERP
BT/BG
BG-23
Requis
Requis : rate, base, amount · Socle EN16931 : category
Disponibilité
Disponible
tax_breakdown[].exemption_reason / exemption_reason_code
Rôle
Motif ou code exonération par ventilation TVA
BT/BG
BT-120 / BT-121
Requis
Socle EN16931 : exemption_reason_code · Optionnel : exemption_reason
Disponibilité
Disponible pour toutes les catégories exonérantes (E, AE, K, G, O), y compris les mélanges — en multi-catégories, le motif par ventilation est requis
vat_exemption_reason / vat_exemption_code
Rôle
Motif ou code exonération global
BT/BG
BT-120 / BT-121
Requis
Socle EN16931 : vat_exemption_reason, vat_exemption_code
Disponibilité
Disponible seulement si une seule catégorie exonérante le porte
line_items[]
Rôle
Lignes facture
BT/BG
BG-25
Requis
Requis : number, description, quantity, unit_price, net_amount, vat_rate · Socle EN16931 : vat_category · Optionnel : unit
Disponibilité
Disponible
line_items[].purchase_order_line_reference / buyer_accounting_reference
Rôle
Références ligne
BT/BG
BT-132 / BT-133
Requis
Optionnel : purchase_order_line_reference, buyer_accounting_reference
Disponibilité
Disponible
line_items[].allowances[] / charges[]
Rôle
Remises et charges de ligne
BT/BG
BG-27 / BG-28
Requis
Optionnel : amount, base_amount, reason, reason_code
Disponibilité
Disponible dans la version API correspondant à cette documentation
line_items[].gross_unit_price (BT-148) / price_discount (BT-147)
Rôle
Prix brut et remise de prix
BT/BG
BT-148 / BT-147
Requis
Optionnel : gross_unit_price, price_discount
Disponibilité
Disponible dans la version API correspondant à cette documentation
payment.due_date / payment.terms / payment.iban / payment.bic
Rôle
Échéance et paiement (compte crédité)
BT/BG
BT-9 / BT-20 / BT-84 / BT-86
Requis
Socle EN16931 : due_date, terms, iban · Optionnel : bic
Disponibilité
Disponible ; due_date doit être une date calendaire AAAA-MM-JJ (année 1900 à 2100). Le schéma d’entrée n’a pas le motif des autres dates : une autre écriture répond 422 invalid_invoice_data.
payment.means_code
Rôle
Code moyen de paiement
BT/BG
BT-81
Requis
Socle EN16931
Disponibilité
Disponible
payment.account_proprietary_id
Rôle
Identifiant de compte propriétaire (sans IBAN)
BT/BG
BT-84
Requis
Socle EN16931
Disponibilité
Disponible : IBAN ou identifiant de compte propriétaire, au choix (CII-SR-470)
payment.account_name
Rôle
Nom du compte de paiement
BT/BG
BT-85
Requis
Optionnel
Disponibilité
Disponible ; requiert iban ou account_proprietary_id
payment.mandate_reference
Rôle
Référence de mandat SEPA (prélèvement)
BT/BG
BT-89
Requis
Optionnel
Disponibilité
Disponible ; cohérent seulement avec means_code 49 ou 59
payment.debtor_account_iban
Rôle
IBAN du compte débité (prélèvement acheteur)
BT/BG
BT-91
Requis
Socle EN16931
Disponibilité
Disponible ; cohérent seulement avec means_code 49 ou 59 — ce n’est pas l’IBAN du factor
payment.creditor_sepa_identifier
Rôle
Identifiant créancier SEPA (ICS)
BT/BG
BT-90
Requis
Optionnel
Disponibilité
Disponible ; cohérent seulement avec means_code 49 ou 59
payee.name / payee.identifier
Rôle
Bénéficiaire / factor (payee déviant du seller)
BT/BG
BT-59 / BT-60 / BG-10
Requis
Socle EN16931 : name* · Optionnel : identifier
Disponibilité
Disponible ; le payee doit différer du seller (BR-17)
payer.name / payer.global_id / payer.legal_registration / payer.postal_address / payer.email / payer.electronic_address
Rôle
Tiers payeur distinct de l’acheteur et du bénéficiaire
BT/BG
EXT-FR-FE-BG-02
Requis
Règles françaises : value*, scheme_id* · Optionnel : name, value, scheme_id, country, street, city, postal_code, email
Disponibilité
Disponible ; name est requis si payer est fourni ; postal_address.country est requis avec une adresse postale ; email est un contact, distinct de l’identifiant de routage electronic_address { value, scheme_id } ; la sortie effective passe au profil EXTENDED
delivery.date / delivery.address
Rôle
Livraison et adresse de livraison
BT/BG
BT-72 / BG-13
Requis
Socle EN16931 : date, country* · Optionnel : street, city, postal_code
Disponibilité
Disponible ; date au format AAAA-MM-JJ, address.country en code ISO 3166-1 alpha-2
invoicing_period (alias accepté : billing_period)
Rôle
Période de facturation
BT/BG
BG-14 / BT-73 / BT-74
Requis
Socle EN16931 : start_date · Optionnel : end_date
Disponibilité
Disponible ; billing_period est accepté comme alias ; start_date et end_date au format AAAA-MM-JJ

Compléments pour un document extrait et champs refusés

Pour une source ERP complète, envoyez les adresses électroniques, les notes et la référence de facture précédente directement dans invoice_data avec les chemins confirmés ci-dessus. Les racines ci-dessous servent surtout à compléter un PDF ou XML analysé ; les champs réellement non supportés sont refusés explicitement plutôt qu’ignorés.

seller_context.electronic_address / buyer_context.electronic_address
Rôle
Routage France pour un document extrait
BT/BG
BT-34 / BT-49
Comment l’envoyer
Complément contextuel d’un PDF ou XML analysé ; avec une source ERP complète, utilisez seller.electronic_address et buyer.electronic_address dans invoice_data
seller_context.document_notes[] / buyer_context.document_notes[]
Rôle
Mentions PMT, PMD ou AAB pour un document extrait
BT/BG
BT-22 / BT-21
Comment l’envoyer
Complément contextuel d’un PDF ou XML analysé ; avec une source ERP complète, utilisez document_notes[] dans invoice_data
invoice_patch.preceding_invoice.reference / issue_date
Rôle
Facture précédente pour un document extrait
BT/BG
BT-25 / BT-26
Comment l’envoyer
Complément d’un PDF analysé ; invoice_patch ne peut pas être combiné avec un invoice_data complet ; reference = le numéro exact de la facture d’origine (lettres, chiffres et + - _ /, sans espace ni phrase), issue_date au format AAAA-MM-JJ
buyer.tax_registration_id
Rôle
Identifiant fiscal acheteur hors TVA
BT/BG
non émis
Comment l’envoyer
Refusé explicitement dans invoice_data : BT-32 est vendeur uniquement

Exemple de données complémentaires

Quand un diagnostic ou un scan demande de compléter un document analysé, reprenez uniquement les racines indiquées : invoice_patch, seller_context ou buyer_context. invoice_patch ne peut pas être combiné avec un invoice_data complet ; pour un flux ERP complet, préférez les champs directs de la matrice.

Les valeurs entre chevrons sont des champs à renseigner, pas des valeurs par défaut. Pour le schéma 0225, utilisez l’identifiant de routage confirmé par votre contexte métier ou par le diagnostic ; ne recopiez pas automatiquement un SIRET.

{
  "invoice_patch": {
    "preceding_invoice": {
      "issue_date": "2025-08-31"
    },
    "payment": {
      "iban": "FR7630006000011234567890189"
    }
  },
  "seller_context": {
    "electronic_address": {
      "value": "FRROUTESELLER001",
      "scheme_id": "0225"
    },
    "document_notes": [
      {
        "subject_code": "PMT",
        "content": "Indemnite forfaitaire de recouvrement de 40 EUR due en cas de retard de paiement"
      },
      {
        "subject_code": "PMD",
        "content": "Penalites de retard exigibles selon les conditions contractuelles"
      },
      {
        "subject_code": "AAB",
        "content": "Aucun escompte pour paiement anticipe"
      }
    ]
  },
  "buyer_context": {
    "electronic_address": {
      "value": "FRROUTEBUYER001",
      "scheme_id": "0225"
    }
  }
}

Réponses à gérer

Succès XML CII seul

Pour output=xml_cii, utilisez result.xml. Le PDF est absent et packagingPerformed=false ; cette absence est normale pour ce livrable.

{
  "success": true,
  "target": {
    "requested": "en16931",
    "executed": "en16931",
    "status": "verified",
    "missing_inputs": [],
    "not_run_reason": null
  },
  "result": {
    "targetProfile": "EN16931",
    "conversionSuccessful": true,
    "xml": "PD94bWwgdmVyc2lvbj0iMS4wIj8+...",
    "xmlSize": 4096,
    "pdf": null,
    "pdfUrl": null,
    "packagingPerformed": false,
    "validation": {
      "valid": true,
      "profile": "EN16931"
    },
    "processedInvoicesCharged": 1,
    "remainingInvoices": 999
  }
}

PDF/A-3 avec cible vérifiée

target.status=verified indique que les contrôles de la cible demandée ont réussi. Vérifiez aussi packagingPerformed=true et la présence de result.pdf avant de stocker le fichier.

{
  "success": true,
  "target": {
    "requested": "en16931",
    "executed": "en16931",
    "status": "verified",
    "missing_inputs": [],
    "not_run_reason": null
  },
  "result": {
    "targetProfile": "EN16931",
    "conversionSuccessful": true,
    "xml": "PD94bWwgdmVyc2lvbj0iMS4wIj8+...",
    "xmlSize": 4096,
    "pdf": "JVBERi0xLjQKJc...",
    "pdfSize": 102400,
    "packagingPerformed": true,
    "validation": {
      "valid": true,
      "profile": "EN16931"
    },
    "processedInvoicesCharged": 1,
    "remainingInvoices": 999
  }
}

Fichier généré avec réserves

target.status=incomplete ne prouve pas la cible complète, même si le PDF et le XML sont livrés et décomptés du quota. Stockez le fichier, conservez les réserves de validationReadiness avec lui, puis bloquez sa transmission tant qu'elles ne sont pas traitées.

{
  "success": false,
  "target": {
    "requested": "france_2026",
    "executed": "france_2026",
    "status": "incomplete",
    "missing_inputs": [],
    "not_run_reason": null
  },
  "result": {
    "targetProfile": "EN16931",
    "conversionSuccessful": false,
    "xml": "PD94bWwgdmVyc2lvbj0iMS4wIj8+...",
    "pdf": "JVBERi0xLjQKJc...",
    "packagingPerformed": true,
    "validationReadiness": {
      "frCtc": {
        "status": "incomplete",
        "blockers": [],
        "advisories": [
          "BR-FR-05_BT-22_PMT"
        ]
      }
    },
    "processedInvoicesCharged": 1,
    "remainingInvoices": 998
  }
}

Conversion bloquée

target.status=blocked ne doit créer aucun fichier final. Affichez les champs retournés avant un nouvel appel ; une liste non exhaustive ne doit pas être présentée comme une checklist finale.

{
  "success": false,
  "target": {
    "requested": "france_2026",
    "executed": null,
    "status": "blocked",
    "missing_inputs": [
      "seller_context.electronic_address.value",
      "buyer_context.electronic_address.value"
    ],
    "not_run_reason": "missing_inputs"
  },
  "verdict": "convert_with_inputs",
  "targetPreflight": {
    "requested": "france_2026",
    "status": "incomplete",
    "exhaustive": false,
    "requiredInputs": [
      {
        "btCode": "BT-34",
        "requestContextPath": "seller_context.electronic_address.value"
      },
      {
        "btCode": "BT-49",
        "requestContextPath": "buyer_context.electronic_address.value"
      }
    ],
    "unsupportedBlockers": []
  }
}

Suivre son quota en production

Quand /convert produit un livrable, y compris avec target.status=incomplete, lisez les headers X-Quota-Limit, X-Quota-Remaining et X-Quota-Reset quand ils sont présents. Le corps de réponse expose aussi result.processedInvoicesCharged et result.remainingInvoices ; une valeur null signifie que le décompte n'est pas connu au moment de la réponse, pas qu'il est nul.

Erreurs fréquentes

  • 401 : clé absente, invalide ou révoquée. Reprenez la clé depuis /api-key ou /dashboard/keys.
  • 400 file_required_for_pdf_output : vous demandez le livrable PDF/A-3 sans PDF fourni et sans invoice_data complet permettant de rendre le porteur. Fournissez l’une de ces deux sources.
  • 400 invalid_options : options.mode="xml_only" n’est pas le sélecteur public du livrable final. Utilisez output=xml_cii.
  • 400 invalid_invoice_patch : invoice_patch combiné avec un invoice_data complet, ou JSON de patch invalide.
  • 409 inline_context_conflict : une valeur inline contredit une valeur visible dans le document.
  • 409 idempotency_conflict : même Idempotency-Key rejouée avec une requête différente (fichier, invoice_data, sortie, cible ou options modifiés). Action : nouvelle clé. La requête rejouée n'est pas exécutée et ne consomme pas de quota (vérifié en production).
  • 409 idempotency_in_progress : même Idempotency-Key et même requête, première requête encore en cours de traitement. Action : attendre puis re-vérifier côté ERP ; ne changez pas de clé pour « forcer » un doublon. Quand la première requête est terminée et sa réponse enregistrée, le même POST rejoue cette réponse.
  • 413 file_too_large : fichier au-delà de la taille maximale (10 Mo).
  • 415 unsupported_media_type : fichier non PDF pour le flux Factur-X PDF/A-3.
  • 422 insufficient_data : données insuffisantes pour produire la cible demandée.
  • 422 invalid_invoice_data : total de TVA différent de la somme de la ventilation (BR-CO-14), remise de prix sans prix brut, catégorie TVA O/K/G/L/M incohérente, motif d’exonération ambigu en mode multi-catégories, buyer.tax_registration_id non supporté, ou règle EN16931 refusée explicitement.
  • 422 unsupported_output_profile_for_invoice_data : invoice_data ne peut pas cibler MINIMUM, BASIC_WL ou BASIC. Gardez le profil EN16931 par défaut ou utilisez EXTENDED si votre contrat le prévoit.
  • 422 unextractable_pdf : aucune donnée exploitable extraite du PDF, sans invoice_data pour compenser.
  • 503 requested_output_unavailable : le PDF/A-3 demandé n’a pas pu être produit ; l’API échoue explicitement (quota remboursé) au lieu de retourner un succès XML-only silencieux.
  • 422 ocr_confidence_too_low : confiance OCR insuffisante en mode strict. Action : fournir invoice_data (recommandé) ou options.confidence_mode="lenient".
  • 503 ocr_service_unavailable / 503 ocr_quota_exhausted : OCR indisponible ou quota OCR épuisé — header Retry-After fourni. Action : rejouer après le délai, même clé d'idempotence. Sans objet en mode invoice_data complet (pas d'OCR).
  • 402 quota_exceeded : quota mensuel de factures traitées atteint (définitif jusqu'au renouvellement ou upgrade — pas de Retry-After). Le header X-Quota-Used peut indiquer l'usage courant au moment du refus ; il ne signifie pas que cet appel refusé a traité une facture.
  • 429 : rate limit par minute — header Retry-After fourni. Action : backoff puis rejouer à l'identique (même clé d'idempotence).

En résumé, pour les codes listés ci-dessus : les 400, 413, 415 et 422 se corrigent avant un nouvel appel, avec une nouvelle clé d'idempotence ; 409 idempotency_in_progress s'attend sur la clé d'origine (en changer relancerait une seconde conversion) ; 402 quota_exceeded se lève par le renouvellement du quota ou un changement de plan ; 429 et les 503 ocr_service_unavailable / ocr_quota_exhausted se rejouent à l'identique après le délai Retry-After ; 503 requested_output_unavailable rembourse le quota et se rejoue avec la même clé. Pour les autres codes, 503 compris, suivez la stratégie publiée par code dans l'extension OpenAPI x-facturx-convert-error-registry : la classe HTTP seule ne dit pas s'il faut relancer.

  • Ne relancez pas à l’identique un 400, 413, 415 ou 422 : selon la stratégie publiée pour le code, corrigez l’entrée et envoyez la requête corrigée avec une nouvelle Idempotency-Key, ou contactez le support avec le request_id (par exemple request_failed).
  • Seuls 429 (en attendant le délai du header Retry-After), 502, 503 et 504 peuvent se relancer, et pas tous : relancez seulement si la stratégie publiée pour le code le prévoit, avec un nombre de relances borné (par exemple 3) et la même Idempotency-Key pour la même requête. Cas particulier, 409 idempotency_in_progress (même requête, même clé, première requête encore en cours de traitement) : attendez puis relancez avec la même clé ; ne changez pas de clé, cela lancerait une seconde opération. D’autres codes 409 ont leur propre stratégie dans x-facturx-convert-error-registry.
  • 402 : ce n’est pas une relance. Pour quota_exceeded (quota mensuel atteint), le renouvellement du quota ou un changement de plan le lève ; les autres 402 ont leur stratégie dans x-facturx-convert-error-registry.
  • 500 : pas de relance automatique. Conservez le request_id et contactez le support.
  • La stratégie de chaque code est publiée dans le fichier OpenAPI, extension x-facturx-convert-error-registry de POST /api/v1/convert : relancer avec la même clé, attendre Retry-After, corriger la requête, passer en output=xml_cii, suivre le job, contacter le support avec le request_id, ou quota remboursé. Un 503 ou un 504 ne se relance pas toujours : certains codes demandent de contacter le support, parfois après le délai Retry-After.

Pour les erreurs métier EN16931 (règles BR-*) remontées par la validation, la bibliothèque d’erreurs détaille cause et correction règle par règle.

Exemple multi-taux avec remise documentaire

Facture 20 % + 5,5 % avec une remise BG-20 portée par son taux : une entrée tax_breakdown par couple (catégorie, taux), bases exactes par groupe (BR-S-08). Exemple exécuté et vérifié contre la production.

{
  "invoice_number": "FA-2026-118",
  "issue_date": "2026-06-11",
  "seller": {
    "name": "Smoke Editeur SAS",
    "address": {
      "country": "FR",
      "street": "10 rue de la Paix",
      "city": "Paris",
      "postal_code": "75002"
    },
    "vat_id": "FR09999991235",
    "siret": "99999123500014"
  },
  "buyer": {
    "name": "Smoke Client SARL",
    "address": {
      "country": "FR",
      "street": "5 avenue des Champs",
      "city": "Lyon",
      "postal_code": "69002"
    },
    "vat_id": "FR83999992456",
    "siret": "99999245600015"
  },
  "totals": {
    "net": "73.33",
    "tax": "7.42",
    "gross": "80.75"
  },
  "tax_breakdown": [
    {
      "rate": "20",
      "category": "S",
      "base": "23.33",
      "amount": "4.67"
    },
    {
      "rate": "5.5",
      "category": "S",
      "base": "50.00",
      "amount": "2.75"
    }
  ],
  "line_items": [
    {
      "number": "1",
      "description": "Prestation 20%",
      "quantity": "1",
      "unit_price": "33.33",
      "net_amount": "33.33",
      "vat_rate": "20"
    },
    {
      "number": "2",
      "description": "Prestation 5.5%",
      "quantity": "1",
      "unit_price": "50.00",
      "net_amount": "50.00",
      "vat_rate": "5.5"
    }
  ],
  "allowances": [
    {
      "amount": "10.00",
      "reason": "Remise",
      "vat_category": "S",
      "vat_rate": "20"
    }
  ],
  "payment": {
    "due_date": "2026-07-11",
    "terms": "30 jours"
  }
}

À préparer avant le premier test

Pour valider votre mapping sans générer de livrable ni consommer de quota, ajoutez dry_run=true : la réponse liste les champs manquants (targetPreflight, et convertPreparation.fieldsToProvide quand des champs guidés sont attendus) avant votre premier appel final.

  • Un invoice_data complet ; si votre flux possède déjà un PDF de facture, gardez aussi un porteur testable sans donnée client sensible si vous partagez l’exemple.
  • Un JSON ERP qui contient les champs dont votre flux a besoin. invoice_data est un champ multipart de type chaîne contenant le JSON sérialisé (pas un body JSON) — en shell, préférez -F "invoice_data=$(cat facture.json)" ou --form-string pour éviter les surprises d'échappement.
  • Montants et taux en chaînes de caractères, arrondis à 2 décimales ("970.00", "20.00") — un montant sub-centime est refusé en 422 ; quantity et unit_price peuvent porter plus de décimales.
  • Une règle d’idempotence : même clé uniquement pour la même requête complète ; nouvelle clé si le fichier, le JSON, la sortie, la cible, les options ou un complément changent.
  • Un affichage clair des erreurs : champs manquants, conflit document/contexte, cible non exécutée.
  • Un message produit séparé : document généré ou diagnostiqué, mais aucun statut PA inventé.

Ce que FacturX API fait

  • Générer un XML CII (base EN16931) depuis un invoice_data complet, avec output=xml_cii, sans PDF.
  • Générer un Factur-X PDF/A-3 avec XML CII embarqué depuis un invoice_data complet sans PDF fourni, ou en utilisant un PDF existant comme porteur, avec output=facturx_pdfa3.
  • Exécuter la validation EN16931, et les contrôles des règles françaises (FNFE-MPE) quand ils sont demandés (validation_target=france_2026) et exécutables — sinon la réponse dit explicitement pourquoi la cible n’a pas tourné (not_run_reason).
  • Retourner des erreurs structurées qui nomment le champ à corriger, et un mode test dry_run=true qui liste les champs manquants sans consommer de quota.
  • Diagnostiquer gratuitement un document existant via le validateur public avant de câbler l’appel API.

Ce que FacturX API ne promet pas

  • Pas d’UBL en sortie de /convert aujourd’hui.
  • Pas de dépôt, de transmission, de réception ni d’acceptation par une PA : /convert produit un document en amont ; le parcours PA reste séparé et relève de votre flux aval.
  • Pas de routage vers une PA ni d’interrogation d’annuaire : les identifiants de routage (BT-34 / BT-49) sont transportés et contrôlés s’ils sont fournis, jamais résolus automatiquement.
  • Pas de « conformité réforme 2026 » absolue : chaque résultat nomme le niveau réellement contrôlé — génération du XML CII, production du Factur-X PDF/A-3, validation EN16931, contrôles des règles françaises (FNFE-MPE) exécutés.
  • Pas de garantie d’acceptation aval : un fichier produit et validé ici reste soumis aux règles du flux qui le reçoit.

Vous branchez des clients finaux sur ce flux ? Le parcours intégrateurs décrit où placer scan, conversion et validation dans votre architecture.

Questions fréquentes

Puis-je sortir un XML CII sans PDF ?

Oui. Demandez output=xml_cii avec un invoice_data complet : le PDF est optionnel pour ce livrable. La réponse contient le XML dans result.xml, avec packagingPerformed=false puisqu’aucun PDF/A-3 n’est produit.

Est-ce un Factur-X ?

Le XML CII seul, non. Un Factur-X est un PDF/A-3 lisible avec le XML CII embarqué. Avec un invoice_data complet, /convert peut générer le Factur-X PDF/A-3 sans fichier PDF fourni : l’API rend le porteur visuel à partir des mêmes données. Si votre ERP dispose déjà d’un PDF, vous pouvez aussi le fournir comme porteur.

Est-ce accepté automatiquement par une PA ?

Non. /convert produit un document et exécute les contrôles demandés : validation EN16931, ou contrôles des règles françaises (FNFE-MPE) quand ils sont demandés et exécutables. Le dépôt, la transmission et l’acceptation relèvent du parcours PA, séparé de cette API.

UBL est-il disponible ?

Non en sortie : /convert ne produit pas d’UBL aujourd’hui. Les deux sorties publiques sont le XML CII (output=xml_cii) et le Factur-X PDF/A-3 (output=facturx_pdfa3). En entrée, un UBL Invoice est accepté et converti en XML CII (mapping strict, sinon 422 ubl_source_mapping_unsupported) ; un Factur-X PDF/A-3 depuis un UBL n’est pas garanti : voir la documentation de l’API.

Que faire en cas de 422 ?

422 insufficient_data : il manque des champs pour la cible demandée — complétez invoice_data avec les chemins listés dans la réponse (missing_inputs, targetPreflight). 422 invalid_invoice_data : une valeur est refusée par une règle (par exemple un total de TVA différent de la somme de la ventilation, ou un champ refusé) — corrigez la valeur nommée dans l’erreur. Testez avec dry_run=true pour itérer sans consommer de quota.