API/Validation
Validate a Peppol document
POST/v1/validations· API key
Validates a Peppol BIS Billing 3.0 invoice or credit note in three
layers, in order: the UBL 2.1 schema (xsd), the EN 16931 rules
(en16931) and the Peppol rules (peppol). A layer that cannot run
because an earlier one failed is reported as skipped, not passed.
Every finding carries the official rule text verbatim, a plain-English explanation, the business terms it concerns and, where one exists, a concrete fix.
Send the document either as raw XML (application/xml, convenient
from curl) or wrapped in JSON (application/json, which is what an
agent's tool call can carry). Both are validated identically.
A document that breaks the rules is a successful validation: the
response is 200 with valid: false. Nothing is stored.
curl -X POST https://api.einvoicing.dev/v1/validations \ -H "Authorization: Bearer $EINVOICING_API_KEY" \ -H "Content-Type: application/xml" \ --data-binary @invoice.xml<?php$client = new GuzzleHttp\Client();$response = $client->request('POST', 'https://api.einvoicing.dev/v1/validations', [ 'headers' => [ 'Authorization' => 'Bearer ' . getenv('EINVOICING_API_KEY'), 'Accept' => 'application/json', ], 'json' => [ 'document' => '<document>', ],]);$data = json_decode((string) $response->getBody(), true)['data'];payload := `{ "document": "<document>"}`req, _ := http.NewRequest(http.MethodPost, "https://api.einvoicing.dev/v1/validations", strings.NewReader(payload))req.Header.Set("Authorization", "Bearer "+os.Getenv("EINVOICING_API_KEY"))req.Header.Set("Content-Type", "application/json")res, err := http.DefaultClient.Do(req)if err != nil { log.Fatal(err)}defer res.Body.Close()const res = await fetch("https://api.einvoicing.dev/v1/validations", { method: "POST", headers: { Authorization: `Bearer ${process.env.EINVOICING_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "document": "<document>" }),});const { data } = await res.json();Parameters
| Name | In | Type | Description |
|---|---|---|---|
ruleset | query | string | Pin validation to a specific ruleset |
Request body
application/xmlapplication/json· required
<?xml version="1.0" encoding="UTF-8"?><Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"> <cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID> <cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID> <cbc:ID>INV-2026-0042</cbc:ID> <cbc:IssueDate>2026-09-11</cbc:IssueDate> <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode> <cbc:DocumentCurrencyCode>GBP</cbc:DocumentCurrencyCode> <!-- parties, tax total, monetary totals and lines omitted for brevity --></Invoice>Response
200 The document was validated. Check valid: true means no finding
has severity error; warnings may still be present.
| Field | Type | Description |
|---|---|---|
datarequired | ValidationReport | The outcome of validating one document. |
{ "data": { "valid": false, "ruleset": { "id": "peppol-bis-billing-3.0.21", "version": "3.0.21" }, "document": { "type": "invoice", "customization_id": "urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0", "profile_id": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0" }, "layers": [ { "name": "xsd", "status": "passed" }, { "name": "en16931", "status": "passed" }, { "name": "peppol", "status": "failed" } ], "summary": { "errors": 1, "warnings": 0 }, "findings": [ { "rule_id": "PEPPOL-EN16931-R003", "layer": "peppol", "severity": "error", "message": "A buyer reference or purchase order reference MUST be provided.", "explanation": "Peppol needs something the buyer can use to route the invoice internally. Neither a buyer reference (BT-10) nor a purchase order reference (BT-13) was present.", "fix": "Add the buyer's purchase order number as `cac:OrderReference/cbc:ID`, or their reference as `cbc:BuyerReference`.", "business_terms": [ "BT-10", "BT-13" ], "location": { "xpath": "/Invoice", "line": 2, "path": null }, "docs_url": "https://www.einvoicing.dev/rules/PEPPOL-EN16931-R003" } ] }}Errors
Every error is application/problem+json (RFC 9457). Branch on type, which is stable, never on title or detail.
| Status | When |
|---|---|
| 400 | The body could not be parsed as the XML or JSON its |
| 401 | No key, or a key that is unknown or revoked. Problem |
| 402 | The Free plan's monthly allowance for this operation is used up. This
is a billing state, not a rate limit: waiting will not help until the
period resets or the plan changes. Paid plans never receive it.
Problem |
| 413 | The body is larger than 5 MiB. Problem |
| 415 | The |
| 422 | The body is well-formed but not a document this API validates:
the root is not a UBL |
| 429 | Too many requests in a short window. Slow down and retry after the
number of seconds in |
| 503 | The validator could not judge the document. The fault is on our
side, not in the document, and nothing was stored. It is never
reported as valid or invalid. Retry after the number of seconds in
|
Schemas
ValidateDocumentRequest
A Peppol document wrapped in JSON, for clients that can only send JSON.
| Field | Type | Description |
|---|---|---|
documentrequired | string | The complete UBL 2.1 Invoice or CreditNote XML, as a string. |
ValidationReport
The outcome of validating one document.
| Field | Type | Description |
|---|---|---|
validrequired | boolean | True when no finding has severity error. |
rulesetrequired | RulesetRef | The ruleset a report was produced against. |
documentrequired | object | What the document declared itself to be. |
typerequired | string | From the document's root element.
|
customization_idrequired | string or null | The document's cbc:CustomizationID (BT-24), or null if absent. |
profile_idrequired | string or null | The document's cbc:ProfileID (BT-23), or null if absent. |
layersrequired | object[] | Each validation layer in the order it runs. |
namerequired | string | xsd is the UBL 2.1 schema, en16931 the European
standard's rules, peppol the Peppol BIS Billing 3.0 rules.
|
statusrequired | string | skipped means an earlier layer failed, so this one could not run meaningfully.
|
summaryrequired | object | |
errorsrequired | integer | Number of findings with severity error. |
warningsrequired | integer | Number of findings with severity warning. |
findingsrequired | Finding[] | Every finding, errors first, then in document order. |