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-TypeBody
application/pdfThe raw bytes of a Factur-X/ZUGFeRD PDF
application/xml or text/xmlThe 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

FieldTypeDescription
formatobjectDetected format and declared profile
invoiceobjectThe EN 16931 semantic model, projected as the document states it
coverageobjectWhat the document does not say, and any reading particularities

format

FieldTypeDescription
kindstringDetected format: facturx, cii or ubl
declared_profilestring | nullThe 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

FieldTypeDescription
absent_fieldsstring[]Top-level fields of invoice the document does not state (their JSON names) - never silent
notesstring[]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.

StatuscodeCase
400INVALID_INPUTJSON 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)
400invalid_base64document_base64 is not valid standard base64 (after whitespace tolerance)
400unreadable_bodyNeither PDF nor XML magic bytes - e.g. a scanned image: OCR is out of scope
400malformed_xmlTruncated or malformed XML, including the embedded XML of a PDF
400unknown_rootWell-formed XML whose root is neither CII (CrossIndustryInvoice) nor UBL (Invoice/CreditNote)
400malformed_pdfPDF the reader cannot parse
400no_embedded_xmlPDF without an invoice XML attachment (a plain PDF, or Factur-X claimed wrongly)
400invalid_decimalAn amount in the document is not a readable decimal
413PAYLOAD_TOO_LARGEBody over the 10 MB cap (announced or read), or an embedded attachment whose decompression exceeds the same bound
404NOT_FOUNDUnknown route
500INTERNALInternal 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 /catalog and 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.