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

one step in your workflow
- uses: attestwire/validate-einvoice-action@v1
  with:
    files: "invoices/**/*.xml"
    sarif: einvoice.sarif

Errors 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.

the upload step
- uses: github/codeql-action/upload-sarif@v4
  if: always()
  with:
    sarif_file: einvoice.sarif
    category: einvoice

It 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:

SARIF and JUnit, from the package
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

ExitWhen
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).

The action's README, every input and output · The library, if you would rather call the functions yourself · The other integrations · What we don't do