# Pre-flight validation

URL: https://postman.slovakodata.com/developers/en/predbezna-validacia/
Updated: 2026-09-29

Check an invoice and the receiver's reachability before you send it.

You can have a document checked before you send it. The endpoint runs **the same
five-level validation chain** that runs after submission — so whatever passes here will
pass there. It creates no invoice, stores nothing and **consumes no credit**.

## Validating a document

```http
POST /api/v1/documents/validate
X-Api-Key: …
Content-Type: application/json
Accept-Language: en
```

```json
{
  "xml_content": "…",
  "receiver_peppol_id": "0245:2120984085"
}
```

Only `xml_content` is required. **Including `receiver_peppol_id` also turns on the
receiver check** via the Peppol SMP; without it only the document itself is validated.

The response is **always 200**, even for an invalid document. An invalid invoice is not a
call failure — the verdict is part of the data.

```json
{
  "valid": false,
  "network_ready": false,
  "overall_severity": "FATAL",
  "validation_levels": {
    "L1_xml": "OK", "L2_xsd": "OK", "L3_en16931": "OK",
    "L4_peppol": "FATAL", "L5_sk": "WARNING"
  },
  "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 state…",
    "fix": "Add the cbc:ProfileID element with the value…",
    "location": "Invoice header → Profile",
    "localized": true
  }],
  "warnings": [],
  "truncated": false,
  "total_findings": 1,
  "document": { "document_type": "INVOICE", "invoice_number": "FA-2026-001", "…": "…" },
  "receiver": {
    "peppol_id": "0245:2120984085",
    "registered": true,
    "supports_document_type": false,
    "reason": "NOT_SERVICED",
    "checked_at": "2026-08-05T10:00:00Z"
  },
  "duration_ms": 1840
}
```

## Findings are localized, the original stays

`title`, `explanation`, `fix` and `location` are translated according to the
`Accept-Language` header (`sk` and `en` are supported; without the header we use `en`).
The language actually applied comes back in `Content-Language`.

The same finding shape is returned by `GET /api/v1/invoices/{id}/validation` — see [Validation](/developers/en/validacia/).

`raw_message` and `raw_location` are **never translated** — they are the original
Schematron text and XPath. Those are exactly what you need when working the problem with
your ERP vendor.

If a rule is not in our catalogue yet, `localized` is `false` and `title` carries the
original English message. A finding is never lost.

## The five validation levels

| Level | What it checks |
|---|---|
| `L1_xml` | XML well-formedness |
| `L2_xsd` | UBL 2.1 schema |
| `L3_en16931` | EN 16931 rules |
| `L4_peppol` | Peppol BIS 3.0 rules |
| `L5_sk` | Slovak national rules |

Values are `OK`, `WARNING`, `FATAL` or `SKIPPED`. `SKIPPED` means the level did not run —
typically because validation stopped earlier: a failure at `L1` halts everything after it.

The EN 16931 and Peppol BIS rules run in a single chain, so they are never executed twice;
each level still reports its own result.

Level `L5_sk` can additionally return `ERROR` — the Slovak rule set failed to execute. It does
not fail the document, but you will find a finding with `rule_id` `SK_ENGINE` among the
warnings, so it is clear the Slovak rules were not verified.

## The receiver check

`network_ready` is `true` only when the document is valid **and** the receiver is in the
network **and** supports the document type. Both `network_ready` and `receiver` appear in
the response **only when you asked for the check**.

The `reason` field distinguishes three situations that must not be conflated:

| `reason` | Meaning | What to do |
|---|---|---|
| `NOT_REGISTERED` | the receiver is not in the Peppol network | verify their Peppol ID |
| `NOT_SERVICED` | the receiver is in the network but does not support this document type | verify the document type |
| `LOOKUP_UNAVAILABLE` | **no verdict was reached** — the SMP did not answer | retry shortly |

With `LOOKUP_UNAVAILABLE` both `registered` and `supports_document_type` are `null`, not
`false` — we did not establish that the receiver is missing, we simply did not find out.

## Checking a receiver without a document

If you only need to know whether a customer is reachable, you do not have to send a
document:

```http
GET /api/v1/participants/0245:2120984085/reachability?document_type=INVOICE
```

The response has the same shape as the `receiver` block above.

## Limits

- documents up to **10 MB**, the same as for sending; larger ones return **413**
- at most **500 findings** per response; when truncated, `truncated` is `true` and
  `total_findings` carries the real count
- if the validation service is temporarily unavailable we return **503** with a
  `Retry-After` header; sending invoices is unaffected
- validations and reachability lookups per minute depend on your plan — from **10
  validations and 30 lookups** on MINI to **300 validations and 120 lookups** on ENTERPRISE
  ([Limits & quotas](/developers/en/limity/)). Both paths have their own counter —
  validating in a loop will not exhaust your invoice-sending quota. Your limit and remaining
  allowance are carried by the `RateLimit-Policy` and `RateLimit` headers; exceeding it
  returns **429** with `Retry-After`
