Send an E-Invoice That Will Be Accepted¶
From a normal invoice payload to a Factur-X PDF, checking at each step what it was held to. About ten minutes.
You need an API key; see Authentication if you do not have one.
What we are building¶
A Factur-X invoice: a PDF/A-3 with factur-x.xml embedded inside it. One
file that a person can read and a machine can parse. It needs no network, no
membership and no agreement with the recipient — you email it like any other
PDF, which is why it is the format to start with.
The rules it must satisfy are EN 16931, the European standard. Most of the work is not rendering; it is the coded fields the standard requires and your existing invoice data probably does not carry.
1. Start from what fails¶
Send the invoice you already have. Nothing is stored and it costs no quota, so this is free to get wrong.
curl https://invoicepdfs.com/api/v1/documents/validate-compliance \
-H "Authorization: Bearer $INVOICEPDFS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"document_type": "invoice",
"profile": "factur_x_en16931",
"data": {
"invoice_number": "INV-2026-0042",
"issue_date": "2026-09-08",
"currency": "EUR",
"seller": {"name": "Acme GmbH"},
"buyer": {"name": "Client SARL"},
"line_items": [
{"name": "Consulting", "quantity": "10", "unit_price": "150.00"}
]
}
}'
You get a list of what is missing, with the business term the standard uses:
{
"data": {
"profile": "factur_x_en16931",
"valid": false,
"in_scope": true,
"fully_checked": true,
"violations": [
{"rule": "BT-118", "path": "tax_scheme", "message": "...", "severity": "fatal", "ruleset": "semantic"}
]
}
}
Work down that list. Each rule is a term you can look up in EN 16931 itself
rather than only in our documentation, and path is the field in your request.
2. Add what the standard needs¶
Four kinds of thing are usually missing from an invoice that was only ever meant to become a PDF.
A tax scheme (BT-118). One per document, and we will not guess it. An
e-invoice can only carry VAT — which is exactly why guessing is tempting and
wrong, since it would put a claim in a document a tax authority reads that you
never made.
Coded tax categories, not just a rate. Two lines at 0% may be zero-rated, exempt, reverse-charge or outside scope, and the rate does not say which. These are different VAT breakdown groups with different mandatory fields.
GET /api/v1/reference/tax-categories returns the full list.
Coded units (BT-130). "hours" is not a unit code; HUR is. "each" is
C62. See GET /api/v1/reference/unit-codes.
Real addresses and tax identification. A country on both parties, and a tax id for the seller on any standard-rated line.
Re-run the check until valid is true.
Check fully_checked, not just valid
valid says nothing fatal was found. fully_checked says every ruleset
that applies actually ran. If the second is false, the first was decided
on a weaker basis — the authoritative Schematron tier did not execute. A
ruleset that could not run is never reported as a pass.
3. Get the PDF¶
Same data, one field different from an ordinary render:
curl https://invoicepdfs.com/api/v1/documents/render \
-H "Authorization: Bearer $INVOICEPDFS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"document_type": "invoice",
"data": { ...the payload you just got passing... },
"output": {"format": "facturx_pdf"}
}'
This path fails closed. The document is validated before it is rendered, and
a fatal violation means no PDF at all — 422 with code: "compliance_failed"
and every violation in details. A file that claims EN 16931 conformance and
does not have it is worse than no file, because the claim travels with it.
4. Keep the receipt¶
The render response records what the file was held to:
{
"data": {
"id": "rnd_01ABC",
"compliance": {
"profile": "factur_x_en16931",
"ruleset_version": "EN16931-CII 1.3.16",
"fully_checked": true,
"advisories": []
},
"download_url": "https://invoicepdfs.com/api/v1/renders/rnd_01ABC/download?token=..."
}
}
That is worth storing alongside your own invoice record. The rulesets are
published artefacts that get revised, so a year from now "was this compliant?"
is only answerable with the version beside it — and
GET /api/v1/renders/{render_id} gives you back what was written down, not a
re-check against today's rules.
compliance is null on a plain pdf render. Its presence is how you tell a
hybrid PDF from an ordinary one.
Sending it somewhere else¶
Factur-X is a file you email. If your recipient wants Peppol instead, ask for the XML and hand it to your access point:
curl https://invoicepdfs.com/api/v1/documents/xml \
-H "Authorization: Bearer $INVOICEPDFS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"document_type": "invoice", "profile": "peppol_bis_billing_3", "data": { ... }}'
Note the profile changed, and so did the rules: Peppol BIS adds its own layer on top of EN 16931 — a buyer reference becomes mandatory, for one — so re-run the check rather than assuming a document that passed Factur-X will pass this.
We do not transmit. Getting a document onto Peppol is the Access Point role, with membership, certificates and accreditation attached, and we are not one. We make the document correct; somebody else moves it.
Where this applies¶
The profile table, and the caveats that matter before you plan around it — Peppol is not the EU, and a US-only business has nothing here to comply with — are in the E-invoicing Compliance reference. Worth reading once before you choose a profile.