One POST. No account, no key, no signup. Send an invoice, get back every rule it breaks with the reason and the fix.
curl -X POST --data-binary @invoice.xml \ -H 'Content-Type: application/xml' \ https://invoicevalidator.eu/v1/validate?profile=peppol
Or as a file upload — this is also how you send a Factur-X / ZUGFeRD PDF:
curl -F file=@invoice.pdf https://invoicevalidator.eu/v1/validate
Body: the invoice, either as a raw XML/PDF body or as multipart with a file
part. Maximum 5 MB for raw XML, 15 MB for a Factur-X/ZUGFeRD PDF —
a hybrid PDF wraps a rendered document around a few KB of XML, so it is legitimately
larger.
| profile | Which rule set to check against.
en16931 = EN 16931 (core). peppol = Peppol BIS Billing 3.0 (+ national rules). xrechnung = XRechnung 3.0.2 (Germany). Defaults to en16931.The Peppol profile also carries the national rule sets for Germany, Denmark, Greece, Iceland, Italy, the Netherlands, Norway and Sweden. They switch on by themselves from the seller's country — a Norwegian seller is checked against NO-R-* as well, with no extra
parameter. |
| syntax | ubl, cii or eslog.
Auto-detected from the root namespace if omitted; you should not normally need it. |
Not supported, and named as such: three EU mandates run on a national XML that is not EN 16931 — Italy's FatturaPA, Spain's Facturae and Poland's FA_VAT. Send one and you get a 422 that says which format it is and why it is not checked, rather than a generic "unrecognised document". If you are mapping EN 16931 to one of those downstream, the upstream document is exactly what this validator is for.
Slovenian e-SLOG 2.0 files are accepted and detected automatically. They are checked
against the official ePOS schema for structure only — profile does not apply and
comes back null, and the checked field says so in words.
What that covers →
{
"syntax": "ubl",
"profile": "peppol", // null for e-SLOG: no profile was applied
"checked": "EN 16931 business rules, peppol profile",
"source": null, // PDF attachment the XML came from, if any
"valid": false, // true when there are no fatal findings
"counts": { "fatal": 2, "warning": 0, "container": 0 },
"findings": [
{
"rule": "BR-CO-17",
"severity": "fatal",
"message": "VAT category tax amount (BT-117) = ...",
"cause": "In a VAT breakdown group the VAT category tax amount ...",
"fix": "Set BT-117 = round(BT-116 x BT-119 / 100, 2) per breakdown group ...",
"confidence": "curated", // curated | template | none
"category": "rule", // rule | syntax | container | codelist
"line": 25,
"excerpt": [ { "line": 24, "text": "..." } ],
"location": "/Invoice/TaxTotal/TaxSubtotal",
"business_terms": ["BT-116", "BT-117", "BT-119"]
}
]
}
confidence tells you where the wording came from: curated is written by
hand, template is generated from the rule's own shape, none means only the
official rule text is available. It is exposed so you never have to guess.
category says what kind of thing failed, and one value deserves attention if you
handle Factur-X or ZUGFeRD. container findings are about the PDF wrapper, not the
invoice: the XML can be perfectly valid EN 16931 while a conformant reader still cannot
find it, because it is not listed in the catalog's /AF array or the XMP
metadata is missing. Those are reported as warnings, so counts.fatal will be
0 — an integration that ships on counts.fatal == 0 alone will send
invoices that receivers cannot open. counts.container is broken out for exactly
that check. France has required Factur-X since 1 September 2026.
A ZIP of invoices in, one result per file out — the shape of the job if you build accounting software and want to know which of your generated invoices are wrong.
curl -F file=@invoices.zip https://invoicevalidator.eu/v1/validate/batch?profile=peppol
Limits: 20 MB archive, 200 files, 60 MB uncompressed, 5 MB per XML
invoice and 15 MB per PDF.
A file that cannot be read comes back with an error instead of findings, so one bad
document never fails the batch.
A Factur-X / ZUGFeRD PDF in, the embedded invoice XML out. The point of a hybrid PDF is that the XML is invisible to a human reader; this hands it back.
curl -F file=@invoice.pdf https://invoicevalidator.eu/v1/extract -o invoice.xml
422 if the file is not a PDF, or is a PDF with no invoice attached — with a sentence saying which.
The whole catalogue. Filter with ?syntax=ubl, ?category=model,
?severity=fatal.
One rule with its cause, fix and the XPath actually evaluated, per syntax. Case-insensitive:
https://invoicevalidator.eu/v1/rules/br-co-17.
| 401 | An API key was sent but is unknown or has been revoked. |
| 402 | The key's monthly allowance is used up. The message gives the number. Nothing is charged on top automatically. |
| 429 | Rate limit. Free: 20 validations and 1,500 page reads a minute, per IP. With a key: 600 validations a minute. The read limit is deliberately high — a crawler working through the rule reference should never be throttled. |
| 422 | A PDF with no embedded invoice XML (a printed PDF is not a
Factur-X invoice), an unknown profile or syntax, or a batch archive
that breaks a limit: more than 200 files, or expanding to more than 60 MB. The message says
which. Batch limits are checked against the sizes declared in the archive, before anything is
decompressed. |
| 400 | Empty body, or XML that cannot be parsed. |
| 413 | Larger than 5 MB (15 MB for a PDF). |
None needed. Everything on this page works without a key, an account or a signup, and that is not a trial — there is no monthly cap on the free tier, because counting one would mean keeping a per-visitor counter for a month and this site does not keep those.
The free tier is bounded by rate: 20 validations a minute. If you are checking a directory of thousands, that is the part that hurts, and it is what the paid plan changes — a key raises you to 600 a minute and includes 50,000 a month. Same engine, same rules, same explanations: you are paying for throughput, not for anything held back.
curl -X POST --data-binary @invoice.xml \ -H 'Authorization: Bearer iv_live_...' \ -H 'Content-Type: application/xml' \ https://invoicevalidator.eu/v1/validate?profile=peppol
Pricing and how to get a key →
Invoices are validated in memory and never written to disk or logged. There is no account, so there is nothing to delete. What is kept is an anonymous daily count of how many validations ran and how many pages were served, plus a fourteen-day web server request log holding the time, the path, the result and the user-agent string — no IP addresses and no document content. See privacy for the detail, including a correction dated 28 August 2026.