POST /invoice/generate

Turns a simple business JSON - parties, lines, VAT - into an XML guaranteed conformant to EN 16931, in the syntax you ask for:

  • CII D16B (default, syntax: "cii"), at the Factur-X EN 16931 profile (URN urn:cen.eu:en16931:2017 emitted in BT-24) - the legal core of the French e-invoicing reform: large businesses must issue electronic invoices from 2026-09-01, small and medium businesses from 2027-09-01;
  • UBL 2.1 Invoice (syntax: "ubl", type 380), the syntax of the Peppol network; with ruleset: "peppol" the output is guaranteed conformant to Peppol BIS Billing 3.0 - the live Belgian mandate (mandatory issuance, penalties since 2026-04).

The default body (no syntax) stays byte-for-byte backward compatible with v1 CII generation: existing agents change nothing. Your agent writes the invoice; the official rule set signs off. This is the only x402 generator that both guarantees and re-proves Peppol BIS 3.0.

The “generated-then-validated” guarantee is the point. Before being served, every 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, identifier plausibility - in strict mode, on the same pinned CEN artefacts. An output that would fail its own validation is a 500: never served, never billed. A 200 therefore carries data.validated: true by construction - the same official yardstick that /invoice/validate sells as a verdict.

This closes the invoice cycle: read → validate → generate → generate-pdf. Receive an invoice and read it, decide whether to accept it, issue your own XML core with this endpoint, or issue the complete Factur-X PDF with /invoice/generate-pdf - the round trip is proven: the generated XML posted back to /invoice/validate?profile=strict returns verdict: "valid".

The runtime is stateless pure computation - no network call, no secret, no store - and a nominal invoice generates in tens of milliseconds. 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 conformant invoice” - so the only billed case is a 200 with a generated, self-validated XML. 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 only endpoint of the Invoice family with a pure JSON input (no binary document). Body cap: 10 MB (a contractual JSON 413 above it; not billed).

Unknown fields are rejected (deny_unknown_fields): a typo is a 400 INVALID_INPUT naming the offending field and listing the expected fields - never a field silently ignored.

Input schema (business JSON)

{
  "syntax": "cii",                             // "cii" (default) | "ubl" - see "Syntax" below
  "ruleset": "peppol",                         // optional, "peppol" only - requires syntax:"ubl", see "Peppol" below
  "invoice_number": "FA-2026-0042",            // BT-1, required (numbering is yours - see limits)
  "issue_date": "2026-07-12",                  // BT-2, required, YYYY-MM-DD
  "due_date": "2026-08-11",                    // BT-9, optional
  "type_code": "380",                          // BT-3, optional (default 380 invoice; 381 credit note)
  "currency": "EUR",                           // BT-5, required, ISO 4217
  "buyer_reference": "BE-REF-0042",            // BT-10, optional (required by Peppol R003)
  "seller": {                                  // BG-4, required
    "name": "…",                               // BT-27, required
    "legal_id": { "value": "732829320", "scheme": "0002" },  // BT-30, optional (ISO 6523 scheme)
    "vat_id": "FR44732829320",                 // BT-31, optional
    "electronic_address": { "value": "0417497106", "scheme": "0208" },  // BT-34, required by Peppol R010 (bare id, no EAS prefix)
    "address": {                               // BG-5, required
      "line1": "…", "line2": "…", "city": "…", "postal_code": "…",
      "country": "FR"                          // BT-40, required, ISO 3166-1
    }
  },
  "buyer": {  },                              // BG-7, same shape (BT-44/BT-47/BT-48, BG-8, electronic_address BT-49)
  "lines": [                                   // BG-25, required, non-empty
    {
      "description": "Handling gloves",        // BT-153 (item name), required
      "qty": "10",                             // BT-129, required (number, or plain-decimal string)
      "unit": "C62",                           // BT-130, UN/ECE Rec 20
      "unit_price": "12.50",                   // BT-146, required
      "vat": {                                 // required
        "category": "S",                       // BT-151 (UNTDID 5305: S, Z, E, AE…)
        "rate": "20",                          // BT-152 (%)
        "exemption_reason": "…"                // BT-120 (categories E/AE…), optional
      }
    }
  ],
  "payment": { "iban": "FR76…", "terms": "30 days net" },  // BT-84 (+ BG-16 code 30), BT-20
  "vat_treatment": {  },                      // optional - the VAT determination writes the compulsory mention, see below
  "totals": {                                  // BG-22, optional - verified, never repaired
    "lines_net": "300.00", "net": "295.00", "vat_total": "59.00",
    "grand_total": "354.00", "prepaid": "0.00", "rounding": "0.00", "due": "354.00"
  }
}

