The short answer
BR-CO-22 fails when a root-level cac:AllowanceCharge marked as a charge, with cbc:ChargeIndicator of true, does not say what the charge is for. Add cbc:AllowanceChargeReasonCode with the UNTDID 7161 code for the service being charged, cbc:AllowanceChargeReason with a description, or both.
Each charge without a reason is reported twice: once as BR-CO-22 and once as BR-38, which applies the identical test. They share one fix.
What the rule checks
Only cac:AllowanceCharge elements that are direct children of the Invoice or CreditNote root and are flagged as charges are examined. Each must contain at least one of the two reason elements.
A code without text passes, and so does text without a code: both variants of the corrected invoice were accepted when tried.
Presence is all this rule looks for. An empty cbc:AllowanceChargeReasonCode got past it when tried, but was then reported by BR-CL-20, PEPPOL-EN16931-CL003 and PEPPOL-EN16931-R008.
Charges on a line have their own pair of rules, BR-CO-24 and BR-44. Removing the reason text from the line charge of the rich invoice reported those two, not BR-CO-22.
The wording of the text is never judged against the code. BR-CO-06, which exists for that purpose, has a test that always passes in this release.
| Term | Meaning | UBL element |
|---|---|---|
| BG-21 | Document level charges | cac:AllowanceCharge[cbc:ChargeIndicator = true] |
| BT-104 | Document level charge reason | cac:AllowanceCharge/cbc:AllowanceChargeReason |
| BT-105 | Document level charge reason code | cac:AllowanceCharge/cbc:AllowanceChargeReasonCode |
How an integration ends up here
Possible causes, from the shape of the rule rather than from measured usage:
- Freight or handling is added as a fixed fee by a shipping module that stores an amount and a VAT category but no service type.
- The charge description lives in the printed layout template and is not part of the data passed to the XML mapping.
- A surcharge is applied automatically, for example on small orders, by a pricing rule that has no name to pass on.
- Blank reason fields are dropped by the serialiser, and the reason was blank on this charge.
How to fix it
- Locate the charge from the finding. In the recorded example it is
cac:AllowanceCharge[2], because the index counts allowances and charges under the root together. - Establish what service or cost the charge recovers, from the order, the shipping record or the pricing rule that added it.
- Look the service up in the UNTDID 7161 charge list that Peppol publishes;
FCis Freight service,DLis Delivery andPCis Packing, for example. Write the matching code incbc:AllowanceChargeReasonCode, and if nothing fits, sendcbc:AllowanceChargeReasontext alone. - Place the code straight after
cbc:ChargeIndicatorand the text after the code, both beforecbc:MultiplierFactorNumericandcbc:Amount, or the XSD layer rejects the document. - Record the reason where the charge is created in the source system, so later documents do not repeat the gap.
Validate your corrected invoice
Before and after
These are fragments, not complete documents. The complete synthetic documents they come from are linked below.
Fragment of the failing invoice: a 10 per cent document charge with no reason code and no reason text
<cac:AllowanceCharge>
<cbc:ChargeIndicator>true</cbc:ChargeIndicator>
<cbc:MultiplierFactorNumeric>10</cbc:MultiplierFactorNumeric>
<cbc:Amount currencyID="GBP">3.50</cbc:Amount>
<cbc:BaseAmount currencyID="GBP">35.00</cbc:BaseAmount>
<!-- tax category omitted from this fragment -->
</cac:AllowanceCharge>Fragment of the corrected invoice: the same charge with reason code CG and a reason text
<cac:AllowanceCharge>
<cbc:ChargeIndicator>true</cbc:ChargeIndicator>
<cbc:AllowanceChargeReasonCode>CG</cbc:AllowanceChargeReasonCode>
<cbc:AllowanceChargeReason>Example document charge</cbc:AllowanceChargeReason>
<cbc:MultiplierFactorNumeric>10</cbc:MultiplierFactorNumeric>
<cbc:Amount currencyID="GBP">3.50</cbc:Amount>
<cbc:BaseAmount currencyID="GBP">35.00</cbc:BaseAmount>
<!-- tax category omitted from this fragment -->
</cac:AllowanceCharge>Two elements are missing from the document charge in the failing invoice: cbc:AllowanceChargeReasonCode with CG and cbc:AllowanceChargeReason with Example document charge. The failing document reports BR-38 beside BR-CO-22 because the two rules test the same thing on the same charge, so restoring the reason clears both. CG means Cleaning in UNTDID 7161 and the recorded text is generic; a real charge should carry the code and wording for its own service.
What the validator reported
- The failing invoice reports
BR-38and BR-CO-22. The corrected document passes every layer with no findings.Download the failing XMLDownload the corrected XML
Recorded on phive 12.1.0 / phive-rules-peppol 4.5.6 / Saxon-HE 12.10, the engine behind the free validator, using synthetic data. A recorded result is regression evidence for these documents; it is not a certification.
Where it applies
- Applies to
InvoiceandCreditNote. A credit note built the same way reportedBR-38andBR-CO-22on the same charge when tried. - The EN 16931 layer reads
cbc:ChargeIndicatoras a boolean. A charge written with1instead oftruestill counts as a charge here: when tried without a reason, it reported this rule andBR-38, alongsidePEPPOL-EN16931-R043for the spelling. - Whether a code belongs to the charge list is a separate question, answered by
BR-CL-20.
Related rules
- BR-CO-21 is the matching requirement for document-level allowances
- BR-CL-20 checks a charge reason code against the UNTDID 7161 list
- BR-CO-12 checks that the charge amounts add up to the charge total
- PEPPOL-EN16931-R043 requires the charge indicator to be written as true or false
- Browse every rule in the reference
- Background: Understanding EN 16931 validation errors
Scope and source
Written for Peppol BIS Billing 3.0.21 (May 2026), EN 16931 1.3.16, as applied to UBL 2.1 Invoice and CreditNote documents. Other profiles, syntaxes and releases can define this identifier differently. Guidance version 2026-09-24.1: source checked 2026-09-24, explanation last updated 2026-09-24.
The official definition of BR-CO-22 carries the normative wording and test. This page is our explanation of it, not a copy.
Guidance does not change the engine verdict. Fixing this finding does not mean the document passes every layer, and validation does not certify legal or tax compliance or transmit a document over Peppol.
