Starting point: the invoice already exists, just in the wrong format
Many companies have written their invoices in Word or Excel for years and send them as PDF. The obligation to send electronic invoices does not change what is on the invoice — only the form in which the buyer accepts it. The obvious route is therefore not to replace the invoicing process but to put a pipeline behind it: read the PDF, rebuild it as [[en-16931|EN 16931]] data, check it, generate [[zugferd|ZUGFeRD]] or [[xrechnung|XRechnung]].
This works because PDFs from templates have a text layer and are uniform. Regular expressions read document number, date, line items and totals reliably — no language model, no cost per document, no external service. The real work is elsewhere: mapping what was read onto the standard. This guide describes where that fails in practice.
What is not on the PDF
Before the first e-invoice exists, four pieces of information are missing that a classic invoice never had to carry:
| Information | Where it comes from | Rule |
|---|---|---|
| Seller's bank account | Master data per department | BR-DE-1 requires payment details (BG-16) |
| Contact person with phone and e-mail | Master data | BR-DE-2, plus BR-DE-6 for XRechnung |
| Buyer's e-mail address | Customer table | no sending without it |
| Leitweg-ID / buyer reference | Customer table | BT-10, BR-DE-15 — for Germany in both formats |
🔴 The customer table is not optional. Neither the e-mail address nor the [[leitweg-id|Leitweg-ID]] is printed on an incoming document — neither was ever needed. A buyer without an entry must stop the document, not wave it through: better a note back to the department than an undeliverable e-invoice.
The seven mapping errors
These errors do not show up while reading the PDF and not in your own schema check. They show up only when the data runs against the real API.
1. The document type is credit_note, not creditnote
An enum value with one letter off is a schema error, not a business-rule
violation. The valid values are invoice, credit_note, proforma,
correction.
2. The due date is mandatory
If the document carries no due date, the issue date is the right fallback — not
leaving it out. A missing dueDate is rejected.
3. The country of the country rules has to be named
German extra fields such as the buyer reference live in their own group, and
that group needs the country code: countrySpecific.countryCode: "DE". Without
it the group is silently dropped — and the Leitweg-ID with it.
4. There is no paymentMeans
The bank account belongs to the seller (seller.bankAccount), not to a separate
payment-means object. Anyone coming from another implementation of the standard
looks for the field in the wrong place.
5. BR-DE-2: the contact person is missing
It is usually printed on the letterhead. It is missing from the data because it
was never a field on any invoice form. Name plus phone or e-mail — as
seller.contact.
6. BR-DE-1: a bank account alone is not enough
Payment details additionally need a payment method:
paymentMethods: [{ type: "bank_transfer" }]. IBAN and BIC without a method are
incomplete. And a payment-terms element without text violates
PEPPOL-EN16931-R008 — paymentTerms.description must not be empty.
7. BR-27: negative unit prices are rejected
On the PDF the credit note shows minus amounts. In EN 16931 the document
type carries the sign: type: "credit_note", all amounts positive, quantity
and unit price positive. Passing the numbers through from the PDF produces a
document that looks structurally clean and fails BR-27.
Recompute before the API does
The line items of a legacy invoice do not always add up to the stated net amount — rounding, attachments, hand-corrected totals. A pipeline that does not recompute forwards the error and gets it back as BR-CO-10. Three checks with a tolerance of two cents are enough: items against net, net plus tax against gross, tax amount against tax rate.
A fourth comparison costs nothing and catches a different mistake: if the document carries a different VAT ID than the department that sent it, the mail went to the wrong mailbox.
Validation means two things
The [[validierung|validation]] returns two answers, and they are not the same:
validanswers the object-level rules — mandatory fields, totals, identifier formats.results[]holds, per target format, the generated XML checked against XSD and [[schematron|Schematron]] — the rules a recipient actually rejects on.
An approval should require both. Anyone checking valid alone sees format
violations only at generation time — or not at all.
Two documents in one file
From practice: one PDF file, two invoices, one per page. A pipeline that assumes
"one PDF = one invoice" processes the first and loses the second without an
error. So read page by page and run every page through the parser on its own;
pages without a document — attachments, terms — yield nothing and do no harm. A
parser that cannot find a field returns null and does not guess: what is
missing surfaces in the downstream check, by name.
What does not pass goes back
If a document fails, nothing may reach the buyer. Instead: a line in the log, the source PDF unchanged in the archive, and a mail back to the department that sent it — with file name, document number and every reason in plain language. Correct, resend. The buyer never notices.
One detail: the hash the API returns with the generated document is the hash of the output bytes. A PDF carries a creation timestamp, so this value changes on every call — it is useless as a duplicate marker in the file name. A fingerprint of your own over the invoice data with a stable key order is not: the same document received twice yields the same file name, the same document with changed content a different one.
Ready-made pipeline
The setup described here — one mailbox per department, rule-based reading,
master data plus customer table, three validation stages, an error path back to
the sender — is available as an n8n workflow template:
PDF invoices from mailbox → ZUGFeRD / XRechnung.
The result is a [[pdf-a-3|PDF/A-3]] with embedded factur-x.xml (EN 16931
profile) or an XRechnung — whichever the buyer accepts.