POST /invoice/read
Extracts a European electronic invoice into the EN 16931 semantic model: parties with their legal registration and VAT identifiers, invoice lines, VAT breakdown, document totals, payment means with IBAN, dates and references. It reads the three formats of the structured e-invoicing core - Factur-X/ZUGFeRD (PDF/A-3 with embedded XML), CII D16B and UBL 2.1 (Invoice and CreditNote) - exactly the formats mandated by the French e-invoicing reform (all businesses must be able to receive them from 2026-09-01; small and medium businesses must issue them from 2027-09-01).
The extraction is faithful, with no conformance judgement: the service
returns what the document actually says - the IBAN as written, totals as
stated without recomputation, the declared Factur-X profile (BT-24) without
verification. And it is structured-only, no OCR guesswork: a scanned PDF
without embedded XML is rejected with a typed 400, never reconstructed. The
runtime is stateless pure computation - no network call, no secret, no
store - so a readable document resolves in milliseconds. See the live
/catalog for the authoritative endpoint listing and
price.
x402 golden rule: the agent pays for the answer to its question. A readable document is a successful answer -> 200 billed, even when the invoice is inconsistent or non-conformant - fidelity is the product, and an inconsistency faithfully relayed is exactly what a downstream check needs to see. Requests the service cannot answer - a malformed body, an unreadable or scanned document, a body over the size cap - leave the 200 range and are not billed.
Request
POST with the invoice document as the body, in any of three equivalent
input forms:
| Content-Type | Body |
|---|---|
application/pdf | The raw bytes of a Factur-X/ZUGFeRD PDF |
application/xml or text/xml | The raw bytes of a CII D16B or UBL 2.1 (Invoice/CreditNote) XML |
application/json | { "document_base64": "..." } - standard base64 of the whole document, whitespace tolerated (wrapped base64 accepted) |
The content type only distinguishes the JSON form; the format of the document
itself is detected from its magic bytes (%PDF or the XML root element),
so a PDF posted as application/xml is still recognized as a PDF.
POST /invoice/read
Content-Type: application/pdf
%PDF-1.7 ... (raw Factur-X bytes)
Body cap: 10 MB. The cap is enforced on the announced Content-Length
and on the actual bytes read (chunked bodies included), and the same bound
applies to the decompression of the embedded XML attachment of a Factur-X
PDF. A body over the cap returns a contractual JSON 413 naming the cap; it
is not billed.
200 response - UnifiedResponse
{
"data": {
"format": { ... },
"invoice": { ... },
"coverage": { ... }
},
"provenance": {
"source": "invoice-document",
"fetched_at": "2026-07-11T17:36:16Z",
"freshness": { "kind": "live" }
}
}
provenance.source:invoice-document- the data comes from the document you posted, not from a reference dataset.freshness.kind:live- pure on-demand computation.
Fields of data
| Field | Type | Description |
|---|---|---|
format | object | Detected format and declared profile |
invoice | object | The EN 16931 semantic model, projected as the document states it |
coverage | object | What the document does not say, and any reading particularities |
format
| Field | Type | Description |
|---|---|---|
kind | string | Detected format: facturx, cii or ubl |
declared_profile | string | null | The profile the document declares in BT-24 - the last segment of a Factur-X/ZUGFeRD URN (minimum, basicwl, basic, extended) or en16931 for the bare EN 16931 URN; null when absent or from an unknown family (CIUS such as XRechnung are not invented). Relayed without verification |
invoice - the semantic model
The invoice projected into EN 16931 terms (BT/BG): number, issue_date,
type_code, currency, due_date, references (buyer_reference,
contract_reference, order_reference, …), notes, preceding_invoices,
the seller and buyer parties (name, legal registration id with scheme,
VAT id, address), payment_means (means code, remittance info, credit
accounts with IBAN and BIC), allowances, charges, totals,
vat_breakdown and lines.
Two fidelity rules apply everywhere:
- A field the document does not state is
null(or an empty array) - never guessed, never filled in. - Monetary amounts are exact decimal strings at the source scale:
"19.90"stays"19.90", never a float, never re-rounded.
coverage
| Field | Type | Description |
|---|---|---|
absent_fields | string[] | Top-level fields of invoice the document does not state (their JSON names) - never silent |
notes | string[] | Reading particularities: embedded XML found under a non-standard attachment name, multiple attachments and which one was read, … |
Showcase case: the Factur-X MINIMUM and BASIC WL profiles carry no
invoice lines by design - the response returns lines: [] and lists
"lines" in absent_fields, so an agent never mistakes “the document does
not say” for “there are no lines worth reading”.
Example - official FNFE BASIC WL sample
Actual service response for the official FNFE Facture_FR_BASICWL.pdf
example, posted as application/pdf:
{
"data": {
"format": { "kind": "facturx", "declared_profile": "basicwl" },
"invoice": {
"number": "FA-2017-0010",
"issue_date": "2017-11-13",
"type_code": "380",
"currency": "EUR",
"vat_accounting_currency": null,
"due_date": "2017-12-13",
"buyer_reference": null,
"contract_reference": "MSPE2017",
"order_reference": "PO445",
"sales_order_reference": null,
"payment_terms": null,
"business_process": null,
"specification_id": "urn:factur-x.eu:1p0:basicwl",
"notes": [
{ "subject_code": null, "content": "Franco de port (commande > 300 € HT)" }
],
"preceding_invoices": [],
"seller": {
"name": "Au bon moulin",
"trading_name": null,
"identifiers": [],
"legal_registration_id": { "value": "99999999800010", "scheme": "0002" },
"vat_id": "FR11999999998",
"tax_registration_id": null,
"legal_form": null,
"electronic_address": null,
"address": {
"line1": "1242 chemin de l'olive",
"line2": null,
"line3": null,
"city": "Malaucène",
"postal_code": "84340",
"country_subdivision": null,
"country": "FR"
}
},
"buyer": {
"name": "Ma jolie boutique",
"trading_name": null,
"identifiers": [],
"legal_registration_id": { "value": "78787878400035", "scheme": "0002" },
"vat_id": "FR19787878784",
"tax_registration_id": null,
"legal_form": null,
"electronic_address": null,
"address": {
"line1": "35 rue de la République",
"line2": null,
"line3": null,
"city": "Lyon",
"postal_code": "69001",
"country_subdivision": null,
"country": "FR"
}
},
"payment_means": [
{
"means_code": "30",
"means_text": null,
"remittance_info": "FA-2017-0010",
"credit_accounts": [
{ "iban_or_account": "FR2012421242124212421242124", "account_name": null, "bic": null }
],
"debited_account": null
}
],
"allowances": [],
"charges": [],
"totals": {
"lines_net": "624.90",
"allowances_total": null,
"charges_total": null,
"net": "624.90",
"vat_total": "46.25",
"grand_total": "671.15",
"prepaid": "201.00",
"rounding": null,
"due": "470.15"
},
"vat_breakdown": [
{
"taxable_amount": "81.90",
"tax_amount": "16.38",
"category": "S",
"rate": "20.00",
"exemption_reason": null,
"exemption_reason_code": null
},
{
"taxable_amount": "543.00",
"tax_amount": "29.87",
"category": "S",
"rate": "5.50",
"exemption_reason": null,
"exemption_reason_code": null
}
],
"lines": []
},
"coverage": {
"absent_fields": [
"vat_accounting_currency",
"buyer_reference",
"sales_order_reference",
"payment_terms",
"business_process",
"preceding_invoices",
"allowances",
"charges",
"lines"
],
"notes": []
}
},
"provenance": {
"source": "invoice-document",
"fetched_at": "2026-07-11T17:36:16Z",
"freshness": { "kind": "live" }
}
}
Note the BASIC WL showcase in action: lines is empty and declared absent,
and every field the document does not state is named in
coverage.absent_fields.
Read is not validate
This endpoint answers “what does this invoice say?” - and only that. It relays the document faithfully, inconsistencies included: the payment IBAN is returned exactly as written, the totals are not recomputed, the declared profile is not checked against the document’s content. That is deliberate: a faithful projection is the raw material for any downstream check, and hiding or “fixing” an inconsistency would destroy the evidence.
The normative verdict - EN 16931 business rules (BR-xx), arithmetic
consistency, plausibility of the identifiers - is a separate job:
/invoice/validate. The two are designed as a
pair: read extracts faithfully without judging, validate judges without
extracting. And when your agent issues its own invoices,
/invoice/generate closes the cycle -
read → validate → generate: receive and read it, decide to accept it,
issue your own.
Errors
Only requests the service cannot answer leave the 200 range. Typed 4xx responses are not billed, and a twisted document is never a 5xx.
| Status | code | Case |
|---|---|---|
| 400 | INVALID_INPUT | JSON body is not a { "document_base64": ... } object (a JSON array or scalar is refused, naming the expected form - never a misleading invalid_base64), the body could not be read, or a query parameter is present (the endpoint takes none - a ?x= is a rejected typo, never silently ignored) |
| 400 | invalid_base64 | document_base64 is not valid standard base64 (after whitespace tolerance) |
| 400 | unreadable_body | Neither PDF nor XML magic bytes - e.g. a scanned image: OCR is out of scope |
| 400 | malformed_xml | Truncated or malformed XML, including the embedded XML of a PDF |
| 400 | unknown_root | Well-formed XML whose root is neither CII (CrossIndustryInvoice) nor UBL (Invoice/CreditNote) |
| 400 | malformed_pdf | PDF the reader cannot parse |
| 400 | no_embedded_xml | PDF without an invoice XML attachment (a plain PDF, or Factur-X claimed wrongly) |
| 400 | invalid_decimal | An amount in the document is not a readable decimal |
| 413 | PAYLOAD_TOO_LARGE | Body over the 10 MB cap (announced or read), or an embedded attachment whose decompression exceeds the same bound |
| 404 | NOT_FOUND | Unknown route |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
{ "error": "body is neither a PDF nor an XML document; scanned PDFs without embedded XML are out of scope (no OCR)", "code": "unreadable_body" }
Attribution
None required: the data in the response comes from the document you posted, not from a third-party dataset.
See also
POST /invoice/validate- the normative EN 16931 verdict on the same document: official BR-xx rule ids, observed/expected values, layer by layer.POST /invoice/generate- issue your own invoice: business JSON to CII XML, guaranteed conformant by the same rule set.- For agents - discovery surfaces, the live
/catalogand how settlement works. - Quickstart - the 402 -> pay -> replay loop with runnable snippets.
GET /iban/resolve- resolve and check the payment IBAN an invoice carries before paying it.