ePostman docs
Console

Validation

Five validation layers from XML to Slovak rules.

3 min read
  • #l1
  • #l4
  • #l5
  • #schematron
  • #peppol bis billing 3.0
  • #en 16931
  • #validation

Five layers. The first fatal error stops the rest and the document is not sent.

LayerWhat it checksOn failure
L1Well-formed XMLFATAL
L2UBL 2.1 schema (XSD)FATAL
L3EN 16931 (CEN Schematron)FATAL or WARNING
L4Peppol BIS Billing 3.0 (Schematron)FATAL
L5Slovak rules per the BIS transpositionFATAL 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 0245 scheme 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.

StatusWhen
200a verdict exists
202the invoice is still PENDING or VALIDATING; both retry_after and the Retry-After header tell you how many seconds to wait
404the 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.

DeprecatedUse instead
overallSeverityoverall_severity
validationLevelsvalidation_levels
durationMsduration_ms
invoiceIdinvoice_id
createdAtcreated_at
organizationIdno replacement — it is the caller’s own organisation
finding: ruleIdrule_id
finding: ruleSetrule_set
finding: messageraw_message (original) or title (localized)
finding: locationraw_location — until 2.0.0 location carries the XPath, afterwards it becomes the readable position as in pre-flight validation