Decimal fields - plain decimals only

The decimal fields (qty, unit_price, vat.rate, totals.*) accept either a JSON number or a string in plain-decimal form -?digits(.digits)? (e.g. "1000", "-2", "12.50"). Scientific notation ("1e3") is refused with an invalid_decimal issue naming the field: an agent that sends it has most likely made a scale error, and the value is never silently reinterpreted. Quantities of 0 and negatives stay accepted (discounts, corrections).

Transparent completions - never inventive

The service completes what is mechanical, echoes it back in data.computed, and invents nothing:

  • line identifiers BT-126 are numbered 1..n;
  • the line net amount BT-131 is computed as qty × unit_price (official EN 16931 rounding - the same rounding the rule engine uses);
  • an IBAN in payment becomes a credit-transfer payment instruction BG-16 (UNTDID 4461 code 30);
  • line-level exemption reasons are carried onto the matching VAT group (category, rate) of the BG-23 breakdown - two diverging reasons in the same group are a typed conflicting_exemption_reasons error, never a silent arbitration.

Totals policy - computed if absent, verified if provided, never repaired

  • totals absent (the recommended nominal path): the BG-22 document totals and the BG-23 VAT breakdown are computed from the lines, with the official BR-CO rounding rules.
  • totals provided: it is verified against that same computation. Any divergence is a 400 with an inconsistent_totals issue naming the BT term and the computed value. The service never silently “repairs” a total: an amount that ends up in the XML is either yours (verified) or computed and echoed back in data.computed.

Syntax - syntax: "cii" | "ubl"

The same business model produces either CII D16B (default) or UBL 2.1 Invoice - one arithmetic core, two serializers. The choice changes neither the price nor the generated-then-validated guarantee: the UBL output is re-parsed and re-passed through the four layers in strict mode, exactly like the CII output.

  • Default cii (or syntax absent): the v1 behaviour, byte-for-byte unchanged.
  • ubl: a UBL 2.1 Invoice, type 380. Two shape differences in the response (see below): data.syntax is emitted ("ubl"), and data.profile is null - the Factur-X profile (MINIMUM…EXTENDED) is a CII notion. In UBL the EN 16931 identifier lives in the XML’s CustomizationID, never a urn:factur-x.eu:… URN.
  • A value other than cii/ubl is a 400 INVALID_INVOICE_INPUT with an invalid_syntax issue - accumulated with the others in one pass, never silently reinterpreted.

Peppol BIS 3.0 - ruleset: "peppol" (UBL only)

