# Read an inbound Factur-X, CII or UBL e-invoice before paying it > One paid call turns a Factur-X, CII or UBL document into the EN 16931 semantic model — parties, lines, VAT, IBAN, dates — faithfully, with no OCR guesswork. Published 2026-08-10 · Updated 2026-08-15 · HTML version: https://invoket.com/blog/read-an-inbound-e-invoice-before-an-agent-pays-it --- From **1 September 2026**, an agent working a French accounts-payable inbox stops receiving PDFs a human reads and starts receiving **structured documents** — Factur-X, CII or UBL. Before it can decide anything about one of them, it needs the document as data: who issued it, against which VAT number, to which IBAN, for how much, due when. That is one paid call — [`POST /invoice/read`](/docs/api/invoice-read) — which projects the document into the EN 16931 semantic model **exactly as the document states it**, with no conformance judgement and no OCR reconstruction. ## Why the September 2026 inbox is different The French e-invoicing reform makes reception the universal obligation before it makes issuance one. From **1 September 2026**, every VAT-registered business established in France must use an approved platform to **receive** its electronic invoices — micro-enterprises included — while large and intermediate-sized enterprises must also **issue** them; small and medium enterprises follow on **1 September 2027** ([impots.gouv.fr](https://www.impots.gouv.fr/facturation-electronique-et-plateformes-agreees)). Reception is the asymmetric half. A company controls what it issues: one format, one template, one tool. It controls nothing about what arrives. The inbox will carry all three syntaxes of the minimum base — hybrid Factur-X PDF/A-3, bare CII, bare UBL — written by every ERP in Europe, at whatever version of the standard the sender happens to be on. The current external specifications are **version 3.2, dated 30 April 2026**, and they lean on three AFNOR standards (XP Z12-012/013/014) for the format and profile detail ([impots.gouv.fr](https://www.impots.gouv.fr/specifications-externes-b2b)). ### The format itself is a moving target Anyone tempted to hardcode a parser should look at the release cadence of the Franco-German standard alone. **ZUGFeRD 2.5 / Factur-X 1.09** was published on **10 June 2026**, to be used from 1 July 2026; it moved to UN/CEFACT **CII D22B** (backward compatible with D16B) and began pulling in elements of the revised EN 16931-1:2026 ([FeRD](https://www.ferd-net.de/publikationen-produkte/publikationen/detailseite/zugferd-25-deutsch)). Six weeks later, the corrigendum **ZUGFeRD 2.5.2** landed on **4 August 2026**, valid from 1 September 2026, cleaning up schema and rule inconsistencies across the EXTENDED, BASIC WL, BASIC and EN 16931 profiles ([FeRD](https://www.ferd-net.de/publikationen-produkte/publikationen/detailseite/zugferd-252-deutsch)). Two releases in under two months, the second one landing the same day the mandate starts. Reading inbound invoices is not a problem you solve once. ## Read answers one question: what does this document say? `/invoice/read` is deliberately narrow. It answers "**what does this invoice say?**" and nothing else: - The payment IBAN comes back **as written**, not repaired, not checked. - Totals are **not recomputed** — a document whose lines do not add up returns the totals it states. - The declared Factur-X profile (BT-24) is **relayed, not verified**. - Monetary amounts are **exact decimal strings at the source scale**: `"19.90"` stays `"19.90"`, never a float, never re-rounded. That is the point rather than a limitation. A faithful projection is the raw material every downstream check needs; an extractor that quietly "fixes" an inconsistency destroys the evidence that something was wrong. The normative verdict — EN 16931 business rules by `BR-xx` id, arithmetic consistency, identifier plausibility — is the job of [`POST /invoice/validate`](/docs/api/invoice-validate). The pair is designed as a division of labour: **read extracts without judging, validate judges without extracting.** There is also no OCR. A scanned PDF with no embedded XML is refused with a typed 400, never reconstructed from pixels. An invoice an agent is about to pay is the wrong place for a confidence score. ## The call `POST` the document as the body, in any of three equivalent forms: | Content-Type | Body | |---|---| | `application/pdf` | Raw bytes of a Factur-X/ZUGFeRD PDF | | `application/xml` | Raw bytes of a CII D16B or UBL 2.1 Invoice/CreditNote | | `application/json` | `{ "document_base64": "..." }` | The content type only distinguishes the JSON form. The actual format is detected from **magic bytes** (`%PDF` or the XML root element), so a PDF posted as `application/xml` is still read as a PDF — a useful property when the document arrives from a platform whose headers you do not control. ```http POST /invoice/read Content-Type: application/pdf %PDF-1.7 ... (raw Factur-X bytes) ``` ## A real response: the credit note that corrects an invoice Here is the actual service answer for the official FNFE `Avoir_FR_type381_BASIC.pdf` sample — a Factur-X BASIC **credit note** — abridged to the fields an agent acts on. ```json { "data": { "format": { "kind": "facturx", "declared_profile": "basic" }, "invoice": { "number": "AV-2017-0005", "issue_date": "2017-11-16", "type_code": "381", "currency": "EUR", "due_date": "2017-11-16", "specification_id": "urn:cen.eu:en16931:2017:compliant:factur-x.eu:1p0:basic", "notes": [ { "subject_code": null, "content": "Avoir suite à bidon 10L d'huile d'olive percé et carton de nougat renversé" } ], "preceding_invoices": [ { "number": "FA-2017-0010", "issue_date": "2017-11-13" } ], "seller": { "name": "Au bon moulin", "legal_registration_id": { "value": "99999999800010", "scheme": "0002" }, "vat_id": "FR11999999998", "address": { "line1": "1242 chemin de l'olive", "city": "Malaucène", "postal_code": "84340", "country": "FR" } }, "buyer": { "name": "Ma jolie boutique", "legal_registration_id": { "value": "78787878400035", "scheme": "0002" }, "vat_id": "FR19787878784" }, "payment_means": [], "totals": { "lines_net": "218.48", "net": "218.48", "vat_total": "14.99", "grand_total": "233.47", "prepaid": "0.00", "due": "233.47" }, "vat_breakdown": [ { "taxable_amount": "20.48", "tax_amount": "4.10", "category": "S", "rate": "20.00" }, { "taxable_amount": "198.00", "tax_amount": "10.89", "category": "S", "rate": "5.50" } ], "lines": [ { "id": "1", "quantity": "5.000", "unit": "C62", "unit_price": "4.10", "net_amount": "20.48", "item_name": "Nougat de l'Abbaye 250g", "vat_category": "S", "vat_rate": "20.00", "standard_item_id": { "value": "3518370400049", "scheme": "0160" } }, { "id": "2", "quantity": "10.000", "unit": "LTR", "unit_price": "19.80", "net_amount": "198.00", "item_name": "Huile d'olive à l'ancienne", "vat_category": "S", "vat_rate": "5.50" } ] }, "coverage": { "absent_fields": ["vat_accounting_currency", "buyer_reference", "sales_order_reference", "payment_terms", "business_process", "payment_means", "allowances", "charges"], "notes": [] } }, "provenance": { "source": "invoice-document", "fetched_at": "2026-08-10T17:41:33Z", "freshness": { "kind": "live" } } } ``` Four facts an agent can route on immediately, none of which require reading the PDF's visual layer: - **`type_code: "381"`** — this is a credit note, not an invoice. It reduces a payable; it is not something to pay. - **`preceding_invoices`** names the document it corrects, `FA-2017-0010` of 13 November 2017 — the reconciliation key, structured, not a free-text "re:" line. - **`payment_means: []`** and `"payment_means"` in `absent_fields`: the document carries **no IBAN**, and says so explicitly. An agent never has to infer whether an empty array means "no payment means" or "we failed to read them". - **`provenance.source: "invoice-document"`** with `freshness.kind: "live"` — this data came from the bytes you posted, not from a reference dataset. No attribution is owed, and nothing here can go stale. ## `coverage` is the field that stops an agent guessing Every extractor returns `null` for missing data. Very few tell you whether the `null` means *the document is silent* or *the profile does not carry that concept*. `coverage.absent_fields` lists, by JSON name, the top-level fields the document **does not state** — so silence is always explicit. The showcase is the Factur-X **MINIMUM** and **BASIC WL** profiles, which carry no invoice lines by design. The response returns `lines: []` *and* names `"lines"` in `absent_fields`. Without that second signal, an agent reading a BASIC WL invoice would conclude the supplier billed nothing. `coverage.notes` covers reading particularities rather than missing data — the embedded XML was found under a non-standard attachment name, or the PDF carried several attachments and this is the one that was read. Both are situations where a silent choice would be the dangerous behaviour. ## `declared_profile` does not invent a CIUS Profile handling shows the same discipline. Post a Peppol BIS Billing 3.0 UBL invoice and the answer is: ```json { "kind": "ubl", "declared_profile": null } ``` with the full URN preserved on the semantic model: ```json "specification_id": "urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0" ``` `declared_profile` is `null` because Peppol BIS is a CIUS, not a Factur-X profile — the service will not coin a name for a family it does not enumerate. The raw `specification_id` is always there, verbatim, for an agent that wants to route on the exact customisation. An XRechnung CII document, by contrast, comes back as `{ "kind": "cii", "declared_profile": "en16931" }` with `specification_id: "urn:cen.eu:en16931:2017"` — what the document declares, no more. This is also why the format churn described above does not break the endpoint: the profile is read as the last segment of whatever URN the document carries, and relayed without verification. A newer Factur-X release passes through the reader; deciding whether the document *lives up to* the profile it claims is [`/invoice/validate`](/docs/api/invoice-validate)'s job, and that side is pinned to a stated artefact set. ## An inbound invoice is untrusted input Everything in this inbox was written by someone else. The runtime is stateless pure computation — no network call, no secret, no store — and hostile documents come back as typed, **unbilled** 4xx rather than as a parser incident. Actual responses: | Input | Answer | |---|---| | PNG bytes (a scan) | `400 unreadable_body` — "document is neither a PDF nor an XML invoice (expected `%PDF-` or an XML root element)" | | PDF with no XML attachment | `400 no_embedded_xml` — "a Factur-X/ZUGFeRD document must embed its CII XML as an attachment" | | XML with an external entity | `400 malformed_xml` — "unsupported XML entity reference: `&xxe;`" | | A zip-bomb attachment | `413 PAYLOAD_TOO_LARGE` | The 10 MB body cap is enforced on the announced `Content-Length`, on the bytes actually read, and — this is the one that matters — on the **decompression of the embedded XML attachment**, so a small PDF cannot expand into a large one inside the reader. A query parameter is rejected too: the endpoint takes none, so `?x=` is treated as a typo rather than silently ignored. ## The x402 golden rule, applied to a bad invoice The agent pays for the *answer to its question*, and the question is "what does this document say?". So a readable document is a **200, billed, even when the invoice is inconsistent or non-conformant** — an inconsistency faithfully relayed is precisely what the downstream check needs to see. Only requests the service cannot answer leave the 200 range, and those are not billed: a malformed body, an unreadable or scanned document, a body over the cap. The authoritative price and accepted rails are served live by the [catalog](https://api.invoket.com/catalog) — never hardcoded in an article. The payment loop is the usual discover → `402` → pay → replay cycle; the [Quickstart](/docs/quickstart) walks it with runnable snippets and [For agents](/docs/for-agents) covers the discovery surfaces. ## The inbound chain read sits at the head of Extraction is step one, and its value is that every later step becomes a lookup with a real key instead of a regex over a PDF. 1. **Read** the document: `POST /invoice/read`. You now hold `seller.vat_id`, `seller.legal_registration_id`, the IBAN in `payment_means`, `due_date`, and the totals. 2. **Judge the document** with [`POST /invoice/validate`](/docs/api/invoice-validate) if conformance matters — the EN 16931 verdict with official `BR-xx` rule ids. 3. **Check the payee before paying.** The IBAN was relayed as written, so resolve it with [`GET /iban/resolve`](/docs/api/iban-resolve) — bank, BIC, SEPA reachability — and screen it with [`GET /iban/screen`](/docs/api/iban-screen), which covers **bank and jurisdiction sanctions risk, not the account holder's name**. 4. **Check the supplier exists and is who it says.** `seller.vat_id` goes to [`GET /company/vat`](/docs/api/company-vat) for the live VIES check; `legal_registration_id` (scheme `0002` is SIRET) goes to [`GET /company/resolve`](/docs/api/company-resolve). 5. **Compute what is actually owed on time.** `due_date` and the totals feed [`POST /invoice/late-payment`](/docs/api/invoice-late-payment) for statutory interest and the recovery indemnity. 6. **Answer in kind.** When your agent issues the credit note or the invoice back, [`POST /invoice/generate`](/docs/api/invoice-generate) closes the cycle — read → validate → generate. Each step is its own settled call, so an agent pays only for the checks a particular document actually warrants. A €40 credit note from a known supplier stops at step 1; a first invoice from a new counterparty with an unfamiliar IBAN runs the chain. ## What read will not do - **No OCR, ever.** A scanned or image-only PDF is refused, not reconstructed. - **No conformance verdict, no repair.** That is [`/invoice/validate`](/docs/api/invoice-validate) — see also [pre-flighting a supplier](/blog/pre-flight-a-supplier-before-an-agent-onboards-it) for the counterparty side of the same decision. - **No transmission.** The endpoint reads a document you already hold; it is not an approved platform and it does not send, receive or archive anything on your behalf. - **No signature or seal verification**, and no PDF/A-3 conformance check on the carrier. - **No VAT determination.** Whether the mechanism on the document is the right one is [`/invoice/vat-treatment`](/docs/api/invoice-vat-treatment)'s question, not a reading question. Used for what it is — one settled call that turns an arbitrary European e-invoice into fields an agent can branch on — read is the cheapest step in the inbound chain and the one every other step depends on. For the full request contract, the field tables and the error codes, see the [`POST /invoice/read` documentation](/docs/api/invoice-read).