Two changes reach npm today, and one of them can refuse input that 5.7.0
accepted. If you call validatePeppolIdentifier anywhere, read the first
section before upgrading.
validatePeppolIdentifier enforces POLICY 1's alphabet
The Peppol Policy for use of Identifiers 4.4.0, POLICY 1, requires that a
participant identifier's value contain only letters a-z and A-Z, digits
0-9, the minus sign, the period, the underscore or the tilde. Up to 5.7.0 the
SDK refused an empty value, leading or trailing whitespace and anything over 64
characters, then applied a format pattern for the schemes it knows — which are
the minority. For every other scheme, nothing looked at which CHARACTERS the
value used.
The alphabet is now checked for every scheme, after those three guards and
before the per-scheme pattern, under both the addressing and the
registration usage. Measured on the published 5.7.0 and on this release:
validatePeppolIdentifier("GB:CRN", "AB/CD");
// 5.7.0 → { valid: true, checksumChecked: false }
// 5.8.0 → { valid: false, error: "Identifier may only contain letters (a-z, A-Z),
// digits (0-9), '-', '.', '_' or '~' (Peppol Policy for use of
// Identifiers 4.4.0, POLICY 1)." }
The set is narrower than ASCII. A space, a slash and a colon are ASCII and are refused.
Why this is a fix and not a restriction. A value outside that set cannot be
carried by the Peppol network, and accepting it was worse than merely
permissive. Participant lookup lower-cases in full Unicode, so an identifier
spelled with the Kelvin sign U+212A and its plain-ASCII twin resolve to the
same network participant — while a system that accepted both would hold them
apart as two customers. validatePeppolIdentifier("0096", "A\u212A9") returned
{ valid: true, checksumChecked: false } in 5.7.0 and now returns the same
POLICY 1 refusal as above.
One thing to check if you display the error text. When a value breaks both the alphabet and its scheme's format, the alphabet is reported first, so the message changes even though the verdict does not. A number pasted with the spaces its owner writes it with is the common case:
validatePeppolIdentifier("GB:VAT", "GB 123 4567 89");
// 5.7.0 → { valid: false, error: "Format mismatch: expected 9 or 12 digits,
// optionally prefixed GB or XI (or GD/HA + 3 digits)." }
// 5.8.0 → { valid: false, error: "Identifier may only contain letters (a-z, A-Z),
// digits (0-9), '-', '.', '_' or '~' (Peppol Policy for use of
// Identifiers 4.4.0, POLICY 1)." }
The same swap happens on 0002 with "732 829 320", on 0184 with
"1234 5678" and on 9944 with "NL 123456789B01". Every one of them was
already refused, in both versions; only the text moved.
Two cases do not change, so there is no need to over-correct. A leading or
trailing space still reports "Identifier must not have leading or trailing whitespace.", because that guard runs first. And a separator POLICY 1 allows
still reaches the pattern: ("GB:VAT", "GB-123-4567-89") returns the format
hint in both versions.
If your form showed the format hint to help someone fix a pasted value, strip
the formatting characters rather than trimming — value.replace(/\s/g, "")
removes the inner spaces that trim() leaves in place.
Nothing in production was affected. We checked 287 live identifiers across our own records before shipping this: zero POLICY 1 violations.
Find a customer by your own reference
legalEntities.list() takes an externalId filter, so you never need to have
stored our id to find a customer again:
const { data } = await peppol.legalEntities.list({ externalId: "acme-42" });
const customer = data[0]; // undefined if you never registered that one
Matched exactly — no prefix search, no case folding, no trimming. Because the
reference is unique among your live customers, the page carries at most one
entity. An unknown or archived reference gives an empty data, not an error; an
empty string, or a reference over 64 characters, is refused with 400 rather
than answered with an empty list.
listAll() accepts it too — it takes every list() option except offset, so
a filter you build once can be handed to either.
This is the SDK surface for GET /v1/legal-entities?externalId=…, already
described on the
legal entities documentation page.
CLI 0.12.3
The CLI bundles the SDK into its published artifact, so an SDK release reaches CLI users only once the CLI is rebundled. 0.12.2 still carried SDK 5.4.2; 0.12.3 carries 5.8.0.
No command, validation rule or exit code changes. validate, convert and
init do not call validatePeppolIdentifier — the alphabet gate is not in the
CLI's bundle at all — so they accept and refuse exactly what 0.12.2 did. What
changes is the SDK version the CLI reports in its User-Agent.
npm install @getpeppr/sdk@5.8.0
npm install -g @getpeppr/cli@0.12.3