ruleset: "peppol" makes the output an invoice guaranteed conformant to Peppol BIS Billing 3.0. The PEPPOL-EN16931-Rxxx rule pack - faithful to OpenPeppol, with sourced ids, the same engine /invoice/validate?ruleset=peppol uses - is replayed at re-validation, on top of the EN 16931 core. The target is the Belgian mandate (mandatory issuance, penalties since 2026-04) and any agent emitting onto the Peppol network, where only UBL 2.1 is admitted.

  • ruleset:"peppol" requires syntax:"ubl". Peppol × CII is an inconsistent input (Peppol mandates UBL) - a 400 ruleset_requires_ubl, not a billed verdict. (Contrast the validation side, where a CII sent with ?ruleset=peppol is a billed 200 with an invalid verdict: there you ask a valid question about an existing document; here you fabricate, and an input that contradicts itself is a bad request.)
  • BIS headers set for you. The service writes the CustomizationID (urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0) and the ProfileID (urn:fdc:peppol.eu:2017:poacc:billing:01:1.0) - the agent does not supply them.
  • Electronic addresses required. Peppol requires the seller’s electronic address (BT-34, R010) and the buyer’s (BT-49, R020). Each is declared per party as electronic_address: { value, scheme }, where scheme is an official EAS code (e.g. 0208 for the Belgian enterprise number, 0009 for the SIRET). value is the bare identifier: the EAS code lives in scheme and must not be repeated inside value (write "0417497106", not "0208:0417497106" - otherwise the output carries the code twice). It is serialized as <cbc:EndpointID schemeID="0208">0417497106</cbc:EndpointID>. Peppol rules check the shape of the identifier for the declared scheme (0208 → a Belgian enterprise number with a valid mod-97 check, PEPPOL-COMMON-R043), so an arbitrary number is a sourced 400, not billed. A missing address is a 400 with the sourced Peppol id (R010/R020); a scheme outside the list is a 400 invalid_eas_scheme. buyer_reference (BT-10) is required by R003. Both shape checks hold without ruleset: "peppol", in CII as well as UBL. The EAS code repeated inside value, and the format of the declared scheme when Peppol imposes a fatal-severity one (0088, 0192, 0184, 0208, 0007, 0151), are checked by the plausibility layer on every call: a malformed address is a 400 implausible_identifier whose path names the failing check (seller_electronic_address / buyer_electronic_address), accumulated with the other issues in the same turn. No electronic address that a Peppol access point could not route comes back validated: true any more - on /invoice/generate and on /invoice/generate-pdf alike.
  • Fatal vs warning. Missed fatal Peppol rules (R00x, R010, R020…) surface alongside the EN 16931 violations in issues[] (a sourced 400, all together, one turn). Passed warning-severity rules do not block: they are reported in data.warnings[] (omitted when empty) - the invoice is served (200, validated: true), and the agent sees the non-binding recommendations.
  • Default (no ruleset). EN 16931 core only, and the response is byte-for-byte backward compatible (no ruleset field serialized at all).

VAT determination - vat_treatment (optional)

An invoice can satisfy every EN 16931 rule and still be refused by the tax authority: an intra-Community supply or a cross-border B2B service without its reverse-charge mention is irregular (Art. 226 of Directive 2006/112/EC), and no BR- rule says so. The optional vat_treatment field has the transaction judged by the engine of POST /invoice/vat-treatment - same fields, same sourced verdict - and writes the determined mention into the field the standard provides for it.

"vat_treatment": {
  "supplier_country": "FR",
  "customer_country": "IE",
  "transaction_type": "services",
  "customer_type": "business",
  "customer_vat_status": "valid",
  "supply_kind": "services_b2b_general",
  "date": "2026-07-12"
}
  • Where the mention is written: in BT-120 (VAT exemption reason) of the BG-23 breakdown groups whose category can carry it - E, AE, K, G, O, the ones where no VAT is charged. Never on a taxed group (S, Z), where BT-120 has no meaning. In CII as in UBL, and all the way into the Factur-X of /invoice/generate-pdf (embedded XML and rendered page).
  • Which wording: all the mentions the verdict requires, in one language - the supplier’s domestic wording when the engine knows one ("Autoliquidation", "Exonération de TVA, article 262 ter I du CGI" for France), the Directive’s English formula otherwise. Never a mix, never one mention dropped in favour of another.
  • Nothing is overwritten: a vat.exemption_reason supplied in the input is kept exactly as written, and vat_treatment.notes[] says so.
  • Consistency is required: supplier_country and customer_country must be the countries the invoice itself declares (BT-40 / BT-55). Otherwise it is a 400 vat_treatment_mismatch naming the field, not billed - a verdict rendered on other countries judges another transaction, and the mention it produced would be false.
  • Honest refusal: an uncovered regime returns mechanism: "undetermined" with its rationale; the invoice is still generated (200), no mention is invented, and notes[] says why.
  • Absent = strictly unchanged behaviour: no new field in the response, and an XML byte-identical to what it was before the field existed.

On a 200, data.vat_treatment carries the verdict flat, exactly as /invoice/vat-treatment serves it - rule_id (INV-VAT-*, never a normative BR-*), mechanism, liable, place_of_supply, rate, required_invoice_mentions, legal_basis, caveats - plus three fields specific to the generation:

FieldDescription
invoice_mentionThe text actually written into BT-120 (null when none applies)
mention_applied_toThe VAT categories that received it - [] when nothing was written
notesWhy nothing was written, or what was kept as it was

