InvoiceValidator.eu

API

One POST. No account, no key, no signup. Send an invoice, get back every rule it breaks with the reason and the fix.

Quick start

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

POST /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.

profileWhich 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.
syntaxubl, 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 →

Response

{
  "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.

POST /v1/validate/batch

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.

POST /v1/extract

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.

GET /v1/rules

The whole catalogue. Filter with ?syntax=ubl, ?category=model, ?severity=fatal.

GET /v1/rules/{id}

One rule with its cause, fix and the XPath actually evaluated, per syntax. Case-insensitive: https://invoicevalidator.eu/v1/rules/br-co-17.

Limits and errors

401An API key was sent but is unknown or has been revoked.
402The key's monthly allowance is used up. The message gives the number. Nothing is charged on top automatically.
429Rate 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.
422A 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.
400Empty body, or XML that cannot be parsed.
413Larger than 5 MB (15 MB for a PDF).

Authentication

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 →

Privacy

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.