If you send from the United Kingdom, Ireland or Germany and you build UBL with this SDK, upgrade. Documents you generated before 4.4.0 were rejected by the network, and the two causes are independent — fixing one alone was not enough.
A scheme has two names, and one of them contains a colon
The official Peppol code list publishes every scheme twice: under its numeric
EAS code (9932) and under a symbolic form (GB:VAT). They are two names for
the same scheme. 98 of the 105 entries contain a colon in that symbolic
form — the same character that separates a scheme from its value.
So GB:VAT:123456789 cannot be split by position, and we were splitting it by
position:
GB:VAT:123456789 → schemeID="GB" value="VAT:123456789" ⛔ both wrong
GB is not in the CEF EAS code list, so BR-CL-25 failed and the document was
refused. This was not an exotic input: GB:VAT is the only code our own
SCHEMES_BY_COUNTRY table offers for the United Kingdom. Anyone following our
guidance hit it.
Both spellings are now accepted anywhere a peppolId is taken, and always
resolved to the numeric code the network requires. The same flaw existed
independently in directory.lookup(), which looked participants up under the
wrong scheme; it is fixed too.
// All four are now equivalent — and all four produce schemeID="9932"
{ peppolId: "9932:123456789" }
{ peppolId: "GB:VAT:123456789" }
{ peppolId: "gb:vat:123456789" }
{ peppolId: " GB:VAT:123456789" }
The second cause: two neighbouring fields, two different code lists
Correcting the split still left those documents rejected, on a different rule.
BR-CL-25 governs cbc:EndpointID/@schemeID and judges against the CEF EAS
list. BR-CL-10 governs cac:PartyIdentification/cbc:ID/@schemeID and judges
against the ISO 6523 ICD list. They are not the same list, and they do not
nest: 9932 (UK), 9935 (Ireland) and 9930 (Germany) are legal in the
first and fatal in the second. We were writing the same scheme into both.
That field is optional, so it is now omitted when the scheme cannot legally
carry it. ⚠️ Clearing BR-CL-10 is not the same as being conformant: dropping
the seller identifier (BT-29) means BR-CO-26 now needs vatNumber (BT-31) or
companyId (BT-30) to be present. validate() warns when neither is.
This one is worth dwelling on if you write your own tests: it hit the numeric spelling too, so no amount of testing different spellings would have surfaced it. Only running the builder's output through the real schematron did.
If you parse Peppol IDs yourself
Don't split on the first colon — and don't split on the last one either, since
nothing guarantees a value has no colon. Resolve the prefix against the code
list. parsePeppolId is now exported for exactly this:
import { parsePeppolId } from "@getpeppr/sdk";
parsePeppolId("GB:VAT:123456789"); // { scheme: "9932", id: "123456789" }
parsePeppolId("DE:LWID:04011000"); // { scheme: "0204", id: "04011000" }
parsePeppolId("0208:0685660237"); // { scheme: "0208", id: "0685660237" }
Also exported: canonicalScheme, lookupCanonicalScheme, ISO6523_ICD_CODES
and isIso6523IcdCode.
Stricter input validation
A malformed peppolId is now a validation error on all three places that take
one — buyer, seller and payee — instead of silently producing an invalid
schemeID. That includes the case worth naming: the scheme code copied
without its identifier ("GB:VAT", "DE:LWID"), which is exactly what our
own guided-options table displays.
Not affected
Documents sent as JSON through POST /v1/invoices were never affected — the
gateway builds that UBL itself. Nor was POST /v1/invoices/import, where you
supply your own document. This fix concerns buildInvoiceXml,
buildCreditNoteXml, toXml() and directory.lookup().