XRechnung API: A developer's guide to Germany's e-invoicing mandate
What XRechnung is, what the German B2B e-invoicing mandate requires, and how to send and receive EN 16931-compliant invoices through a REST API.
If you operate a business in Germany or bill German customers, you're already under the 2025 E-Rechnung mandate. This guide walks through what XRechnung actually is, what you must support, and how to implement it without drowning in XML. Our XRechnung API handles every step described below; if you're evaluating vendors, that's the product page.
What is XRechnung?
XRechnung is the German reference implementation of the EU invoicing standard EN 16931. It's a structured XML format that machines can read end-to-end — no OCR, no PDF parsing, no manual entry. It's been mandatory for invoicing German federal government buyers since 2020, and from 2025 the scope expanded to all domestic B2B transactions.
XRechnung comes in two XML syntaxes:
- UBL (Universal Business Language) — an OASIS standard, used across the EU
- UN/CEFACT CII (Cross Industry Invoice) — older, still valid
Both carry the same semantic content. Most German public-sector portals accept either; pick UBL if you're starting fresh.
What the mandate actually requires
The 2025–2028 rollout has three phases:
| Date | Who | What changes |
|---|---|---|
| 2025-01-01 | All German VAT-registered businesses | Must be able to receive E-Rechnung from any domestic supplier |
| 2027-01-01 | Businesses with > €800k annual turnover | Must send E-Rechnung for domestic B2B (small businesses still have a grace period) |
| 2028-01-01 | All businesses regardless of size | Must send E-Rechnung for all domestic B2B |
"Receive" means: your AP process can accept a structured invoice (not a PDF) and process it without a human re-keying data. Most companies underestimate this. If your current workflow is "invoice arrives as PDF attachment, bookkeeper types it into DATEV", you're not compliant.
XRechnung vs ZUGFeRD vs Factur-X — what's the difference?
They're easy to confuse. Here's the short version:
- XRechnung — pure XML, used for invoicing the German public sector and (now) B2B.
- ZUGFeRD — a hybrid invoice: a normal PDF/A document with the full XML invoice embedded inside. Humans see the PDF; machines parse the XML. Popular in German B2B because it's friendly to legacy processes.
- Factur-X — the French equivalent of ZUGFeRD. Identical technically; different branding.
A well-designed API should handle all three interchangeably. The data model is the same — it's the packaging that differs.
The minimum data model
At the semantic level, every EN 16931 invoice must carry:
- Invoice number, issue date, due date
- Seller identification (name, address, VAT ID)
- Buyer identification, plus the Leitweg-ID for public-sector buyers
- Line items with descriptions, quantities, unit prices
- VAT breakdown per rate
- Totals (net, tax, gross)
- Payment terms and bank details
The trickiest field for developers new to the space is the Leitweg-ID — a routing identifier mandated for public-sector buyers. It's not optional and it's not obvious where to get it (the buyer supplies it). B2B invoices don't need it.
Validating an invoice with the API
Here's what a typical validation call looks like:
curl -X POST https://api.postscale.io/v1/invoices/validate \
-H "Authorization: Bearer ps_live_..." \
-H "Content-Type: application/xml" \
--data-binary @invoice.xml
Response on a valid invoice:
{
"valid": true,
"format": "xrechnung-ubl",
"standard": "EN 16931",
"profile": "urn:cen.eu:en16931:2017",
"errors": [],
"warnings": []
}
Response on an invalid invoice is structured so you can fix fields programmatically:
{
"valid": false,
"errors": [
{
"rule": "BR-CO-15",
"path": "/Invoice/LegalMonetaryTotal/TaxInclusiveAmount",
"message": "Invoice total must equal sum of line totals plus tax"
}
]
}
A well-designed validator returns both the rule ID (from the official Schematron) and a plain-English message. The former is for your test suite; the latter is for your error UI.
Sending an invoice
Once validated, sending reduces to either:
- Email delivery — attach the XML (or PDF/A with embedded XML for ZUGFeRD) to a regular email and send. This works for most B2B cases.
- Peppol — for cross-border EU or public-sector delivery, use the Peppol network. Peppol is a closed network of access points; you publish your receiver ID and buyers send via their own access point.
curl -X POST https://api.postscale.io/v1/invoices/send \
-H "Authorization: Bearer ps_live_..." \
-H "Content-Type: application/json" \
-d '{
"format": "zugferd",
"from": "billing@yourcompany.com",
"to": "accounting@customer.de",
"invoice_xml": "<base64-encoded-xml>",
"pdf_template": "standard"
}'
Receiving and parsing inbound invoices
This is the side most companies neglect. From 2025-01-01, you must accept incoming E-Rechnung. The minimum-viable setup:
- Dedicated inbox, e.g.
invoices@yourcompany.com - MX record routing that inbox through an inbound-email API
- Webhook that fires on every message, attaches parsed invoice JSON
- Your AP system reads the JSON and books it
With Postscale:
{
"id": "in_01HY...",
"to": "invoices@yourcompany.com",
"from": "billing@supplier.de",
"subject": "Invoice #4421",
"attachments": [
{
"filename": "invoice-4421.xml",
"content_type": "application/xml",
"parsed_invoice": {
"invoice_number": "4421",
"issue_date": "2026-04-15",
"seller": { "name": "Supplier GmbH", "vat_id": "DE123456789" },
"buyer": { "name": "You GmbH", "vat_id": "DE987654321" },
"total_net": 1250.00,
"total_vat": 237.50,
"total_gross": 1487.50,
"currency": "EUR",
"line_items": [...]
}
}
]
}
The parsed_invoice field means your AP workflow never touches XML directly — it works off JSON with typed fields.
What to test
If you're building this today, the must-pass test cases are:
- Valid UBL invoice from a German supplier — parse succeeds, totals reconcile.
- Valid CII invoice — same data, different syntax; your code should normalize.
- ZUGFeRD PDF with embedded XML — extract XML, validate it, ignore the PDF rendering.
- Invoice with a Leitweg-ID — required field present for public-sector buyers.
- Invoice with missing mandatory field — your system rejects cleanly with a fixable error.
- Invoice with reverse-charge VAT — edge case that breaks naive parsers.
- Credit note (Rechnungskorrektur) — same format, negative amounts.
Run these against your staging environment before you open a single production inbox.
Key takeaways
- E-Rechnung is XML, not a PDF with an XML attachment attribution. Build around the structured data, not the pixels.
- Receiving is mandatory from 2025; sending phases in through 2028. Start with receive.
- Handle XRechnung, ZUGFeRD, and Factur-X interchangeably — they're the same data model.
- Use a validator that returns structured errors, not a generic "invalid invoice".
- The Leitweg-ID is the field everyone misses.
If you're implementing this, our Invoices API handles validation, sending, and inbound parsing through a single EU-hosted endpoint. Or read the full invoice docs for reference.