Skip to content
Back to News
Changelog

SDK 3.2.0 and CLI 0.6.0 — VAT categories L and M are refused before you send

sdkpeppol

Two VAT category codes have been listed as usable for months and cannot be delivered. L (IGIC, Canary Islands) and M (IPSI, Ceuta & Melilla) are valid under EN 16931 — read the standard and you will reasonably conclude they work — but our provider has no vocabulary for them, so a document carrying one could never reach its recipient.

The gateway now refuses them with a 422 unsupported_vat_category. This release makes the SDK and the CLI say so before the send, so you find out at validate time rather than at the rejection.

The sendable list

import { SENDABLE_VAT_CATEGORIES } from "@getpeppr/sdk";
// ["S", "Z", "E", "AE", "K", "G", "O"]

Alongside it, VALID_VAT_CATEGORIES holds the ten codes EN 16931 allows. The two lists are deliberately separate, because "is this a VAT category?" and "can we deliver it?" are different questions — and answering the second with the first is what produced the wrong error message until now.

getVatCategories() entries carry a matching sendable: boolean, so you can filter the catalogue for a dropdown without hard-coding the list.

One code moves the other way

B (Italian split payment) is accepted by the Peppol network, and our validator called it invalid. That is the more expensive direction of error: telling a developer the network refuses a code it accepts closes a corridor that is open. B is now recognised as a real category, and separately reported as one we cannot route.

Two findings instead of one

validateSchematron used to report a single verdict for anything it did not like. It now distinguishes:

⚠️ The rule id PEPPOL-EN16931-R006 is gone. If you filter violations on that string, update the filter. It is not a deprecation with a grace period: the id never named a real rule — that Peppol family stops at R005, R007 and R008 in rulebook v3.0.20, and R006 does not exist. We were citing a rule you could not look up.

Two smaller fixes worth knowing

A vatCategory of null now counts as absent, exactly like undefined. The gateway has always accepted it; the validator reported "null" is not a VAT category code for payloads that send fine — the shape any JSON serialiser emits for an unset field.

And vatCategory is case-sensitive, which is now documented on the type. "AE" is reverse charge; "ae" is not a category. This one is load-bearing: "ae" used to be coerced to standard rate, shipping a valid-looking invoice that never shifted the VAT liability to the buyer.

The CLI catches up

@getpeppr/cli 0.6.0 rebundles this SDK. That matters more than a version number: getpeppr validate runs the validator locally from the bundled SDK, so 0.5.4 was still passing L and M in silence, still calling B invalid, and still citing PEPPOL-EN16931-R006.

It is a minor rather than a patch because documents that used to pass now fail — if you gate a pipeline on getpeppr validate's exit code, its verdict changes. That is the intended outcome: those documents were being refused at send time anyway.

Upgrading

npm install @getpeppr/sdk@3.2.0
npm install -g @getpeppr/cli@0.6.0

L and M remain in the vatCategory type so existing code still compiles, with the warning carried in the doc comment. They will be removed in the next major release.


Update, 2026-08-17. That last sentence did not hold. SDK 4.0.0 is the next major release and it shipped with L and M still in the type. Nothing about their behaviour changed — they are still unroutable and still refused with a 422 unsupported_vat_category — but the removal was not bundled into an unrelated breaking change. Treat it as pending, not done. See the 4.0.0 notes.