# BR-CO-04: Add the VAT category code to every invoice line

Each line needs `cac:Item/cac:ClassifiedTaxCategory/cbc:ID` under the `VAT` tax scheme, giving the VAT category of the item.

- Layer: EN 16931
- Severity: fatal (the document is invalid)
- Topics: VAT, Lines and prices
- Official definition: https://docs.peppol.eu/poacc/billing/3.0/rules/ubl-tc434/BR-CO-04/
- Explanation last updated: 2026-09-24

## The short answer

`BR-CO-04` fails for each `cac:InvoiceLine` (or `cac:CreditNoteLine`) whose item has no `cac:ClassifiedTaxCategory` with a `cbc:ID` under the `VAT` tax scheme. Add `cbc:ID` as the first child of `cac:Item/cac:ClassifiedTaxCategory`, with the VAT category the line was taxed in: `Z` for the zero rated line in the recorded example.

A line without a category also drops out of its VAT breakdown. The breakdown still includes the line amount, so the taxable amount check for that category fails alongside.

## What the rule checks

The rule runs once per line, and the finding location is the line. It looks inside `cac:Item` for a `cac:ClassifiedTaxCategory` whose `cac:TaxScheme/cbc:ID` is `VAT`, ignoring case and surrounding spaces, and passes when that category has a `cbc:ID` element.

A category under another tax scheme does not count. When tried, a line whose category had a tax scheme of `GST` was reported by this rule.

A present but empty `cbc:ID` is not caught here. When tried, an empty code on a Standard rated line was reported by `BR-CL-18` and `PEPPOL-EN16931-R008` instead, together with `BR-S-01` and `BR-S-08`.

A line with no `cac:ClassifiedTaxCategory` at all fails this rule too. When tried, it also reported `UBL-SR-48`, which asks for exactly one classified tax category per line.

| Term | Meaning | UBL element |
|---|---|---|
| BG-25 | Invoice line | `cac:InvoiceLine (cac:CreditNoteLine in a credit note)` |
| BT-151 | Invoiced item VAT category code | `cac:InvoiceLine/cac:Item/cac:ClassifiedTaxCategory/cbc:ID (cac:CreditNoteLine/cac:Item/cac:ClassifiedTaxCategory/cbc:ID in a credit note)` |

## How an integration ends up here

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

- Items without a tax code in the product master are exported with an empty tax category instead of being stopped.
- The line category is written only when it differs from a document default, so lines in the default category lose it.
- Zero rated or exempt lines are treated as carrying no VAT, and the export skips their category code.
- Lines typed in by hand, such as ad hoc services or adjustments, are created without an item tax code.

## How to fix it

1. Find the VAT category the line was actually taxed in, from its item tax code or the tax determination for that line.
2. Write it as `cbc:ID`, the first child of `cac:ClassifiedTaxCategory`, followed by `cbc:Percent` (left out for category `O`) and `cac:TaxScheme` with `cbc:ID` of `VAT`.
3. Check that the line now falls into the breakdown with the same category and rate, so that breakdown taxable amount matches its lines again.
4. Stop the export when a line has no tax code, rather than sending it without a category.

## 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 Zero rated breakdown totals 10.00, but line 2 has lost its category code

```xml
<cac:TaxSubtotal>
  <cbc:TaxableAmount currencyID="GBP">10.00</cbc:TaxableAmount>
  <cbc:TaxAmount currencyID="GBP">0.00</cbc:TaxAmount>
  <cac:TaxCategory>
    <cbc:ID>Z</cbc:ID>
    <cbc:Percent>0</cbc:Percent>
    <!-- tax scheme omitted from this fragment -->
  </cac:TaxCategory>
</cac:TaxSubtotal>

<cac:InvoiceLine>
  <cbc:ID>2</cbc:ID>
  <cbc:InvoicedQuantity unitCode="C62">2</cbc:InvoicedQuantity>
  <cbc:LineExtensionAmount currencyID="GBP">10.00</cbc:LineExtensionAmount>
  <cac:Item>
    <cbc:Name>Example zero-rated item</cbc:Name>
    <cac:ClassifiedTaxCategory>
      <cbc:Percent>0</cbc:Percent>
      <cac:TaxScheme>
        <cbc:ID>VAT</cbc:ID>
      </cac:TaxScheme>
    </cac:ClassifiedTaxCategory>
  </cac:Item>
  <!-- price omitted from this fragment -->
</cac:InvoiceLine>
```

