Générer un Factur-X en Node.js
L'écosystème JavaScript n'a pas d'équivalent mûr aux
bibliothèques Factur-X du monde Python ou Java : produire un PDF/A-3
embarquant un XML CII valide, puis le passer au Schematron, suppose
en pratique de sortir de Node. Voici la voie courte, en fetch
natif, sans dépendance.
Pourquoi c'est plus dur qu'il n'y paraît en JS
Trois obstacles, dans cet ordre :
- Le PDF/A-3. Les générateurs PDF JavaScript courants produisent du PDF ordinaire. PDF/A-3 impose des contraintes d'archivage — polices embarquées, profil colorimétrique, métadonnées XMP — et une pièce jointe déclarée avec la bonne relation.
- Le XML CII. Le profil EN 16931 attend un ordre d'éléments et des cardinalités précis ; un XML « qui a l'air bon » échoue au XSD.
- Le Schematron. Les règles métier s'expriment en XSLT 2.0+, ce qui suppose un moteur de type Saxon — donc une JVM ou un binaire natif à opérer en production à côté de votre application Node.
D'où le choix courant : produire la facture ailleurs, et ne garder côté Node que l'appel réseau.
Générer une facture
Node ≥ 18 : fetch est natif, aucun paquet à installer.
import { writeFileSync } from "node:fs";
const API = "https://confactura.fr";
const KEY = "df_live_…"; // POST /v1/signup pour en obtenir une (gratuit)
const invoice = {
number: "2026-0042",
issue_date: "2026-09-01",
due_date: "2026-09-30",
seller: {
name: "Ma Société",
address_line: "1 rue X",
postcode: "75001",
city: "Paris",
siren: "123456789",
vat_id: "FR32123456789",
electronic_address: "[email protected]",
},
buyer: {
name: "Client SARL",
address_line: "2 av. Y",
postcode: "69001",
city: "Lyon",
electronic_address: "[email protected]",
},
lines: [{ description: "Presta dev", quantity: 5, unit_price: "450.00" }],
};
const res = await fetch(`${API}/v1/invoices/facturx`, {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": KEY },
body: JSON.stringify(invoice),
});
if (res.status === 422) {
// Facture non conforme : rapport détaillé, aucun PDF produit.
const { errors } = await res.json();
console.error("règles en échec :", errors);
} else if (!res.ok) {
throw new Error(`Confactura ${res.status}: ${await res.text()}`);
} else {
const pdf = Buffer.from(await res.arrayBuffer());
writeFileSync("facture-2026-0042.pdf", pdf); // PDF/A-3b, XML CII embarqué
}
Attention au piège du res.text()
La réponse est un flux binaire. La lire avec text() puis la
réécrire produit un PDF corrompu de façon silencieuse — il s'ouvrira
peut-être, mais la pièce jointe XML sera illisible. Utilisez
arrayBuffer(), et vérifiez le résultat sur le
validateur plutôt que dans un lecteur PDF.
Les champs que la réforme française rend obligatoires
Le profil EN 16931 seul ne suffit pas : le Schematron CTC français ajoute ses propres règles. Trois oublis expliquent la quasi-totalité des 422 au premier essai.
electronic_addresssur le vendeur et l'acheteur (BR-FR-12/BR-FR-13) — adresse de routage, pas un contact commercial ;vat_idcôté vendeur dès qu'une ligne est en TVA standard (BR-S-02) ;- un
sirenvalide côté vendeur.
Les mentions légales 2026 — frais de recouvrement, pénalités de retard,
escompte, catégorie d'opération — sont ajoutées côté serveur : voir
BR-FR-05.
Valider un PDF depuis vos tests
/v1/validate est gratuit, anonyme et ne consomme aucun
quota — donc appelable à chaque exécution de la suite de tests.
import { readFileSync } from "node:fs";
const form = new FormData();
form.append("file", new Blob([readFileSync("facture-2026-0042.pdf")],
{ type: "application/pdf" }), "facture.pdf");
const report = await (await fetch(`${API}/v1/validate`, {
method: "POST", body: form,
})).json();
console.log(report.valid, report.errors);
Et le XML seul ?
POST /v1/invoices/xml renvoie le CII sans le PDF — utile si
vous conservez votre propre mise en page, ou si votre Plateforme Agréée
attend du XML brut. Même corps de requête, mêmes validations.
Essayer maintenant
20 documents par mois gratuits, sans carte bancaire. La clé API est créée en une requête.
Obtenir ma clé API Valider un PDF existant