Spec cycle · May 2026
What changed in the May 2026 release
Peppol BIS Billing 3.0.21 and KoSIT xrechnung-schematron 2.6.0, diffed rule by rule from the vendored Schematron rather than read from release notes. Short answer: nothing in this cycle changes how a conformant invoice renders. What may change is whether your documents still validate.
This is the first of a twice-yearly series: each May and November, within a few
weeks of the release, a mechanical diff of the official validation artefacts
and a plain statement of what it means for people who validate or render EN
16931 invoices. It is also late. The May release went out on
docs.peppol.eu
on 20 May 2026 and became mandatory on 17 August, but it was never tagged on
GitHub, and our release watch was reading GitHub tags. It now reads the live
artefacts. If your own tooling pins the Peppol repository by tag, it has the
same blind spot; the last tag there is still v3.0.20.
Does it touch the PDF?
No. Every change in this cycle is in the validation layer: new checks, tightened checks, code-list updates, reworded messages. Our twenty-fixture corpus validated with zero fatals and zero warnings against the new artefacts without a single fixture edit, and the rendered output of every fixture is unchanged. If your invoices validated in November, their rendering is the same today. The lists below are about whether they still validate.
Peppol BIS Billing 3.0.21
Published 20 May 2026, mandatory from 17 August 2026. The headline feature is a
new optional profile, billing with response (profile
identifier 02), for invoices that require an invoice response
message. The artefacts ship a new example for it,
base-example_profile02.xml.
Six new rules, all warnings, all national
| Rule | Checks |
|---|---|
PEPPOL-COMMON-R054 | Dutch Chamber of Commerce numbers (scheme 0106) are eight digits |
PEPPOL-COMMON-R055 | Dutch organisation identifiers (0190) are twenty digits |
PEPPOL-COMMON-R056-1, -2 | Dutch VAT numbers (9944) match NL123456789B12 |
PEPPOL-COMMON-R057 | Dutch establishment numbers (0217) are twelve digits |
DE-R-T02 | Germany to Germany: BT-124, the external document location, is an absolute URL with a valid scheme |
Fifteen rules whose test expression changed
These are the ones that can move a document from green to red:
PEPPOL-EN16931-R004, GR-R-001-3 through -7,
GR-R-002, GR-R-005, GR-R-008-3,
DE-R-017, DE-R-019, DE-R-020,
DE-R-026, PEPPOL-EN16931-P0100 and
PEPPOL-EN16931-P0101. Two deserve a closer look.
P0100 and P0101, the invoice and credit-note type-code
checks, now apply to profile 02 as well as 01. A
renderer does not care which profile an invoice declares. A validation
pipeline that keys on ProfileID ending in :01:1.0
now has a second value to accept.
R004, the customization-identifier check, now also rejects a
value containing ::. Some access points had been sending
identifiers that were malformed but technically passed the prefix test; they
no longer do.
Four severity changes and a hundred rewordings
PEPPOL-COMMON-R052, PEPPOL-COMMON-R053,
DK-R-003 and DK-R-017 changed flag. Roughly a hundred
further rules changed only in their message text: every assertion now carries
its rule identifier as a [PEPPOL-…]- prefix, matching the CEN
convention, and the German, Dutch, Italian and Greek national rules were
reworded in the same pass. Harmless to documents, loud in any validator log
you snapshot-test.
The CEN layer inside Peppol
Peppol's artefacts bundle their own copy of the EN 16931 rules, and that copy
moved too. UBL-SR-56 is new and fatal: at most
one originator document reference (BT-17). BR-CO-25
is gone. That was the rule requiring a due date or payment terms whenever the
amount due is positive, and it was the most argued-over rule in the standard.
Its removal is the single largest loosening in this cycle.
Tests changed on BR-CO-26 (a SEPA-scheme party identifier no
longer satisfies "the seller must be identifiable"), UBL-SR-23,
BR-B-02, UBL-CR-674, and the currency code lists
BR-CL-01, -03, -04, -05,
-10, -11, -21, -25 and
-26: ANG and BGN are retired,
XCG is added, following ISO 4217.
KoSIT xrechnung-schematron 2.6.0
Released 31 August 2026, compatible with XRechnung 3.0, and itself built on Peppol BIS Billing 3.0.21, so the German layer and the Peppol layer finally agree on the same base.
| Rule | Severity | Checks |
|---|---|---|
BR-DE-19, BR-DE-20 | warning | BT-84 and BT-91 hold a correct IBAN when BT-81 says SEPA (codes 58 and 59); UBL and CII |
BR-TMP-2 | warning → fatal | BT-124 is an absolute URL |
BR-TMP-6 (UBL) | warning | every date business term is YYYY-MM-DD |
BR-TMP-7 (CII) | warning | every date business term carries format="102" and is YYYYMMDD |
BR-TMP-4, BR-TMP-5 (CII) | as replaced | temporary replacements for the broken CII-SR-475 and -476 contexts until CEN fixes eInvoicing-EN16931#508 |
Code lists are aligned to CEN 1.3.16, and the IBAN pattern moved into the
shared common.sch. The date-format rules formalise what our CII
normalizer already required, so nothing in our corpus moved.
PINT A-NZ 1.1.3
The Australia and New Zealand profile republished its artefacts on the same
unversioned URL in September; the version line on the page caught up later. We
pin that zip by hash, so the republish was caught and reviewed the day it
happened: currency-list retirements, ibr-126 loosened for item
price extensions, ibr-sr-45 removed. No corpus impact.
What we shipped
Spec cycle 2026.05: image ghcr.io/faktorei/render:2026.05,
reporting spec_release 2026.05, with Peppol pinned to the commit on
OpenPeppol's 2026-Q2-QA2 branch whose artefacts are byte-identical
to the live files, and KoSIT pinned to v2.6.0. Corpus green, no
fixture edits, rendering unchanged. A weekly check now compares the live
artefacts against the pins and opens an issue on drift, so the November
release, already in OpenPeppol's QA branch, will be caught the week it lands.
The pins, the vendoring script and the upstream watch are Apache-2.0:
github.com/faktorei/stylesheets,
under specwatch/. The rule lists above come from diffing the
vendored .sch files between pins, which anyone can reproduce with
pull.py at the two refs.