Factur-X and ZUGFeRD e-invoices from JSON
, 6 min read
Factur-X (called ZUGFeRD in Germany) is an ordinary-looking PDF invoice with a machine-readable copy of the same invoice inside it. People open the PDF; the buyer’s accounting software reads the XML and books the invoice without anyone retyping it. The XML follows EN 16931, the European e-invoicing standard, and the PDF must be PDF/A-3, the archival flavour of PDF.
Building one yourself means producing CII XML that passes the official rules, then turning your PDF into valid PDF/A-3 with the XML attached and the right metadata. pagenull does both in one request.
1. Send the invoice as data
Unlike a plain invoice PDF, where you send pre-formatted strings, an e-invoice needs real numbers: quantities, net unit prices and VAT rates. We calculate each line, the VAT breakdown and the totals with exact decimal arithmetic, so the printed page and the XML can’t disagree.
curl https://api.pagenull.dev/v1/invoices \
-H "Authorization: Bearer $PAGENULL_API_KEY" \
-H "Content-Type: application/json" \
-o INV-2026-0142.pdf \
-d '{
"invoice": {
"number": "INV-2026-0142",
"issueDate": "2026-11-03",
"dueDate": "2026-11-17",
"buyerReference": "PO-7781",
"seller": {
"name": "Northwind Studio EOOD",
"address": { "line1": "12 Example Street", "city": "Sofia", "postcode": "1000", "country": "BG" },
"vatId": "BG123456789"
},
"buyer": {
"name": "Acme Robotics EOOD",
"address": { "line1": "1 Vitosha Blvd", "city": "Sofia", "postcode": "1000", "country": "BG" }
},
"lines": [
{ "description": "API integration, October", "quantity": 12, "unit": "HUR", "unitPrice": "85.00", "vatRate": 20 },
{ "description": "PDF template design", "quantity": 1, "unitPrice": "340.00", "vatRate": 20 }
],
"payment": { "iban": "BG80BNBG96611020345678" }
}
}'You get back a PDF/A-3b file with factur-x.xml inside, using the EN16931 profile. The printed side uses the built-in invoice template; pass template with one of your saved template ids to use your own layout. It receives the same computed values, already formatted.
2. Pick the right VAT category
Each line has a vatCategory. The common ones for EU businesses:
S | Standard or reduced rate, with its vatRate, e.g. 20 or 9. The default. |
AE | Reverse charge, e.g. services to a business in another EU country. Needs both VAT numbers. |
K | Intra-community supply of goods. Needs both VAT numbers; the buyer’s country is used as the delivery country. |
Z, E, G, O | Zero rated, exempt, export outside the EU, not subject to VAT. |
Everything except S takes vatRate: 0 and prints an exemption note (“Reverse charge” and so on; override it with vatExemptionReason). If the invoice breaks one of the standard’s rules, such as reverse charge without the buyer’s VAT number, you get 422 invalid_invoice naming the field, before anything is rendered. Which category applies is a tax question; ask your accountant when in doubt.
3. Choose a profile
- EN16931 (the default): the full standard. Use this unless a buyer asks for something else.
- BASIC: the same structure with fewer optional fields.
- MINIMUM: only the header and totals, for booking support. Germany doesn’t accept it as an e-invoice.
4. Check it yourself
Don’t take our word for it. Mustang is a free, open-source validator: it checks the PDF/A part with veraPDF and the XML against the official Factur-X rules. With Java installed:
java -jar Mustang-CLI-2.26.0.jar --action validate --source INV-2026-0142.pdf
# … <summary status="valid"/>Our test suite runs exactly this on every case we support (each profile, reverse charge, intra-community supply, mixed rates, prepaid amounts, credit notes) and fails on any error or warning.
Test renders
Add "test": true while you integrate. The PDF is watermarked and the XML carries the note TEST RENDER. NOT A VALID INVOICE. Software that reads only the XML would otherwise treat it as real, so never send a test render to a customer.
What isn’t supported yet
- Discounts and surcharges on the whole invoice (put a discount on its own line instead).
- Payment methods other than SEPA credit transfer.
- XRechnung and UBL, the XML-only formats some public-sector buyers require.
- Labels in other languages in the built-in layout. Numbers and dates follow
language; save your own template for translated labels.
Where the invoice lives
As with every render, we keep nothing: not the data, the XML or the PDF. Invoices are legal records you’re required to keep for years, so store the returned file under your own retention policy, or pass deliverTo with a presigned URL in your own bucket.