Python & Node SDK
Generate German e-invoices with Python and Node.js (API integration)
Short answer: send a compact JSON payload via HTTP POST to the fakturai REST API and receive an EN 16931-compliant ZUGFeRD/Factur-X PDF back — no XML knowledge, no local generator libraries, no manual namespace declarations. The API checks the generated file against a KoSIT-aligned accept gate before returning it.
Why API instead of a local library?
Local XML libraries for ZUGFeRD or XRechnung require deep format knowledge: EN 16931 BT/BG codes, namespace declarations, Schematron rules, profile specifics. The API abstracts all of that:
- Language-agnostic — works with Python, Node.js, TypeScript, Go, curl, n8n, Make, AI agents.
- No local tooling — no local validation tooling, no KoSIT validator installed locally.
- KoSIT accept gate server-side — the file is only returned when it reaches ACCEPT level.
- Always current — profile updates (e.g. XRechnung 3.0) land server-side, not in your client code.
Python example (httpx / requests)
This synthetic B2B example requires an approved web-app instance, a saved seller profile and its API key. Seller details come from that profile; the server allocates the invoice number. This endpoint has noformat switch for standalone XRechnung XML. Successful generation counts toward usage; do not automatically retry generation.
Set FAKTURAI_BASE_URL to the web app (locally, e.g. http://127.0.0.1:3004; for approved hosted use, https://fakturai.de) and securely set FAKTURAI_API_KEYin your environment, not the source code. Install httpx with python -m pip install httpx, save the block as generate.py, then run python generate.py. Use synthetic data only; the request is sent to the configured instance.
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": "Consulting", "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)For requests, replace httpx.post with requests.post — the payload is identical.
Node.js / TypeScript example (fetch)
Use Node.js 22 or newer, save as generate.mjs and run node generate.mjs with the same environment variables. Both examples save invoice.pdf only on201 application/pdf. Error responses are JSON, not a PDF or a passed 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: "Consulting", 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()));The payload is the same JSON structure regardless of language. In TypeScript, add a type definition for the payload; the result is a Buffer / ArrayBuffer.
Key mandatory fields
invoice_date(BT-2) anddue_date(BT-9) — YYYY-MM-DD;currency(BT-5) defaults to EUR.buyer.nameand nestedbuyer.addresswithline_one,postcode,cityandcountry.- At least one line:
name,quantity,unit,unit_price,vat_rate. Quantity, unit price and VAT rate are decimal strings, not JSON numbers.
MCP integration for AI agents
Alongside the REST API, a MCP server exposes the same generate-and-check logic as a tool call. AI agents and MCP clients can generate e-invoices without crafting HTTP manually. See create e-invoices per API & MCP.
What fakturai does not replace
fakturai generates and checks the technical format and structural compliance per EN 16931 with a KoSIT-aligned accept gate. The content and tax correctness of the JSON payload, GoBD-compliant archiving and accounting treatment remain your responsibility. fakturai is not tax advice and makes no legal, tax or delivery guarantees.
Next step
Free German e-invoice validator · Create an API key · API docs
Related: API & MCP creation guide · E-invoice example (XML) · XRechnung validation errors