Real response (an FR -> IE service invoice, category AE, abridged):

{
  "data": {
    "validated": true,
    "computed": {
      "vat_breakdown": [
        { "taxable_amount": "3000", "tax_amount": "0", "category": "AE", "rate": "0", "exemption_reason": "Autoliquidation" }
      ]
    },
    "vat_treatment": {
      "rule_id": "INV-VAT-S02",
      "rule_summary": "Cross-border B2B service inside the Union with a proven VAT number: the customer reverse-charges (Art. 44, 196)",
      "liable": "customer",
      "mechanism": "reverse_charge",
      "place_of_supply": { "country": "IE", "basis": { "celex": "32006L0112", "article": "44", "title": "Place of supply of services to a taxable person: where the customer is established", "query": "/legal/eu-act?celex=32006L0112&article=44" } },
      "invoice_mention": "Autoliquidation",
      "mention_applied_to": ["AE"]
    }
  }
}

The mention is in the XML itself (<ram:ExemptionReason>Autoliquidation</ram:ExemptionReason>), not merely reported next to it. And when the verdict determines nothing - here a service connected with immovable property, without property_country - the invoice is still generated and the response says exactly that:

{
  "rule_id": "INV-VAT-L01",
  "mechanism": "undetermined",
  "liable": "undetermined",
  "rationale": "A service connected with immovable property is taxed where the property is located (Art. 47), regardless of where either party is established. Supply property_country to obtain a determination.",
  "invoice_mention": null,
  "mention_applied_to": [],
  "notes": ["the determined treatment requires no specific invoice mention: nothing was written to BT-120"]
}

200 response - UnifiedResponse

