eliDocs
E-invoicing answers

How do I validate an e-invoice from code?

One POST of the file, structured JSON back. This covers the request, the fields worth branching on, and the failures that are not your invoice.

Validating an e-invoice programmatically is a single request: you post the document, you get back a verdict plus every finding as structured data. No file conversion, no toolchain to install, no Schematron processor to keep current.

The request

The request

Post the raw file. XML goes as application/xml; a hybrid ZUGFeRD or Factur-X PDF goes as application/octet-stream or application/pdf, and the invoice inside it is what gets judged.

validate.sh
curl -X POST "https://api.beliq.eu/v1/validate?format=auto" \
  -H "Authorization: Bearer $BELIQ_API_KEY" \
  -H "Content-Type: application/xml" \
  --data-binary @invoice.xml

format=auto lets the engine detect what it was given, which is what you want in an integration handling documents from more than one source. The detected syntax and profile come back on the response, so you can log what it decided rather than guessing.

The response you branch on

The response you branch on

Three fields carry the decision:

validboolean

The verdict. It is true only when nothing at fatal or error severity fired.

errorsarray

The findings that made it false.

warningsarray

warning and info findings, which do not affect the verdict.

Each finding gives you ruleId, severity, location (an XPath into your source) and a human-readable message. Branch on ruleId, never on message: rule IDs are the authority’s own stable identifiers, message text is prose and can be reworded.

The response also names what judged the document: the core rule version, the country or network overlay version where one applied, and the full list of rule artifacts with their hashes. Log those alongside your own record of the invoice. They are what lets you answer, later, why a document that passes today did not pass six months ago.

Failures that are not your invoice

Failures that are not your invoice

An integration has to tell three things apart, and the HTTP status does it:

A 200 with valid: false is a working call. Validation succeeded and the document failed. Do not retry it; nothing about the outcome will change.

A 4xx is your request. A 400 means the body failed the schema; a 413 means the file exceeded the endpoint’s size cap and was never read; a 415 means the content type is not one that route accepts; a 422 means the engine judged the document unprocessable. None of these are worth retrying unchanged.

A 429 or 503 is worth retrying. 429 means the monthly quota is exhausted or you are past the burst rate limit, and Retry-After tells you how long in seconds. 503 means the engine was briefly unavailable; retry after the same header, and note that the quota unit is refunded, so a retry does not cost a second document.

One case deserves a distinct branch: ACCOUNT_THROTTLED. Unlike an ordinary rate limit it blocks every route and can last minutes, and retrying inside a tight loop extends it. Back off properly rather than treating it as a burst.

What to store

What to store

Keep the response next to the invoice, not just the boolean. It carries a hash of the exact bytes that were judged and a fingerprint of the rules that judged them, both reproducible on your own machine without calling Beliq again. That turns “we validated this” into something checkable years later, which is the difference between a log line and evidence.

Try it without writing code first

Try it without writing code first

The playground in the Beliq dashboard runs the same engine on a sample invoice, with no key and no code, and has a Copy as cURL button that gives you the exact call you just ran. Starting there and then pasting the command into your own environment is usually faster than reading the reference cold.

Related