One base URL, three endpoints, no SDK required. Works without a key (50 documents/month per IP); add X-Api-Key for the paid tiers. Every generated document is validated by the official KoSIT validator before it leaves the server.
| Endpoint | Purpose | Input | Output |
|---|---|---|---|
POST /v1/invoices | generate | JSON (see model below) | XML (XRechnung) or PDF (ZUGFeRD); or JSON with base64 + report |
POST /v1/validate | validate | raw XML body, or multipart file (XML/PDF) | JSON report: valid, errors[], warnings[] |
POST /v1/parse | read | raw XML body, or multipart file (XML/PDF) | JSON: parties, lines, totals, payment |
GET /v1/usage | quota | — | plan, used, limit |
# XML curl -X POST https://xrechnung.dev/v1/validate --data-binary @invoice.xml # ZUGFeRD / Factur-X PDF (the embedded XML is extracted and checked) curl -X POST https://xrechnung.dev/v1/validate -F file=@invoice.pdf
{"valid": false, "scenario": "EN16931 XRechnung (CII)", "profile": "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0",
"errors": [{"code": "BR-DE-15", "message": "Das Element \"Buyer reference\" (BT-10) muss übermittelt werden.", "location": "/rsm:CrossIndustryInvoice[1]/…"}],
"warnings": [], "counts": {"errors": 1, "warnings": 0, "informations": 0}, "usage": {"plan": "free", "used": 1, "limit": 50}}
curl -X POST https://xrechnung.dev/v1/invoices -H "Content-Type: application/json" -H "X-Api-Key: $KEY" -o RE-2026-0001.pdf -d '{
"format": "zugferd", "language": "de",
"invoice": {
"number": "RE-2026-0001", "issue_date": "2026-09-07", "delivery_date": "2026-09-05",
"seller": {"name": "Beispiel GmbH", "vat_id": "DE123456789", "email": "rechnung@beispiel.de", "phone": "+49 30 1234567",
"address": {"street": "Musterstr. 1", "postcode": "10115", "city": "Berlin", "country": "DE"}},
"buyer": {"name": "Kunde AG", "vat_id": "DE987654321", "email": "ap@kunde.de",
"address": {"street": "Kundenweg 9", "postcode": "80331", "city": "München", "country": "DE"}},
"buyer_reference": "04011000-12345-67",
"lines": [{"description": "Beratung September", "quantity": "10", "unit": "HUR", "unit_price": "120.00", "vat_rate": "19"},
{"description": "Fahrtkosten", "quantity": "1", "unit": "C62", "unit_price": "85.50", "vat_rate": "19"}],
"payment": {"iban": "DE02120300000000202051", "bic": "BYLADEM1001", "due_date": "2026-09-21", "terms": "Zahlbar innerhalb von 14 Tagen ohne Abzug."},
"notes": ["Vielen Dank für Ihren Auftrag."]
}}'
| Field | Notes |
|---|---|
format | xrechnung (CII XML, XRechnung 3.0 profile) or zugferd (PDF/A-3 with EN 16931 XML) |
output | binary (default: the file) or json (base64 + full validation report) |
invoice.type_code | 380 invoice (default), 381 credit note, 384 corrected invoice; set preceding_invoice_number for 381/384 |
invoice.seller | vat_id or tax_number required; email and phone required (XRechnung BR-DE-6/7); contact_name defaults to the company name |
invoice.buyer_reference | Leitweg-ID for public-sector buyers; defaults to the buyer name (XRechnung requires BT-10) |
lines[].unit | UN/ECE Rec. 20: HUR hour, C62 piece, DAY, MON, KGM, MTR, E48 service unit |
lines[].vat_category | S standard (default), Z zero, E exempt, AE reverse charge, K intra-EU, G export, O out of scope — exemption codes/texts are added automatically |
invoice.small_business | true for § 19 UStG: no VAT, mandatory note added |
invoice.payment | iban → SEPA credit transfer (code 58); due_date, terms, reference (defaults to the invoice number) |
Money fields are strings or numbers with up to 2 decimals; totals are computed server-side per line with round-half-up, so they always satisfy the EN 16931 arithmetic rules.
curl -X POST https://xrechnung.dev/v1/parse -F file=@incoming.pdf
{"syntax": "CII", "number": "RE-2026-0001", "issue_date": "2026-09-07", "currency": "EUR",
"seller": {"name": "Beispiel GmbH", "vat_id": "DE123456789", …}, "buyer": {…},
"lines": [{"id": "1", "description": "Beratung September", "quantity": "10", "unit": "HUR", "unit_price": "120.00", "vat_rate": "19", "net_total": "1200.00"}],
"taxes": [{"category": "S", "rate": "19", "basis": "1285.50", "tax": "244.25"}],
"totals": {"net": "1285.50", "tax": "244.25", "gross": "1529.75", "due": "1529.75"},
"payment": {"iban": "DE02120300000000202051", "due_date": "2026-09-21", "reference": "RE-2026-0001"}}
import requests
API = "https://xrechnung.dev"
H = {"X-Api-Key": "xr_live_..."} # omit for the free tier
report = requests.post(f"{API}/v1/validate", data=open("invoice.xml", "rb"), headers={**H, "Content-Type": "application/xml"}).json()
assert report["valid"], report["errors"]
pdf = requests.post(f"{API}/v1/invoices", json={"format": "zugferd", "invoice": invoice}, headers=H)
pdf.raise_for_status(); open("RE-2026-0001.pdf", "wb").write(pdf.content)
data = requests.post(f"{API}/v1/parse", files={"file": open("incoming.pdf", "rb")}, headers=H).json()
print(data["totals"]["gross"], data["payment"]["iban"])
$ch = curl_init("https://xrechnung.dev/v1/invoices");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(["format" => "xrechnung", "invoice" => $invoice]),
CURLOPT_HTTPHEADER => ["Content-Type: application/json", "X-Api-Key: $key"],
CURLOPT_RETURNTRANSFER => true,
]);
$xml = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) !== 200) { $err = json_decode($xml, true); /* $err["detail"] */ }
file_put_contents("RE-2026-0001.xml", $xml);
const res = await fetch("https://xrechnung.dev/v1/invoices", {
method: "POST", headers: {"Content-Type": "application/json", "X-Api-Key": process.env.XR_KEY},
body: JSON.stringify({format: "zugferd", invoice})});
if (!res.ok) throw new Error(await res.text());
await fs.promises.writeFile("RE-2026-0001.pdf", Buffer.from(await res.arrayBuffer()));
| Status | Meaning |
|---|---|
| 401 | invalid or inactive API key |
| 422 | invoice data rejected (message names the field and the rule, e.g. "seller.phone is required (XRechnung BR-DE-6)") — or the document could not be read |
| 413 | document larger than 5 MB |
| 429 | monthly quota reached — upgrade at /#pricing |
Documents are processed in memory and never stored. Quota resets on the first of each month (UTC). Postman: collection. Full schema: OpenAPI.
Get an API key — €19/month OpenAPI reference