Fragment of the corrected invoice: line 2 is Zero rated again

```xml
<cac:InvoiceLine>
  <cbc:ID>2</cbc:ID>
  <cbc:InvoicedQuantity unitCode="C62">2</cbc:InvoicedQuantity>
  <cbc:LineExtensionAmount currencyID="GBP">10.00</cbc:LineExtensionAmount>
  <cac:Item>
    <cbc:Name>Example zero-rated item</cbc:Name>
    <cac:ClassifiedTaxCategory>
      <cbc:ID>Z</cbc:ID>
      <cbc:Percent>0</cbc:Percent>
      <cac:TaxScheme>
        <cbc:ID>VAT</cbc:ID>
      </cac:TaxScheme>
    </cac:ClassifiedTaxCategory>
  </cac:Item>
  <!-- price omitted from this fragment -->
</cac:InvoiceLine>
```

The corrected invoice has `<cbc:ID>Z</cbc:ID>` at the start of line 2's `cac:ClassifiedTaxCategory`; nothing else differs. The failing document also reports `BR-Z-08`, which requires the Zero rated breakdown taxable amount to equal the Zero rated line amounts. With line 2 uncategorised the document has no Zero rated lines, so the expected figure is 0.00 against the 10.00 in the breakdown. `BR-Z-01` stays silent, because it does not object to a Zero rated breakdown with nothing Zero rated behind it. Restoring the code clears both findings.

### What the validator reported

- The failing invoice reports **BR-CO-04** and `BR-Z-08`. The corrected document passes every layer with no findings.
  - [Download the failing XML](https://ironfang.uk/finance/rule-examples/BR-CO-04-invalid.xml)
  - [Download the corrected XML](https://ironfang.uk/finance/rule-examples/invoice-rich.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 `Invoice` and `CreditNote`. In a credit note the line is `cac:CreditNoteLine`; with the code removed from its only, Standard rated, line it reported this rule with `BR-S-01` and `BR-S-08` when tried.
- Which companions appear depends on the category the line lost. A Standard rated line leaves the Standard rated breakdown with nothing behind it, which `BR-S-01` and `BR-S-08` both catch; the Zero rated line here brings only `BR-Z-08`.
- Reported by the EN 16931 layer. Once a code is present, `BR-CL-18` checks that it is a permitted category code.
- Document-level allowances and charges carry their category in `cac:AllowanceCharge/cac:TaxCategory`, which this rule does not read; `BR-32` covers allowances.

## Related rules

- [BR-CL-18 checks that the line category code is one of the permitted codes](https://ironfang.uk/docs/finance/rules/BR-CL-18.md)
- [BR-47 is the same requirement for the category code of each VAT breakdown](https://ironfang.uk/docs/finance/rules/BR-47.md)
- [BR-S-01 is reported with this rule when the uncategorised line was Standard rated](https://ironfang.uk/docs/finance/rules/BR-S-01.md)
- [BR-Z-01 requires one Zero rated breakdown when Zero rated lines exist, but not the reverse](https://ironfang.uk/docs/finance/rules/BR-Z-01.md)

## 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-04](https://docs.peppol.eu/poacc/billing/3.0/rules/ubl-tc434/BR-CO-04/) 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.

## Links

- [This rule as a web page](https://ironfang.uk/docs/finance/rules/BR-CO-04)
- [Free Peppol invoice validator](https://ironfang.uk/tools/peppol-validator)
- [Rule index](https://ironfang.uk/docs/finance/rules.md)
- [Ironfang Finance API docs](https://ironfang.uk/docs/finance)
- The same rule is available to MCP clients as the tool `finance.rule.get` on https://mcp.ironfang.uk/mcp
