einvoice / Browser validator
Validate an invoice in your browser
Drop an XRechnung XML — UBL or UN/CEFACT CII, both official syntaxes — or a ZUGFeRD / Factur-X PDF here and the same 297-rule EN 16931 engine that powers the CLI grades it — no account, no install. The engine runs inside your browser (CPython on WebAssembly via Pyodide), so the invoice itself is never uploaded: after the one-time runtime download, validation makes no network request that carries your file.
Step 1 — load the validator
Nothing is fetched until you click. The button downloads the pinned Pyodide runtime from the jsDelivr CDN (exact version 314.0.2, subresource-integrity checked) plus the engine modules from this site — about 13 MB uncompressed, less on the wire. On a slow connection the first start can take a minute; afterwards your browser cache makes it quick.
No browser run today? The identical engine is a pip install away (Python 3.10+, zero dependencies):
python3 -m pip install verifyhash-einvoice
einvoice validate --profile xrechnung invoice.xml
Step 2 — pick an invoice
What you get, and honest limits
The output is the engine’s own conformance report: a pass/fail verdict (fail means a fatal rule fired), the fatal and warning counts, and one row per finding with its severity, rule id and message. Where the bundled remediation catalog has an entry for the rule, the row also carries that entry’s plain-language title and its one-line Fix hint — the same wording the CLI’s --json report and the rule pages use, relayed verbatim, never rephrased in the browser. Every rule id that has a reference page links to it — the same pages under the rule index, with the official Schematron assert, the BT/BG terms and a concrete fix in English and German.
Where the engine could place a finding in the file, the row also carries a line number — in one of two deliberately different shapes, because they mean different things. at line 28 means the offending element is on line 28. (insertion point line 28) means the opposite: nothing on line 28 is wrong, that is simply where the missing element would go — the wording matches the CLI’s text report exactly. Findings the engine cannot place honestly carry no line at all rather than a guessed one; the 0-th line and a “:1” fallback are both worse than silence.
Every result — pass or fail — also offers a Download this report link. That file is the same single self-contained HTML document einvoice validate --format html writes on the command line: no script, no external stylesheet, no web font, nothing to fetch, so it opens the same way in five years or behind a corporate proxy. It is built by the engine in this tab from the validation you just ran and handed to you as a local blob: file — your invoice is still not uploaded, and neither is the report. Forward it to whoever owns the Leitweg-ID, or attach it to the ERP ticket.
Next to that link, a Language of the downloaded report selector switches that one file between English (the default) and German — both are built by the engine from the same single validation run, so switching re-validates nothing and uploads nothing. Stated exactly: the German file carries German rule titles and German Behebung fix hints on all 297 catalogued rules, plus the official KoSIT German <sch:assert> wording on the 50 rules that publish one; an EN 16931 core rule with no official German keeps its English message, which the engine itself marks [en] and explains in a note rather than machine-translating. The page you are reading stays in English either way.
Same limits as the CLI, stated plainly: no XSD structural validation; UBL Invoice and UBL 2.1 CreditNote are both validated through the same EN 16931 engine, and so is UN/CEFACT CII — both as a plain .xml file whose root element is CrossIndustryInvoice (XRechnung’s second official syntax, and what ZUGFeRD and Factur-X carry) and as the XML embedded in a ZUGFeRD/Factur-X .pdf, which the page extracts for you before grading. Any other root element is still refused up front with the structural S-ROOT fatal rather than half-graded. Every fireable official BR-CL-* code-list check is now implemented in both syntaxes. A green result means “no implemented fatal rule fired”, not “certified legally conformant”. Browser-specific: the ~13 MB runtime is real — on a metered or very slow connection the terminal route below is the better tool. Encrypted or exotic PDF containers the zero-dependency extractor cannot open are reported honestly as unsupported-container, never silently passed.
Prefer the terminal?
The browser page and the package run the same engine modules — the copies under engine/ are byte-identical to the released package, pinned by sha256 in a committed manifest. Locally that is:
python3 -m pip install verifyhash-einvoice
einvoice validate --profile xrechnung invoice.xml
einvoice validate --json --profile xrechnung invoice.xml
Start with the 5-minute worked walkthrough, or read the honest comparison with the official KoSIT validator and Mustangproject.