Check invoices before releasing your software
catch the rejection before your customer does.
Add invoice checks to continuous integration (CI), the automated checks that run when your code changes. Review errors before releasing invoices to customers or a delivery provider.
The whole integration
- uses: attestwire/validate-einvoice-action@v1
with:
files: "invoices/**/*.xml"
sarif: einvoice.sarifErrors marked fatal stop the automated job. Results appear beside the code changes and in a linked findings table. Optional SARIF output is a structured report that GitHub can show in its Security tab.
- uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: einvoice.sarif
category: einvoiceIt runs offline, in the runner
It needs no account or API key and makes no network call. The action bundles a pinned engine version, so the same version runs the same rules.
Upgrade the rules by changing the action version in a pull request. You can review any changed findings before enforcing them.
Add api-key to use the hosted ruleset instead. Each response
names the engine and ruleset used for that document.
Create reports outside GitHub
The action is a wrapper. Underneath it, the library exports the same two reports directly:
import { validateInput, toSarif, toJunitXml } from "@attestwire/en16931";
const { errors } = validateInput(invoice);
const provenance = { engineVersion: "0.14.0", profile: "xrechnung-ubl" };
writeFileSync("einvoice.sarif", JSON.stringify(toSarif(errors, provenance)));
writeFileSync("einvoice.xml", toJunitXml(errors, provenance));Jenkins, GitLab, CircleCI, Buildkite and Azure Pipelines all read JUnit XML; GitHub reads SARIF.
Both are pure functions. They read no clock, file or
environment variable: every value that varies between runs (the engine
version, the ruleset versions, the timestamp, the document URI) is passed
in. The same inputs give byte-identical output, so two reports differ
only where the findings differ. A report that cannot name the validator
version that produced it cannot be checked later, which is why
engineVersion is the one required field.
Neither function decides whether your build fails. SARIF has no "fail the
build" flag; your repository settings decide that.
toJunitXml maps a fatal finding to <failure>,
and the README documents the full mapping.
Exit codes
| Exit | When |
|---|---|
0 |
Every document validated, and nothing crossed the fail-on threshold. |
1 |
A document produced a finding at or above fail-on. |
1 |
A file could not be read, parsed, or is not an invoice. |
1 |
The files pattern matched nothing. |
1 |
An input is missing or invalid, or (api mode) the key was rejected or the API unreachable. |
A run over zero files fails. An unreadable file also fails instead of being skipped. Invalid settings are reported before files are opened.
Informational findings never fail a build under any setting. A warning
fails only under fail-on: warning, because a warning is
something the official validator reports while still accepting the
document, so by default the build stays green for it.
What this check does not cover
A release pipeline that ships documents into a regulated flow may still want the official validator in it.
This action checks the invoice's contents, not the XML file's
structure. The official validators also check structure, so a file this
action passes can still be rejected by them. Rules that constrain the XML
itself do not run here at all: BR-DE-13 and
BR-DE-21 (a BR code is the name of one rule; the second of
these checks the specification identifier, field BT-24, the standard's
number for that field).
The limits page lists every gap.
A practical setup is to run this action on pull requests and keep the official validator in the release job. One gives developers a fast, actionable result; the other remains the external authority.
What we can show you is our own homework against those validators, failures included: the KoSIT record (KoSIT is the German office that publishes the official XRechnung checker), the Peppol record (Peppol is the network many European buyers receive invoices through) and the DGFiP record (the French tax administration).