Kurz gesagt: Sie senden einen kompakten JSON-Payload per HTTP POST an die fakturai REST-API und erhalten eine EN-16931-konforme ZUGFeRD-/Factur-X-PDF zurück — ohne XML-Wissen, ohne lokale Generator-Bibliotheken, ohne manuelle Namespace-Deklarationen. Die API prüft die erzeugte Datei vor der Auslieferung gegen ein KoSIT-orientiertes Akzept-Gate.
Lokale XML-Bibliotheken für ZUGFeRD oder XRechnung erfordern tiefes Format-Know-how: EN-16931-BT-/BG-Codes, Namespace-Deklarationen, Schematron-Regeln, Profil-Spezifika. Die API abstrahiert das:
Dieses synthetische B2B-Beispiel erzeugt eine ZUGFeRD/Factur-X-PDF. Voraussetzung: eine freigegebene Web-App-Instanz, ein gespeichertes Verkäuferprofil und dessen API-Key. Verkäuferdaten kommen aus dem Profil, die Rechnungsnummer vergibt der Server. Dieser Endpoint unterstützt keinen format-Schalter für reine XRechnung-XML. Erfolgreiche Erzeugung zählt zum Verbrauch; keine automatische Wiederholung starten.
Setzen Sie FAKTURAI_BASE_URL auf die Web-App (lokal z. B. http://127.0.0.1:3004, bei freigegebenem Betrieb https://fakturai.de) und FAKTURAI_API_KEY sicher in Ihrer Umgebung, nicht im Quellcode. Installieren Sie httpx mit python -m pip install httpx, speichern Sie den Block als generate.py und starten Sie python generate.py. Verwenden Sie nur synthetische Daten; die Anfrage wird an die konfigurierte Instanz übertragen.
import os, pathlib
import httpx
base = os.environ["FAKTURAI_BASE_URL"].rstrip("/")
key = os.environ["FAKTURAI_API_KEY"]
payload = {
"invoice_date": "2026-06-16",
"due_date": "2026-06-30",
"currency": "EUR",
"buyer": {
"name": "Synthetic Client GmbH",
"address": {
"line_one": "Beispielweg 2", "postcode": "80331",
"city": "München", "country": "DE"
},
"contact_email": "billing@example.invalid"
},
"lines": [{
"name": "Beratungsleistung", "quantity": "2", "unit": "HUR",
"unit_price": "50.00", "vat_rate": "19.00"
}]
}
resp = httpx.post(
base + "/api/v1/invoices",
json=payload,
headers={"Authorization": "Bearer " + key},
timeout=120.0,
)
resp.raise_for_status()
if resp.status_code != 201 or resp.headers.get("content-type", "").split(";")[0] != "application/pdf":
raise RuntimeError("Expected HTTP 201 application/pdf")
pathlib.Path("invoice.pdf").write_bytes(resp.content)Für requests ersetzen Sie httpx.post durch requests.post — der Payload ist identisch.
Node.js 22 oder neuer: als generate.mjs speichern und mit node generate.mjs starten. Dieselben Umgebungsvariablen gelten. Beide Beispiele speichern nur bei 201 application/pdfeine invoice.pdf; Fehlerantworten sind JSON, keine PDF und kein bestandener Test.
import fs from "node:fs/promises";
const base = process.env.FAKTURAI_BASE_URL;
const key = process.env.FAKTURAI_API_KEY;
if (!base || !key) throw new Error("Set FAKTURAI_BASE_URL and FAKTURAI_API_KEY");
const payload = {
invoice_date: "2026-06-16",
due_date: "2026-06-30",
currency: "EUR",
buyer: {
name: "Synthetic Client GmbH",
address: {
line_one: "Beispielweg 2", postcode: "80331",
city: "München", country: "DE"
},
contact_email: "billing@example.invalid"
},
lines: [{
name: "Beratungsleistung", quantity: "2", unit: "HUR",
unit_price: "50.00", vat_rate: "19.00"
}]
};
const res = await fetch(new URL("/api/v1/invoices", base), {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: "Bearer " + key },
body: JSON.stringify(payload),
signal: AbortSignal.timeout(120_000),
});
if (!res.ok) throw new Error("API error " + res.status);
if (res.status !== 201 || res.headers.get("content-type")?.split(";")[0] !== "application/pdf") {
throw new Error("Expected HTTP 201 application/pdf");
}
await fs.writeFile("invoice.pdf", Buffer.from(await res.arrayBuffer()));Der Payload ist dieselbe JSON-Struktur — die Sprache ist irrelevant für die API. In TypeScript fügen Sie eine Typ-Definition für den Payload hinzu; das Ergebnis bleibt ein Buffer bzw. ein ArrayBuffer.
| Feld (JSON) | EN-16931-Code | Pflicht |
|---|---|---|
invoice_date | BT-2 | ✓ (YYYY-MM-DD) |
due_date | BT-9 | ✓ (YYYY-MM-DD) |
buyer.name, buyer.address | BG-7 / BG-8 | ✓ (verschachtelte Adresse) |
lines[].name, lines[].quantity | BT-153 / BT-129 | ✓ (Menge als Dezimalstring) |
lines[].unit, lines[].unit_price | BT-130 / BT-146 | ✓ (Preis als Dezimalstring) |
lines[].vat_rate | BT-152 | ✓ (Dezimalstring) |
currency | BT-5 | Optional, Standard EUR |
Jede erzeugte Datei durchläuft serverseitig ein vierschichtiges Prüf-Gate:
Erreicht die Datei nicht ACCEPT-Status, gibt die API einen Fehler mit den konkreten Schematron-Regeln zurück — so sehen Sie sofort, welche Pflichtfelder fehlen oder welche Summe nicht stimmt.
Vorhandene Dateien — z. B. aus einem Drittsystem oder einem früheren API-Aufruf — können Sie ohne Konto im kostenlosen Validator gegen EN 16931 prüfen.
Neben der REST-API steht ein MCP-Server bereit, der dieselbe Erzeuge-und-prüfe-Logik als Tool-Call exponiert. AI-Agenten und MCP-Clients können damit E-Rechnungen erzeugen, ohne HTTP manuell zu formulieren. Details im Beitrag E-Rechnung per API & MCP erstellen.
fakturai erzeugt und prüft die technische Format- und Strukturkonformität nach EN 16931 mit einem KoSIT-orientierten Akzept-Gate. Die inhaltliche und steuerliche Richtigkeit des JSON-Payloads, die GoBD-konforme Archivierung der erzeugten Datei und die buchhalterische Behandlung bleiben in Ihrer Verantwortung. fakturai ist kein Buchhaltungssystem und gibt keine Rechts-, Steuer- oder Zustellungsgarantien.
Vertragsreferenz: REST-Dokumentation. Synthetische Integrationsbeispiele; technische Struktursprache nach EN 16931, kein Rechts-/Steuer-/Zustellungsversprechen.
English companion: Generate German e-invoices with Python and Node.js (API integration).