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:

  1. Generated-then-validated. The embedded factur-x.xml is 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.
  2. Re-read. The served PDF is opened by our own /invoice/read and the embedded factur-x.xml is extracted bit-for-bit - the XML you receive flat in data.xml is 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 contractual 413 above 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" }
  }
}
FieldDescription
pdf_base64The 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.
xmlThe 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)
profileThe URN emitted in BT-24 and in the Factur-X XMP - always urn:cen.eu:en16931:2017 in v1
validatedAlways true on a 200 - the echo of the generated-then-validated and re-read post-conditions
computedThe 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_treatmentThe 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.xml extracted from the served PDF is identical to data.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.

StatuscodeCaseBilled
200-PDF generated, its embedded XML validated and re-read (validated: true)yes
400INVALID_INPUTInvalid JSON, unknown field / typo (named in the message), or any query parameter (none accepted)no
400INVALID_INVOICE_INPUTConformant generation impossible - all issues in issues[]no
413PAYLOAD_TOO_LARGEBody over the 10 MB capno
404NOT_FOUNDUnknown routeno
500INTERNALReserved 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 exposedno

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/resolve and /company/vat are 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