POST /invoice/generate-pdf
Turns the same business JSON as /invoice/generate
into the hybrid deliverable of the French e-invoicing reform: a
readable PDF/A-3B invoice with an embedded CII D16B XML
(factur-x.xml, EN 16931 profile) and the Factur-X XMP metadata that binds
the two. Where /invoice/generate serves the
XML core alone, this endpoint serves the complete Factur-X document -
the file a human opens as a PDF and a machine parses as structured EN 16931.
Factur-X is the hybrid format of the reform’s legal core: large businesses must receive electronic invoices from 2026-09-01, and small and medium businesses must issue them from 2027-09-01. Your agent writes the invoice; the official rule set signs off; the output is the PDF/A-3 file both sides can use.
The double guarantee - the point
This is, to our knowledge, the only x402 Factur-X generator that re-validates and re-reads its own output. Two post-conditions are enforced before any byte is served or billed:
- Generated-then-validated. The embedded
factur-x.xmlis re-parsed and re-passed through the four layers of/invoice/validate- official XSDs, EN 16931 business rules with their BR-xx ids, Factur-X profile conformance, and identifier plausibility - in strict mode, on the same pinned CEN artefacts. - Re-read. The served PDF is opened by our own
/invoice/readand the embeddedfactur-x.xmlis extracted bit-for-bit - the XML you receive flat indata.xmlis provably the exact XML inside the PDF.
A failure of either post-condition is a 500: never served, never
billed - an internal bug, logged, never exposed. A 200 therefore carries
data.validated: true by construction: the same official yardstick that
/invoice/validate sells as a verdict, applied
to the document you get back.
The runtime is stateless pure computation - no network call, no secret, no
store - and the output is byte-deterministic: the same input yields the
same PDF, byte for byte, because every value comes from the invoice (its
dates and ids), never from the wall clock. See the live
/catalog for the authoritative endpoint listing and
price.
x402 golden rule: the agent pays for the answer to its question. The question is “give me a complete Factur-X document” - so the only billed case is a 200 with a generated, self-validated, self-read PDF. Input the service cannot turn into a conformant invoice is a typed 400 with all actionable issues reported together, not billed.
Request
POST with Content-Type: application/json. The input schema is identical
to /invoice/generate
- the same business JSON (parties, lines, VAT, optional totals), the same
deny_unknown_fields, the same plain-decimal rule, the same computed-if-absent / verified-if-provided / never-repaired totals policy. It is not duplicated here: read that page for the field-by-field reference. Body cap: 10 MB (a contractual413above it; not billed).
Only the output differs: instead of the XML alone, you receive the XML wrapped in a PDF/A-3B envelope.
The optional vat_treatment field holds here exactly as it does on
/invoice/generate:
the determined legal mention (reverse charge, exemption reason) is written into
BT-120, so it travels in the embedded CII XML - the bit-exact re-read
post-condition still holds - and in the rendered page, where the VAT
breakdown of the template carries its reason. The response returns the sourced
verdict in data.vat_treatment. Absent, the output is unchanged.
syntax: "ubl" remains refused (Factur-X embeds CII).
200 response - UnifiedResponse
{
"data": {
"pdf_base64": "JVBERi0xLjcKJ…",
"xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rsm:CrossIndustryInvoice …>…</rsm:CrossIndustryInvoice>",
"profile": "urn:cen.eu:en16931:2017",
"validated": true,
"computed": { "totals": { ... }, "vat_breakdown": [ ... ] },
"vat_treatment": { ... }
},
"provenance": {
"source": "en16931-artefacts",
"fetched_at": "2026-07-12T14:18:41Z",
"freshness": { "kind": "snapshot", "as_of": "2026-07-11T00:00:00Z" }
}
}
| Field | Description |
|---|---|
pdf_base64 | The complete PDF/A-3B document, base64-encoded - readable as a PDF, with the CII XML and the Factur-X XMP embedded. Decode to get the .pdf file. |
xml | The embedded CII D16B document, served flat as well - byte-for-byte the factur-x.xml inside the PDF (the re-read post-condition proves it) |
profile | The URN emitted in BT-24 and in the Factur-X XMP - always urn:cen.eu:en16931:2017 in v1 |
validated | Always true on a 200 - the echo of the generated-then-validated and re-read post-conditions |
computed | The BG-22 totals and BG-23 VAT breakdown actually serialized in the XML, as exact decimal strings - the agent’s read-back, whether the totals were computed or verified |
vat_treatment | The sourced VAT verdict and the mention actually written into BT-120; omitted when the vat_treatment input field is absent |
provenance.freshness: kind is snapshot and as_of dates the normative
artefacts the invoice was generated, validated and re-read against (EN 16931
validation artefacts + Factur-X specification) - there is no fetch and no clock
behind the guarantee, it is pinned to a rule-set version.
Post-conditions - stated plainly
Every 200 has been proven to satisfy, before it was served:
- PDF/A-3B, veraPDF-conformant: the envelope passes the veraPDF PDF/A-3B profile - a durably archivable, standards-conformant PDF, not merely “a PDF with a file attached”.
- Bit-exact embedding: the
factur-x.xmlextracted from the served PDF is identical todata.xml- the readable document and the machine document cannot drift. - Byte-deterministic: the same input produces the same PDF, byte for byte. Timestamps, ids and the XMP come from the invoice, never from the clock - reproducible output an agent can hash and de-duplicate.
Errors and status matrix
The error surface is identical to
/invoice/generate:
two top-level 400 codes (INVALID_INPUT for a body that is not the schema -
unknown field, bad JSON, any query parameter; INVALID_INVOICE_INPUT for a
schema-valid body that cannot be generated conformant), with all actionable
issues[] reported together - stable codes including
inconsistent_totals, rule_violation (BR-xx), implausible_identifier,
amount_out_of_range (checked arithmetic, never a panic), invalid_decimal.
See that page for the full issue-code table and a real multi-violation example.
| Status | code | Case | Billed |
|---|---|---|---|
| 200 | - | PDF generated, its embedded XML validated and re-read (validated: true) | yes |
| 400 | INVALID_INPUT | Invalid JSON, unknown field / typo (named in the message), or any query parameter (none accepted) | no |
| 400 | INVALID_INVOICE_INPUT | Conformant generation impossible - all issues in issues[] | no |
| 413 | PAYLOAD_TOO_LARGE | Body over the 10 MB cap | no |
| 404 | NOT_FOUND | Unknown route | no |
| 500 | INTERNAL | Reserved to a violated post-condition: the embedded XML failed its own validation, the re-read did not match, or the PDF failed PDF/A-3B - an internal bug; the output is never served, detail logged, never exposed | no |
A twisted input is never a 5xx: everything the agent can fix is a 400 with named issues. A 5xx here means we broke a promise, and you were not billed.
Limits of v1 - stated plainly
- EN 16931 profile only: the embedded XML is the Factur-X EN 16931 profile (the reform’s legal core) - no MINIMUM/BASIC/EXTENDED, no national CIUS (XRechnung), no Peppol BIS 3.0.
- CII embedded only: the document carries a CII D16B XML - not UBL. UBL is read and validated elsewhere, never embedded here.
- PDF/A-3B: the level-B conformance (visual reproduction), not A-3U or A-3A - no tagged/accessible structure guarantee.
- No branding, no layout: the visible page is a minimal deterministic render of the invoice - the value is the conformance and the embedded XML, not the template. This is not a design tool.
- No numbering, no archiving, no signature, no transmission:
invoice_number(BT-1) is yours; the service is stateless pure computation - no duplicate detection, no e-signature, no PDP transmission. - Plausibility is syntax-only: identifiers are checked for syntactic
plausibility (mod-97, Luhn, VAT syntax), never against a registry. Generate
the document, then check the counterparty -
/company/resolveand/company/vatare separate paid calls.
The full invoice cycle
This closes the loop read → validate → generate → generate-pdf:
POST /invoice/read- faithfully extract an incoming invoice into the EN 16931 semantic model.POST /invoice/validate- the same four-layer rule set, sold as a standalone verdict on any incoming invoice.POST /invoice/generate- the XML core: the same business JSON, the CII D16B alone (no PDF).POST /invoice/generate-pdf(this page) - the complete document: the same core wrapped in a readable, archivable, self-validated PDF/A-3B.
Generate the invoice, then check the counterparty:
/company/resolve confirms the SIREN identity and
/company/vat checks the live EU VAT status - two
paid calls that turn a syntactically plausible party into a verified one.
Attribution
The conformance guarantee is computed against the official EN 16931 validation
artefacts (CEN, published under EUPL 1.2), the Factur-X specification
(FNFE-MPE) and the PDF/A-3 standard (ISO 19005-3, checked with veraPDF) - the
exact versions are pinned in every response
(provenance.freshness.as_of). The invoice content itself comes from the JSON
you posted.
See also
POST /invoice/generate- the XML core alone, when you do not need the PDF envelope.POST /invoice/validate- re-prove any incoming or generated invoice with the same rule set.POST /invoice/vat-treatment- the sourced VAT determination behind thevat_treatmentfield, sold on its own.- For agents - discovery surfaces, the live
/catalogand how settlement works. - Quickstart - the 402 -> pay -> replay loop with runnable snippets.