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 (URNurn:cen.eu:en16931:2017emitted 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; withruleset: "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
paymentbecomes a credit-transfer payment instruction BG-16 (UNTDID 4461 code30); - 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_reasonserror, never a silent arbitration.
Totals policy - computed if absent, verified if provided, never repaired
totalsabsent (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.totalsprovided: it is verified against that same computation. Any divergence is a400with aninconsistent_totalsissue 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 indata.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(orsyntaxabsent): the v1 behaviour, byte-for-byte unchanged. ubl: a UBL 2.1 Invoice, type 380. Two shape differences in the response (see below):data.syntaxis emitted ("ubl"), anddata.profileisnull- the Factur-X profile (MINIMUM…EXTENDED) is a CII notion. In UBL the EN 16931 identifier lives in the XML’sCustomizationID, never aurn:factur-x.eu:…URN.- A value other than
cii/ublis a400 INVALID_INVOICE_INPUTwith aninvalid_syntaxissue - 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"requiressyntax:"ubl". Peppol × CII is an inconsistent input (Peppol mandates UBL) - a400 ruleset_requires_ubl, not a billed verdict. (Contrast the validation side, where a CII sent with?ruleset=peppolis a billed 200 with aninvalidverdict: 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 theProfileID(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 }, whereschemeis an official EAS code (e.g.0208for the Belgian enterprise number,0009for the SIRET).valueis the bare identifier: the EAS code lives inschemeand must not be repeated insidevalue(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); aschemeoutside the list is a400 invalid_eas_scheme.buyer_reference(BT-10) is required by R003. Both shape checks hold withoutruleset: "peppol", in CII as well as UBL. The EAS code repeated insidevalue, 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 a400 implausible_identifierwhosepathnames 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 backvalidated: trueany more - on/invoice/generateand on/invoice/generate-pdfalike. - 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 indata.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 (norulesetfield 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_reasonsupplied in the input is kept exactly as written, andvat_treatment.notes[]says so. - Consistency is required:
supplier_countryandcustomer_countrymust be the countries the invoice itself declares (BT-40 / BT-55). Otherwise it is a400 vat_treatment_mismatchnaming 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, andnotes[]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:
| Field | Description |
|---|---|
invoice_mention | The text actually written into BT-120 (null when none applies) |
mention_applied_to | The VAT categories that received it - [] when nothing was written |
notes | Why 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" }
}
}
| Field | Description |
|---|---|
xml | The complete document - CII D16B or UBL 2.1 per syntax - ready to transmit |
profile | The 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 |
validated | Always true on a 200 - the echo of the generated-then-validated post-condition |
computed | The 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 |
syntax | Emitted in UBL only ("ubl"); absent in CII (backward-compatible) |
ruleset | Emitted in Peppol only ("peppol"); absent otherwise (backward-compatible) |
warnings | Passed Peppol warning-severity rules (non-binding); omitted when empty |
vat_treatment | The 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 carriesissues[]with all independent errors reported together, so the agent fixes everything in one turn - no round trip per error. Each issue carries a stablecode, amessage, and the normative vocabulary where available (termin BT/BG,rule_idBR-xx,path).
Stable issue codes:
code | Meaning |
|---|---|
missing_field | a required term is absent (BT/BG named, path given) |
invalid_date | a date is not YYYY-MM-DD (BT-2, BT-9, …) |
invalid_text | empty or illegal text for the named field |
inconsistent_totals | a provided total diverges from the BR-CO computation (computed value in the message) |
rule_violation | an EN 16931 business rule is violated - one issue per violation, with its official rule_id (BR-xx) |
implausible_identifier | IBAN (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_currency | the 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 |
unrepresentable | a term the generated CII could not carry faithfully - refused, not dropped |
unsupported_profile | a requested profile other than EN 16931 (the only generation profile in v1) |
conflicting_exemption_reasons | diverging BT-120 exemption reasons within the same VAT group |
amount_out_of_range | a 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_decimal | a decimal-field string is outside the plain form -?digits(.digits)? - scientific notation first - for the field named in path |
invalid_syntax | syntax outside cii/ubl |
invalid_ruleset | ruleset outside peppol |
ruleset_requires_ubl | ruleset:"peppol" without syntax:"ubl" (Peppol mandates UBL 2.1 - an inconsistent input, not a verdict) |
invalid_eas_scheme | an EAS code (electronic_address.scheme) outside the official Peppol list |
vat_treatment_mismatch | a 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.
| Status | code | Case | Billed |
|---|---|---|---|
| 200 | - | Invoice generated and passed its own validation (validated: true) | yes |
| 400 | INVALID_INPUT | Invalid JSON, unknown field / typo (named in the message), or any query parameter (none accepted) | no |
| 400 | INVALID_INVOICE_INPUT | Conformant generation impossible - all issues in issues[] | no |
| 413 | PAYLOAD_TOO_LARGE | Body over the 10 MB cap | no |
| 404 | NOT_FOUND | Unknown route | no |
| 500 | INTERNAL | Reserved to a violated post-condition: the output failed its own validation - an internal bug; the output is never served, detail logged, never exposed | no |
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 - usePOST /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"withsyntax:"ubl"returns an actionableunrepresentableissue (“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/catalogfor 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.
/invoice/generate(syntax:"ubl",ruleset:"peppol") fabricates the guaranteed BIS 3.0 invoice - this endpoint./invoice/validate?ruleset=peppolre-proves it, verdict in hand, on the same OpenPeppol pack./company/peppolchecks 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=peppolit 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 thevat_treatmentfield, sold on its own.- 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 before paying an invoice you received.