The short answer
BR-O-10 fails when the cac:TaxCategory of an O VAT breakdown has no cbc:TaxExemptionReasonCode and no cbc:TaxExemptionReason. Add VATEX-EU-O as the code, Not subject to VAT or the equivalent standard text in the language of the invoice, or both; the corrected invoice sends both.
There is no rate in an O breakdown to anchor on. The reason elements come straight after cbc:ID and before cac:TaxScheme.
What the rule checks
Each cac:TaxCategory with cbc:ID of O and scheme VAT inside cac:TaxTotal/cac:TaxSubtotal is tested on its own, and a finding points at that element. A reason code child or a reason text child satisfies it.
One of the two is plenty. When tried, the corrected invoice stayed valid with only VATEX-EU-O, and again with only the text.
Wording is not inspected. The text Outside the scope of VAT passed, and so did the unrelated code VATEX-EU-132, with no finding on any layer.
Peppol ties some codes to their own categories. On an O breakdown, VATEX-EU-IC reported PEPPOL-EN16931-P0106, VATEX-EU-G reported PEPPOL-EN16931-P0104 and VATEX-EU-AE reported PEPPOL-EN16931-P0107 when tried; VATEX-EU-O on an exempt breakdown reported PEPPOL-EN16931-P0105.
An empty cbc:TaxExemptionReason gets past this rule and is caught by PEPPOL-EN16931-R008, which is what happened when tried.
| Term | Meaning | UBL element |
|---|---|---|
| BT-118 | VAT category code | cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:ID |
| BT-120 | VAT exemption reason text | cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:TaxExemptionReason |
| BT-121 | VAT exemption reason code | cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:TaxExemptionReasonCode |
How an integration ends up here
Possible causes, from the shape of the rule rather than from measured usage:
- Reason fields are generated only for exempt breakdowns, and
Ogoes through a separate branch that writes the category and nothing more. - The source system gives out-of-scope amounts no tax code, so there is nowhere to hold a reason.
- The
Obreakdown was modelled on the Zero rated one, which carries no reason, and the reason fields were never added. - The reason is recorded against the line in the source data and not carried into the breakdown.
How to fix it
- Confirm that the amounts are really outside the scope of VAT and that the whole document is, since
Ocannot share a document with other categories. - In the
cac:TaxCategoryof theObreakdown, addcbc:TaxExemptionReasonCodewithVATEX-EU-O,cbc:TaxExemptionReasonwithNot subject to VATor its standard translation, or both. - Put them after
cbc:IDand beforecac:TaxScheme, with the code ahead of the text. AnObreakdown needs nocbc:Percentto sit between them. - Attach the reason to the
Ocategory in the mapping, so that every not subject to VAT breakdown is written with it.
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 O breakdown holds only the category and the scheme
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="GBP">25.00</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="GBP">0.00</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>O</cbc:ID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>Fragment of the corrected invoice: code and text between the category and the scheme
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="GBP">25.00</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="GBP">0.00</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>O</cbc:ID>
<cbc:TaxExemptionReasonCode>VATEX-EU-O</cbc:TaxExemptionReasonCode>
<cbc:TaxExemptionReason>Not subject to VAT</cbc:TaxExemptionReason>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>The corrected invoice carries cbc:TaxExemptionReasonCode VATEX-EU-O and cbc:TaxExemptionReason Not subject to VAT in the O tax category; in the failing invoice that category holds only cbc:ID and cac:TaxScheme. The rest of the two documents is identical, and BR-O-10 is the one finding the failing document reports.
What the validator reported
- The failing invoice reports BR-O-10. 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 UBL
InvoiceandCreditNote. Rewritten as a credit note, the failing invoice reported this rule at the same breakdown when tried. - Only
Obreakdowns under theVATscheme are in scope.BR-E-10,BR-AE-10,BR-IC-10andBR-G-10are the matching rules for exempt, reverse-charge, intra-community and export breakdowns. - A reason on the breakdown does not settle the other
Orules: the document must still have no VAT identifiers underBR-O-02and no other categories underBR-O-11.
Related rules
- BR-E-10 is the reason rule for exempt breakdowns, where the wording has to name the exemption
- BR-IC-10 is the reason rule for intra-community supply breakdowns
- BR-CL-22 rejects a reason code that is not in the VATEX list
- BR-O-11 keeps every other VAT category out of a document that has an O breakdown
- 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-O-10 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.