{
  "data": {
    "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
xmlThe complete document - CII D16B or UBL 2.1 per syntax - ready to transmit
profileThe Factur-X URN emitted in BT-24 (always urn:cen.eu:en16931:2017 in v1) in CII; null in UBL - the Factur-X profile is a CII notion
validatedAlways true on a 200 - the echo of the generated-then-validated post-condition
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
syntaxEmitted in UBL only ("ubl"); absent in CII (backward-compatible)
rulesetEmitted in Peppol only ("peppol"); absent otherwise (backward-compatible)
warningsPassed Peppol warning-severity rules (non-binding); omitted when empty
vat_treatmentThe sourced VAT verdict and the mention actually written; 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 and validated against (EN 16931 validation artefacts v1.3.16 + Factur-X 1.08 + the OpenPeppol BIS 3.0 pack, en16931-1.3.16+facturx-1.08+xrechnung-3.0.2+peppol-bis-3.0.20) - there is no fetch and no clock behind the guarantee, it is pinned to a rule-set version.

Example - real nominal round trip

Actual service response (2026-07-12) for a French two-line invoice, standard VAT 20 %, without totals - so the totals below are computed:

Input:

{
  "invoice_number": "FA-2026-0042", "issue_date": "2026-07-12",
  "due_date": "2026-08-11", "currency": "EUR",
  "seller": { "name": "Menuiserie Dubois SARL",
    "legal_id": { "value": "732829320", "scheme": "0002" },
    "vat_id": "FR44732829320",
    "address": { "line1": "12 rue des Artisans", "city": "Lyon",
                 "postal_code": "69003", "country": "FR" } },
  "buyer": { "name": "Agence Lumen SAS", "vat_id": "FR23342663721",
    "address": { "line1": "4 avenue du Parc", "city": "Paris",
                 "postal_code": "75011", "country": "FR" } },
  "lines": [
    { "description": "Fabrication comptoir chene massif", "qty": "1",
      "unit": "C62", "unit_price": "1200.00",
      "vat": { "category": "S", "rate": "20" } },
    { "description": "Pose et finition sur site", "qty": "6",
      "unit": "HUR", "unit_price": "85.00",
      "vat": { "category": "S", "rate": "20" } }
  ],
  "payment": { "iban": "FR7630006000011234567890189", "terms": "30 jours nets" }
}

Response (200; the XML is abridged here - the full CII document is about 6 KB):

{
  "data": {
    "xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rsm:CrossIndustryInvoice …>…</rsm:CrossIndustryInvoice>",
    "profile": "urn:cen.eu:en16931:2017",
    "validated": true,
    "computed": {
      "totals": {
        "lines_net": "1710", "net": "1710", "vat_total": "342",
        "grand_total": "2052", "due": "2052"
      },
      "vat_breakdown": [
        { "taxable_amount": "1710", "tax_amount": "342", "category": "S", "rate": "20" }
      ]
    }
  },
  "provenance": {
    "source": "en16931-artefacts",
    "fetched_at": "2026-07-12T14:18:41Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-07-11T00:00:00Z" }
  }
}

The round trip is verified end to end: this data.xml, posted unchanged to /invoice/validate?profile=strict, returns verdict: "valid".

400 errors - actionable, reported together, not billed

Two top-level codes ({ error, code }):

  • INVALID_INPUT - the body is not the schema: invalid JSON, an unknown field / typo (deny_unknown_fields - the message names the offending field and lists the expected ones), or a query parameter (the endpoint takes none - a ?x= is rejected, never ignored).
  • INVALID_INVOICE_INPUT - the schema is fine but the invoice cannot be generated conformant: the body carries issues[] with all independent errors reported together, so the agent fixes everything in one turn - no round trip per error. Each issue carries a stable code, a message, and the normative vocabulary where available (term in BT/BG, rule_id BR-xx, path).

Stable issue codes:

codeMeaning
missing_fielda required term is absent (BT/BG named, path given)
invalid_datea date is not YYYY-MM-DD (BT-2, BT-9, …)
invalid_textempty or illegal text for the named field
inconsistent_totalsa provided total diverges from the BR-CO computation (computed value in the message)
rule_violationan EN 16931 business rule is violated - one issue per violation, with its official rule_id (BR-xx)
implausible_identifierIBAN (mod-97), SIREN/SIRET (Luhn), VAT number syntax or an electronic address (BT-34/BT-49: EAS code not repeated inside value, plus the declared scheme’s format when Peppol imposes a fatal-severity one) fails - one issue per failing check, path = the check name (seller_electronic_address…), whatever the ruleset; whether the identifier exists is /company/resolve, /company/vat and /company/peppol’s job
implausible_currencythe invoice currency BT-5 is an ISO 4217 code with no billing use - XXX (“no currency involved”) or XTS (reserved for test purposes) - path = currency_is_monetary. A dedicated code rather than a rule_violation, because no BR is violated: BR-CL-03/BR-CL-04 are right to accept every ISO 4217 code, and calling this a rule violation would name a rule that upstream never broke. The metals (XAU, XAG, XPD, XPT) and units of account (XDR, XUA) have real uses and pass. Applies to /invoice/generate-pdf too, and is accumulated with the other issues in one turn
unrepresentablea term the generated CII could not carry faithfully - refused, not dropped
unsupported_profilea requested profile other than EN 16931 (the only generation profile in v1)
conflicting_exemption_reasonsdiverging BT-120 exemption reasons within the same VAT group
amount_out_of_rangea computed amount (line net BT-131 = qty × unit_price, BG-22 totals, BG-23 breakdown) overflows the representable decimal domain (Decimal::MAX ≈ 7.9 × 10²⁸) - checked arithmetic, never a panic or a 5xx
invalid_decimala decimal-field string is outside the plain form -?digits(.digits)? - scientific notation first - for the field named in path
invalid_syntaxsyntax outside cii/ubl
invalid_rulesetruleset outside peppol
ruleset_requires_ublruleset:"peppol" without syntax:"ubl" (Peppol mandates UBL 2.1 - an inconsistent input, not a verdict)
invalid_eas_schemean EAS code (electronic_address.scheme) outside the official Peppol list
vat_treatment_mismatcha country of vat_treatment contradicts the one the invoice declares (BT-40/BT-55): the verdict would judge another transaction - path = vat_treatment.supplier_country / .customer_country
missing_field, invalid_date, invalid_country, date_out_of_range (paths vat_treatment.*)faults of the vat_treatment field itself - a required field absent, an impossible calendar date, a country code outside ISO 3166-1, a date before the VAT rate snapshot - accumulated with the invoice’s own issues, in one turn
rule_violation (Peppol)a fatal Peppol BIS 3.0 rule missed - rule_id PEPPOL-EN16931-Rxxx (e.g. R010/R020, seller/buyer electronic address BT-34/BT-49) - surfaced alongside the EN 16931 core violations

Real example (the input above with totals.grand_total mutated to 2152.00):

{ "code": "INVALID_INVOICE_INPUT",
  "error": "invoice input is invalid: 1 actionable issue(s) listed in issues[]",
  "issues": [ { "code": "inconsistent_totals", "term": "BT-112",
    "message": "provided total BT-112 = 2152.00 is inconsistent with the value computed from the invoice body (2052, EN 16931 BR-CO rules)" } ] }

Real Peppol example (a syntax:"ubl" + ruleset:"peppol" request missing the buyer’s electronic address BT-49) - the Peppol id is sourced, the request is not settled:

{ "code": "INVALID_INVOICE_INPUT",
  "error": "invoice input is invalid: 1 actionable issue(s) listed in issues[]",
  "issues": [ { "code": "rule_violation", "rule_id": "PEPPOL-EN16931-R010",
    "message": "[PEPPOL-EN16931-R010] Buyer electronic address (BT-49) MUST be provided.",
    "path": "/*/AccountingCustomerParty/Party/EndpointID" } ] }

Status matrix

Only a real answer is billed - the x402 golden rule.

StatuscodeCaseBilled
200-Invoice generated and passed its own validation (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 output failed its own validation - 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.

Limits of v1 - stated plainly

  • XML only on this endpoint (CII or UBL): this endpoint emits the XML - the legal core of the reform. For the complete Factur-X document - a readable PDF/A-3B carrying that same XML embedded (factur-x.xml) plus the Factur-X XMP - use POST /invoice/generate-pdf, which wraps this core (CII only) and re-validates and re-reads its own output.
  • Two syntaxes, two conformance profiles: CII EN 16931 (default) and UBL 2.1 (syntax:"ubl", with Peppol BIS 3.0 as an option). Factur-X EN 16931 profile only in CII (no MINIMUM/BASIC/EXTENDED emission); XRechnung is not produced in generation (it remains served in validation via /invoice/validate?ruleset=xrechnung).
  • Invoice (type 380) only in UBL: the UBL credit note (CreditNote, type 381) is not generated - it is a distinct XML root, out of scope for v2; a type_code: "381" with syntax:"ubl" returns an actionable unrepresentable issue (“use CII”). The CII credit note is still produced.
  • No numbering: invoice_number (BT-1) is yours - the service does not allocate or track invoice numbers.
  • No archiving, no state: stateless pure computation - no duplicate detection, no signature, no PDP transmission.
  • Plausibility is syntax-only: the identifiers in the input are checked for syntactic plausibility (mod-97, Luhn, VAT syntax), never against a registry. Generate the invoice, then check the counterparty - company identity (/company/resolve) and live EU VAT status (/company/vat) are separate paid calls; see the live /catalog for the endpoints actually served today.

The three-settle Peppol flow

On a Peppol issuance flow an agent can chain three distinct paid calls, one flow: generate a BIS 3.0 invoice, prove it, then check the recipient’s Peppol registration.

  1. /invoice/generate (syntax:"ubl", ruleset:"peppol") fabricates the guaranteed BIS 3.0 invoice - this endpoint.
  2. /invoice/validate?ruleset=peppol re-proves it, verdict in hand, on the same OpenPeppol pack.
  3. /company/peppol checks the recipient’s reachability in the Peppol directory before you send.

Three grids, three settles, one flow.

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 - for ruleset:"peppol" - the OpenPeppol BIS Billing 3.0 rule pack. 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-pdf - the same business JSON as a complete Factur-X PDF/A-3B with the CII XML embedded.
  • POST /invoice/validate - the same four-layer rule set, sold as a standalone verdict on any incoming invoice; with ?ruleset=peppol it re-proves a BIS 3.0 invoice you generated here.
  • GET /company/peppol - check the recipient is a reachable Peppol participant before you send the BIS 3.0 invoice.
  • POST /invoice/read - the faithful extraction of an incoming invoice into the EN 16931 semantic model.
  • POST /invoice/vat-treatment - the sourced VAT determination behind the vat_treatment field, sold on its own.
  • 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 before paying an invoice you received.