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.

validateInput(invoice).errors[0] — for that same rejection
{
  "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.

npm install @attestwire/en16931
import { validateInput,
         generateXRechnungUBL }
  from "@attestwire/en16931";
const { valid, errors } =
  validateInput(invoice);
const xml =
  generateXRechnungUBL(invoice);

MIT · v0.2.0 · @attestwire/en16931 on npm

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
POST /v1/keys — the key is in the response, once
curl -sX POST \
  -H content-type:application/json \
  -d '{"email":"you@example.com"}' \
  https://api.attestwire.com/v1/keys

100 documents a month, free, no card, one key per address. Your address reaches the API host and nobody else. See privacy. Next: the API quickstart.

Read the source See pricing

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.

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.

install it
npm install @attestwire/en16931

npm · GitHub · MIT licence

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

a complete XRechnung, minus one field
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.

a key, in one call — the plaintext key is returned once, and there is no recovery flow
curl -X POST https://api.attestwire.com/v1/keys \
  -H 'content-type: application/json' \
  -d '{"email":"you@example.com"}'
and then the call you actually came for
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.

PlanDocuments / monthPriceWhich 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.

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.

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