Skip to content

BR-CO-21: Give each document-level allowance a reason code or reason text

A discount or other allowance on the whole document must say why it is given, in cbc:AllowanceChargeReasonCode, cbc:AllowanceChargeReason or both.

EN 16931Fatal: the document is invalidAllowances and charges

The short answer

BR-CO-21 fails when a cac:AllowanceCharge directly under the document root, with cbc:ChargeIndicator of false, has neither cbc:AllowanceChargeReasonCode nor cbc:AllowanceChargeReason. Add the reason the allowance was granted, taken from the discount record in your source system: a code from the UNTDID 5189 allowance list, free text, or both.

BR-33 has the same test and is reported on the same allowance, so one missing reason produces two findings. Adding either element clears both.

What the rule checks

The rule visits each cac:AllowanceCharge that is a child of the Invoice or CreditNote root and whose indicator reads as false. It passes when that allowance has a reason code element, a reason text element, or both.

Either element on its own is enough. When tried, the corrected invoice with its allowance code removed, and again with its allowance text removed, passed every layer.

Only the existence of the element is tested. An empty cbc:AllowanceChargeReason, or one holding only spaces, satisfied this rule when tried, and the Peppol layer then reported it as PEPPOL-EN16931-R008.

Allowances on a line and charges at document level are not counted here; they have BR-CO-23 and BR-CO-22. The price discount inside cac:Price needs no reason at all.

Nothing compares the code with the text. BR-CO-05, the rule meant to check that both describe the same allowance, always passes in this release.

TermMeaningUBL element
BG-20Document level allowancescac:AllowanceCharge[cbc:ChargeIndicator = false]
BT-97Document level allowance reasoncac:AllowanceCharge/cbc:AllowanceChargeReason
BT-98Document level allowance reason codecac:AllowanceCharge/cbc:AllowanceChargeReasonCode

How an integration ends up here

Possible causes, from the shape of the rule rather than from measured usage:

  • The discount is stored as an amount only, with no type or description, so the mapping has nothing to write in either element.
  • The reason is mapped from an optional field such as a promotion name, which was blank for this document, and the serialiser skipped both elements.
  • The mapping writes a code only when the discount type has a known UNTDID equivalent and never writes text, so an unmapped type ends up with neither.
  • A goodwill reduction or settlement discount is added as an allowance by a later processing step that does not set a reason.

How to fix it

  1. Find the allowance from the finding location. cac:AllowanceCharge[1] is the first cac:AllowanceCharge under the root, and the index counts charges as well as allowances.
  2. Take the reason from the source record that created the allowance: the discount type, the promotion, the contract term or the rebate agreement.
  3. If that reason matches an entry in the UNTDID 5189 subset Peppol publishes, write the code in cbc:AllowanceChargeReasonCode; 95 is Discount and 100 is Special rebate. If no entry fits, send cbc:AllowanceChargeReason text alone.
  4. Keep the UBL order: cbc:ChargeIndicator, then the code, then the text, then cbc:Amount. When tried, text placed before the code, or after the amount, failed the XSD layer and the later layers were skipped.
  5. Do not emit an empty element as a placeholder. It swaps this finding for PEPPOL-EN16931-R008.

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: the document allowance of 1.00 gives no reason

<cac:AllowanceCharge>
  <cbc:ChargeIndicator>false</cbc:ChargeIndicator>
  <cbc:Amount currencyID="GBP">1.00</cbc:Amount>
  <!-- tax category omitted from this fragment -->
</cac:AllowanceCharge>

Fragment of the corrected invoice: the allowance carries reason code 95 and a reason text

<cac:AllowanceCharge>
  <cbc:ChargeIndicator>false</cbc:ChargeIndicator>
  <cbc:AllowanceChargeReasonCode>95</cbc:AllowanceChargeReasonCode>
  <cbc:AllowanceChargeReason>Example document discount</cbc:AllowanceChargeReason>
  <cbc:Amount currencyID="GBP">1.00</cbc:Amount>
  <!-- tax category omitted from this fragment -->
</cac:AllowanceCharge>

The corrected invoice has reason code 95 and the text Example document discount on its document allowance; the failing one has neither, and nothing else differs. Besides BR-CO-21, the failing document reports BR-33, an EN 16931 rule with exactly the same test on the same allowance, and the same edit clears it. The amount, VAT category and totals are untouched, so no arithmetic rule fires.

What the validator reported

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 Invoice and CreditNote alike. When tried on the credit note version of the recorded example, the same pair of findings, BR-33 and BR-CO-21, was reported.
  • The EN 16931 rules read cbc:ChargeIndicator as a boolean, so an allowance written with 0 is still in scope: when tried without a reason, it reported this rule and BR-33, plus PEPPOL-EN16931-R043 for the spelling.
  • A code that is present but not on the allowance list passes this rule and is rejected by BR-CL-19.

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-21 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.