L'API Recepto en Node.js

Trois scripts : valider, lire, générer. Prérequis : Node.js 20 ou plus récent, sans dépendance.

La validation se fait sans clé. La lecture et la génération lisent une clé d'API dans la variable d'environnement RECEPTO_API_KEY ; une clé gratuite s'obtient sur la page des tarifs.

Valider une facture

Le fichier part tel quel dans le champ file. La réponse donne le format, le profil, et chaque erreur avec l'identifiant et le texte de sa règle.

// Valider une facture avec Recepto : gratuit, sans clé.
//   node validate.mjs facture.pdf
import { readFile } from "node:fs/promises";
import { basename } from "node:path";

const API = process.env.RECEPTO_API ?? "https://recepto.fr/v1";
const path = process.argv[2];

const form = new FormData();
form.append("file", new Blob([await readFile(path)]), basename(path));
const response = await fetch(`${API}/validate`, { method: "POST", body: form });
const report = await response.json();

if (!response.ok) {
  // 413 ou 422 : fichier illisible ; 429 : réessayer après Retry-After secondes.
  console.error(`Refusé (${response.status}) : ${report.detail}`);
  process.exit(1);
}
console.log(`${report.format} ${report.profile} : ${report.valid ? "valide" : "invalide"}`);
for (const error of report.errors) {
  console.log(`- ${error.rule_id} : ${error.message}`);
}

Lire une facture reçue

/v1/parse renvoie la même structure JSON pour CII, UBL et Factur-X, avec le résumé de la validation dans source. Schéma : /v1/schemas/invoice-1.0.json.

// Lire une facture reçue en JSON (clé d'API requise).
//   RECEPTO_API_KEY=rcp_... node parse.mjs facture.pdf
import { readFile } from "node:fs/promises";
import { basename } from "node:path";

const API = process.env.RECEPTO_API ?? "https://recepto.fr/v1";
const headers = { Authorization: `Bearer ${process.env.RECEPTO_API_KEY}` };
const path = process.argv[2];

const form = new FormData();
form.append("file", new Blob([await readFile(path)]), basename(path));
const response = await fetch(`${API}/parse`, { method: "POST", body: form, headers });

if (response.status === 429) {
  console.error(`Limite atteinte : réessayer dans ${response.headers.get("Retry-After")} s.`);
  process.exit(1);
}
if (!response.ok) {
  throw new Error(`Recepto a répondu ${response.status} : ${await response.text()}`);
}

const { invoice, source } = await response.json();
console.log(`Facture ${invoice.number} de ${invoice.seller.name}`);
console.log(`Net à payer : ${invoice.totals.due} ${invoice.currency}`);
console.log(`Valide selon EN 16931 : ${source.valid ? "oui" : "non"}`);
console.log(`Lectures restantes ce mois-ci : ${response.headers.get("X-Quota-Remaining")}`);

Générer une facture Factur-X

L'entrée contient les parties et les lignes, sans aucun total. /v1/generate calcule totaux et TVA selon EN 16931, produit un PDF/A-3 Factur-X, et ne le renvoie que si son XML passe le XSD et le Schematron. Données de l'exemple :

{
  "number": "F2026-0042",
  "issue_date": "2026-10-15",
  "due_date": "2026-11-14",
  "delivery_date": "2026-10-10",
  "seller": {
    "name": "Atelier Numérique SAS",
    "legal_registration": {
      "value": "999999998",
      "scheme": "0002"
    },
    "vat_id": "FR11999999998",
    "address": {
      "line1": "12 rue des Lilas",
      "city": "Nantes",
      "postal_code": "44000"
    }
  },
  "buyer": {
    "name": "Boulangerie du Port SARL",
    "vat_id": "FR19787878784",
    "address": {
      "line1": "3 quai de la Fosse",
      "city": "Nantes",
      "postal_code": "44100"
    }
  },
  "payment": {
    "means_code": "30",
    "iban": "FR7630006000011234567890189"
  },
  "lines": [
    {
      "name": "Développement du site vitrine",
      "quantity": "12.5",
      "unit_code": "HUR",
      "unit_price": "65.00",
      "vat_rate": "20"
    }
  ]
}
// Générer une facture Factur-X à partir de ses données (clé d'API requise).
//   RECEPTO_API_KEY=rcp_... node generate.mjs facture.json facture.pdf
import { readFile, writeFile } from "node:fs/promises";

const API = process.env.RECEPTO_API ?? "https://recepto.fr/v1";
const [source, target] = process.argv.slice(2);

const response = await fetch(`${API}/generate`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RECEPTO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: await readFile(source),
});

if (response.status === 422) {
  const refusal = await response.json();
  // La facture obtenue enfreint une règle officielle : le rapport dit laquelle.
  for (const error of refusal.report?.errors ?? []) {
    console.log(`- ${error.rule_id} : ${error.message}`);
  }
  process.exit(1);
}
if (!response.ok) {
  throw new Error(`Recepto a répondu ${response.status} : ${await response.text()}`);
}

await writeFile(target, Buffer.from(await response.arrayBuffer()));
console.log(`Facture Factur-X écrite dans ${target}`);

Erreurs et limites

Référence complète de l'API · Erreurs de validation expliquées