France, 1 September 2026: every French VAT-registered business must be able to receive e-invoices.
EN 16931 errors that tell you what to change
Your customer's German portal rejected the invoice with
[BR-DE-15]-Das Element „Buyer reference“ (BT-10) muss übermittelt
werden. Attestwire returns the rule, the business term, what the
regulation requires, and the field to set, as JSON, in English. Open
source, MIT, zero dependencies, or a hosted API with 100 documents a
month free.
{
"rule": "BR-DE-15",
"field": "BT-10",
"severity": "fatal",
"message": "XRechnung requires a buyer reference (BT-10). For German public-sector buyers this is the Leitweg-ID; business buyers may supply any reference, but the field must be present.",
"fix": "Ask your client for their Leitweg-ID (public sector) or an order/customer reference, and set buyerReference.",
"example": "\"buyerReference\": \"04011000-1234512345-06\"",
"xpath": "/ubl:Invoice/cbc:BuyerReference",
"docsUrl": "https://attestwire.com/rules/BR-DE-15"
}A Schematron failure tells you which assertion tripped. It does not tell you that the thing it wants is a number only your customer can give you.
Start in one command
Two ways in, and neither one goes through a sales call. Run the library inside your own process, or call the hosted API and skip the install entirely. Both are free to start.
The library
Runs in your process, offline. No account and no key, because there is nothing to call. Import a function, hand it an invoice, get back the XML.
import { validateInput,
generateXRechnungUBL }
from "@attestwire/en16931";
const { valid, errors } =
validateInput(invoice);
const xml =
generateXRechnungUBL(invoice);
The hosted API
Nothing to install. Give an address, get a key back on this page, and post your first document.
Prefer the terminal? Same call, one curl
curl -sX POST \
-H content-type:application/json \
-d '{"email":"you@example.com"}' \
https://api.attestwire.com/v1/keysStore this key now. It is shown once and cannot be recovered — we keep only its SHA-256 hash.
Why this isn't a weekend job
Take the rejection at the top of this page. Everything you need is missing from that sentence. It does not tell you that BT-10 is the Leitweg-ID. It does not tell you that the Leitweg-ID belongs to your customer, not to you. So it does not tell you that the fix is an email, not a code change. The knowledge lives in a 200-page standard, a national CIUS, and one portal's habits. That is the part Attestwire has already done: 251 rules you can break, each with a page, an error payload, and a fix.
Don't trust us. Run it.
The two release fixtures we ship pass KoSIT 1.6.2 with the XRechnung
3.0.2 configuration: XSD plus both Schematron layers, verdict
ACCEPTABLE, zero messages. That is a conformance check on
two documents, not a parity suite. It says nothing about paths those
fixtures don't exercise. The fixtures and the script are in the repo
(packages/en16931/fixtures,
scripts/kosit-check.md); the run takes an afternoon.
- Every rule has a page. 251 rules you can trip from your own invoice, each carrying the exact error payload the library returns, so the link in your stack trace lands on the thing you are looking at. 40 more guard our own arithmetic and have pages that say so. Browse the reference.
- We publish where we differ. Our check is sometimes stricter than the published rule and sometimes narrower, and each rule's page says which, in the same words we would use in a bug report. You should be able to predict what we will reject before you send it.
-
Open source, and published. MIT-licensed
TypeScript:
npm install @attestwire/en16931, or read every line first at github.com/attestwire/en16931. Zero runtime dependencies, so the audit is the one file tree. - Live telemetry, not a marketing uptime number. We run a public VIES monitor you can go and read at vies.attestwire.com: 28 member states, polled every five minutes, measured data only.
Where it's early. The 291 rules on this
site are the ones v0.2.0 installs: the
reference is generated by running the published library, so the
numbers here and the numbers in your node_modules are the
same numbers. What is early is no longer the coverage. It is the
edges: generation is UBL only, so there is no Factur-X or CII output
and no credit notes; validation reads the JSON input model and not an
XML file you already have; and there is no SOC 2, no ISO 27001 and no
support SLA on any plan.
The complete list is on the about page.
Independently built and run. About Attestwire.
Run it in your own process — no account, no network call
@attestwire/en16931 is an open-source TypeScript
implementation of the EN 16931 semantic model and its national
restrictions. JSON in, conformant XML out — or an invoice in, a list of
teaching errors out. It makes no network calls and needs no account,
so it runs anywhere JavaScript runs. Generation targets XRechnung UBL
and Peppol BIS today. There is no Factur-X or CII generation, and
credit notes are not built yet. Validation covers the CII and Factur-X
profiles as well, but it reads your JSON, not an XML file you already
have.
published, still 0.x · v0.2.0 On npm and on GitHub. The shapes below are the shipped shapes.
npm install @attestwire/en16931Here is the call that produced the error at the top of this page. The object is a complete, conformant XRechnung with one field taken out, so its length is information in itself: this is what the German CIUS actually demands.
import { validateInput } from "@attestwire/en16931";
const result = validateInput({
profile: "xrechnung-ubl",
invoiceNumber: "2026-000142",
issueDate: "2026-08-09",
currency: "EUR",
invoiceTypeCode: "380",
seller: {
name: "Acme GmbH",
vatId: "DE123456789",
address: {
line1: "Chausseestr. 1",
city: "Berlin",
postalCode: "10115",
countryCode: "DE",
},
electronicAddress: { schemeId: "0204", value: "04011000-1234512345-06" },
contact: {
name: "Buchhaltung",
phone: "+49 30 1234567",
email: "rechnungen@acme.example",
},
},
buyer: {
name: "Stadt Bonn",
vatId: "DE987654321",
address: {
line1: "Berliner Platz 2",
city: "Bonn",
postalCode: "53111",
countryCode: "DE",
},
electronicAddress: { schemeId: "0204", value: "04011000-1234512345-06" },
},
lines: [
{
id: "1",
description: "Consulting, August 2026",
quantity: 10,
unitCode: "HUR",
unitPrice: 150,
vatCategory: "S",
vatRate: 19,
},
],
payment: {
meansCode: "58",
iban: "DE02120300000000202051",
accountName: "Acme GmbH",
},
paymentTerms: "30 days net",
});
result.valid; // false
result.errors[0]; // the object below
Every field in that error is load-bearing. rule and
field are the identifiers your customer's tax adviser and
their portal's rejection notice will use. message states
the requirement, fix states the action, xpath
points at the element in the generated document, and
docsUrl resolves to a page on this site. It is the same
URL whether the error surfaced in your CI, in a log line, or inside an
agent's tool call.
251 rules are implemented and documented, across EN 16931 core, the German XRechnung CIUS and Peppol BIS 3, and every one of them is a rule your own invoice can break. A further 40 constrain the arithmetic this library performs for you — the VAT breakdown, the document totals, the line net amount — and no input can reach them, so they are counted separately and their pages say plainly that a finding there would be our bug.
That is the reference set: every rule of EN 16931, the XRechnung CIUS
and Peppol BIS 3 that the JSON input model can express. What is left out
is a list rather than a fraction, and it is published in full:
validating XML you already have, credit notes, any Factur-X or CII
output, the rules the generator controls, and four the reference
schematron binds to true() and therefore does not test
either. Read the list before you commit to us.
Browse the reference too: each page says where our
check is stricter or looser than the published one, so you can calibrate
against us instead of guessing. Ask any compliance vendor for their
equivalent list.
Or skip the install and call the API
Same engine, behind an HTTP endpoint, for when you would rather not run validation inside your own stack. Post an invoice, get back the XML or the teaching errors. The free tier is open right now: 100 documents a month, no card, one key per email address. Paid plans are $49 for 2,500/mo and $199 for 25,000/mo. The pricing page says which one fits you and what happens when you hit a limit.
curl -X POST https://api.attestwire.com/v1/keys \
-H 'content-type: application/json' \
-d '{"email":"you@example.com"}'curl -X POST https://api.attestwire.com/v1/validate \
-H "authorization: Bearer $ATTESTWIRE_KEY" \
-H 'content-type: application/json' \
-d @invoice.json
That answers with
{ "valid": false, "profile": "xrechnung-ubl", "errors": [ … ],
"warnings": [] }, and the objects in errors are the
same TeachingErrors as the library's: same
rule, field, message,
fix, example, xpath,
docsUrl. The API passes your payload to the same function
you would call locally and returns its result verbatim. The hosted API
is not a different product; it is the same code behind an HTTP endpoint.
Full reference, the invoice model and the error catalogue: https://api.attestwire.com/docs.
| Plan | Documents / month | Price | Which tier |
|---|---|---|---|
| Free | 100 | $0 | enough to wire the integration up and run a real month of a side project. |
| Starter | 2,500 | $49/mo | 2,500 docs ≈ monthly invoicing for ~80 SMB customers. |
| Scale | 25,000 | $199/mo | 25,000 docs ≈ a mid-size billing platform issuing ~800 invoices a day. |
The details, so nothing surprises you: quota is documents per calendar
month, counted per key, resetting at 00:00 UTC on the 1st. A refused
call does not consume a document. Upgrades run through Stripe-hosted
Checkout: POST /v1/billing/checkout returns the URL to
redirect to. Cancel any time in the
billing portal, effective at
period end. Full pricing and FAQ.
Three things to read before you integrate. Invoices are processed in memory and never stored (security). The terms spell out that this is a technical conformance check and not a compliance guarantee. And a GDPR Art. 28 DPA is available: email us your company details and it is in place.
Need more than 100 a month?
Upgrade yourself on the pricing page: one form, your API key, done. Above 25,000, email hello@attestwire.com and a person answers.
If Attestwire stops, your build doesn't
The library is MIT-licensed and on npm. The version you depend on keeps working offline, with no account and no calls home, and anyone can fork it. The hosted API runs the same code, so migrating off it is swapping an HTTP call for a function call. If the API is ever discontinued, every account gets at least 60 days' notice, and that is in the terms rather than a promise on a page. More on how this is built and run → about Attestwire.
Where Attestwire stops and your access point starts
Attestwire doesn't transmit invoices, and won't. No Peppol access point, no French PDP, no KSeF connection — that is a regulated, capital-intensive business with different economics. We build and check the document; your access point moves it. The library's output is shaped to hand straight to one, so this is a clean seam rather than a gap.
Two free tools you can use right now, no signup
They have plain functional names because most people arrive at them from a search engine mid-problem, not from this page.
- French E-Invoicing Checker live Look up any French company and find out whether it is registered with an approved e-invoicing platform in the official DGFiP directory. Every step is explained in English, because the directory itself is not.
-
Is VIES Down?
live
When a VAT lookup fails, this tells you whether
it was you or them. All 28 member state endpoints (27 plus Northern
Ireland), polled every five minutes, with per-country uptime, latency
and an error breakdown. Free badges and open JSON at
/api/status.json, with no key. It exists because we needed it, and it is unrelated to buying anything. To be clear about what it is not: it does not validate VAT numbers for you, it tells you whether VIES can. Our own uptime is at status.attestwire.com.
The dates you're building against
Here is the calendar, so you can size the work. It is a calendar, not a countdown; nobody here benefits from you panicking in August. France is the near one: from 1 September 2026 every French VAT-registered business has to be able to receive e-invoices, with no size test, and issuing phases in for large and mid-size firms from the same date. If you are on the receiving side, Attestwire validates Factur-X and XRechnung CII today, and the free French E-Invoicing Checker tells you whether a given customer is registered on an approved platform. Verify any of these against the primary source. These dates have moved before, and they may again.
| Where | When | What changes |
|---|---|---|
| France | 1 September 2026 | Every French VAT-registered business must be able to receive e-invoices. No size test. Issuing phases in from the same date for large and mid-size firms, 2027 for the rest. |
| Germany | 2027 and 2028 | Receiving domestic B2B e-invoices has been mandatory since January 2025. Issuing starts 1 January 2027 above €800,000 prior-year turnover, 1 January 2028 for everyone else. |
| Poland | Live | KSeF has been mandatory since 1 February 2026 for taxpayers over PLN 200m and 1 April 2026 for the rest. It uses the national FA(3) schema, not EN 16931 — a separate mapping. |
| United Kingdom | 1 April 2029 | Announced at the Autumn Budget 2025: structured e-invoicing for VAT invoices, B2B and B2G, with technical detail due in the 2026 roadmap. |