Five layers. The first fatal error stops the rest and the document is not sent.
| Layer | What it checks | On failure |
|---|---|---|
L1 | Well-formed XML | FATAL |
L2 | UBL 2.1 schema (XSD) | FATAL |
L3 | EN 16931 (CEN Schematron) | FATAL or WARNING |
L4 | Peppol BIS Billing 3.0 (Schematron) | FATAL |
L5 | Slovak rules per the BIS transposition | FATAL or WARNING |
Every layer reports its own result
Layers L2, L3 and L4 run as a single chain, so the rules are never executed twice.
Each layer still reports its own result — a finding is attributed to the rule set that
produced it.
A layer is OK, WARNING, FATAL or SKIPPED.
SKIPPED does not mean “passed”. It means the layer never ran because the chain stopped
earlier: an XSD error stops both L3 and L4, a fatal error at L1 stops everything else.
“Not checked” and “fine” never collapse into the same value.
Layer L5 can additionally return ERROR — the Slovak rule set failed to execute. This does
not stop the document, but a finding with rule_id SK_ENGINE is added to the warnings so the
response makes it obvious that the Slovak rules were not verified.
What the Slovak layer checks
Layer L5 runs when the supplier is a Slovak entity. It has two severities —
a fatal error stops the document, a warning only flags it and lets it through.
- Fatal: a missing
0245scheme on identifiers, an incomplete supplier or buyer address. - Warning: a suspicious tax ID or company ID format, a missing legal form, tax-representative inconsistencies.
Validation result via the API
GET /api/v1/invoices/{id}/validation
X-Api-Key: …
Accept-Language: en
Returns the latest verdict stored for the invoice. Results are append-only — revalidating after a fix adds a new record and the previous one stays in history.
| Status | When |
|---|---|
200 | a verdict exists |
202 | the invoice is still PENDING or VALIDATING; both retry_after and the Retry-After header tell you how many seconds to wait |
404 | the invoice does not exist — or predates result persistence and will never get a verdict |
Findings have the same shape as in pre-flight validation:
title, explanation and fix are localized according to the Accept-Language header (sk
or en, en without the header; the language used comes back in Content-Language), while
raw_message and raw_location are the original Schematron text and XPath. One finding model
is therefore enough for both endpoints.
{
"id": "5a0c…",
"invoice_id": "7104dd91-…",
"created_at": "2026-09-01T09:41:10Z",
"valid": false,
"overall_severity": "FATAL",
"validation_levels": {
"L1_xml": "OK", "L2_xsd": "OK", "L3_en16931": "OK",
"L4_peppol": "FATAL", "L5_sk": "SKIPPED"
},
"errors": [{
"rule_id": "PEPPOL-EN16931-R001",
"rule_set": "PEPPOL",
"level": 4,
"severity": "FATAL",
"raw_message": "Business process MUST be provided.",
"raw_location": "/Invoice[1]/cbc:ProfileID",
"title": "Business process identifier is missing",
"explanation": "Peppol requires the invoice to declare…",
"fix": "Add the cbc:ProfileID element with the value…",
"location": "/Invoice[1]/cbc:ProfileID",
"localized": true
}],
"warnings": [],
"truncated": false,
"total_findings": 1,
"duration_ms": 1840
}
The example is trimmed — the response also carries the deprecated fields described below.
id tells you which validation run produced the verdict.
If we do not have the rule in the catalogue yet, localized is false and title carries the
original English message. With more than 500 findings the list is truncated (truncated) and
the real count is in total_findings; errors take precedence over warnings.
Deprecated fields — removed in 2.0.0
Until version 2.0.0 the response also carries the original fields with identical values, so
existing integrations keep working unchanged. They are marked deprecated: true in OpenAPI.
| Deprecated | Use instead |
|---|---|
overallSeverity | overall_severity |
validationLevels | validation_levels |
durationMs | duration_ms |
invoiceId | invoice_id |
createdAt | created_at |
organizationId | no replacement — it is the caller’s own organisation |
finding: ruleId | rule_id |
finding: ruleSet | rule_set |
finding: message | raw_message (original) or title (localized) |
finding: location | raw_location — until 2.0.0 location carries the XPath, afterwards it becomes the readable position as in pre-flight validation |