# BR-CL-23: Use a UN/ECE Recommendation 20 code in unitCode

Every `unitCode` on a line quantity or price base quantity must be a UN/ECE Rec 20 or Rec 21 code, in upper case. Local abbreviations such as `EACH` fail.

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

## The short answer

`BR-CL-23` fails when a `unitCode` attribute holds something that is not on the unit of measure code list shipped with the validation artefacts. Replace your local unit abbreviation with the UN/ECE Recommendation 20 code that means the same thing: `KGM` for kilograms, `HUR` for hours, `C62`, `H87` or `EA` for countable items.

The finding location names the quantity element with the bad code. If you change the unit on the line quantity, change it on `cac:Price/cbc:BaseQuantity` too, because Peppol requires the two to agree.

## What the rule checks

The rule runs on `cbc:InvoicedQuantity`, `cbc:CreditedQuantity` and `cbc:BaseQuantity`, each time the element has a `unitCode` attribute, and looks the value up in a fixed list. That list combines UN/ECE Recommendation 20, the units of measure, with Recommendation 21, the package types, whose codes are written with an `X` in front: `XPK` is accepted and a bare `PK` is not.

The match is exact and case-sensitive. `c62` and `h87` fail where `C62` and `H87` pass. Abbreviations that look plausible but are not on the list also fail: `EACH`, `PCE`, `pcs`, `KG` and `HR` are all rejected. An empty `unitCode=""` fails as well.

Each quantity is a separate finding. A line with the same bad code on the quantity and on the price base quantity reports the rule twice.

A quantity with no `unitCode` attribute at all is outside this rule, and the UBL schema allows it. On the line quantity the omission is reported by `BR-23` instead. On `cbc:BaseQuantity` the attribute can be left out without any finding.

| Term | Meaning | UBL element |
|---|---|---|
| BT-130 | Invoiced quantity unit of measure code | `cac:InvoiceLine/cbc:InvoicedQuantity/@unitCode` |
| BT-149 | Item price base quantity: its unit of measure code attribute | `cac:InvoiceLine/cac:Price/cbc:BaseQuantity/@unitCode` |

## How an integration ends up here

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

- The unit is copied straight from the product master, which uses the abbreviations of the ERP or the warehouse and not UN/ECE codes.
- A mapping table exists but covers only the units in use when it was written, and a new unit falls through unmapped.
- Codes pass through a step that lower-cases them.
- The system uses a widely seen abbreviation such as `PCE` on the assumption that it is a standard code, and it is not on this list.
- Package types are sent without the `X` prefix that marks them as Recommendation 21 codes.
- Service lines have no unit in the source, and a made-up default is written to fill the attribute.

## How to fix it

1. List the distinct units your system can put on an invoice line.
2. Map each one to the Recommendation 20 code with the same meaning, or to the `X`-prefixed Recommendation 21 code for a package type. Keep the mapping as data so a new unit is a new row and not a code change.
3. Do not collapse unknown units into `C62`. The unit is part of what was sold; a quantity of 2 in kilograms and 2 of one item are different statements.
4. Write the code in upper case to `unitCode` on the line quantity, and the same code to `cac:Price/cbc:BaseQuantity` when that element is present.
5. Fail the export for a unit with no mapping so the gap is found when the unit is introduced.

## Before and after

These are fragments, not complete documents. The complete synthetic documents they come from are linked below.

Line of the failing invoice: EACH is not a Recommendation 20 code

```xml
<cac:InvoiceLine>
  <cbc:ID>1</cbc:ID>
  <cbc:InvoicedQuantity unitCode="EACH">2</cbc:InvoicedQuantity>
  <cbc:LineExtensionAmount currencyID="GBP">25.00</cbc:LineExtensionAmount>
  <!-- item omitted from this fragment -->
  <cac:Price>
    <cbc:PriceAmount currencyID="GBP">12.5</cbc:PriceAmount>
    <cbc:BaseQuantity unitCode="C62">1</cbc:BaseQuantity>
  </cac:Price>
</cac:InvoiceLine>
```

Line of the corrected invoice: C62 on both quantities

```xml
<cac:InvoiceLine>
  <cbc:ID>1</cbc:ID>
  <cbc:InvoicedQuantity unitCode="C62">2</cbc:InvoicedQuantity>
  <cbc:LineExtensionAmount currencyID="GBP">25.00</cbc:LineExtensionAmount>
  <!-- item omitted from this fragment -->
  <cac:Price>
    <cbc:PriceAmount currencyID="GBP">12.5</cbc:PriceAmount>
    <cbc:BaseQuantity unitCode="C62">1</cbc:BaseQuantity>
  </cac:Price>
</cac:InvoiceLine>
```

Only the `unitCode` of `cbc:InvoicedQuantity` differs: `EACH` in the failing invoice, `C62` in the corrected one. The failing document also reports `PEPPOL-EN16931-R130`, located at `cbc:BaseQuantity`: that Peppol rule requires the price base quantity to use the same unit as the line quantity, and `C62` is not `EACH`. It is a consequence of changing one attribute and not the other. With `EACH` on both quantities the document reports `BR-CL-23` twice and `PEPPOL-EN16931-R130` not at all.

### What the validator reported

- The failing invoice reports **BR-CL-23** and [PEPPOL-EN16931-R130](https://ironfang.uk/docs/finance/rules/PEPPOL-EN16931-R130.md). The corrected document passes every layer with no findings.
  - [Download the failing XML](https://ironfang.uk/finance/rule-examples/BR-CL-23-invalid.xml)
  - [Download the corrected XML](https://ironfang.uk/finance/rule-examples/invoice-minimal.xml)
- The failing credit note reports **BR-CL-23** and [PEPPOL-EN16931-R130](https://ironfang.uk/docs/finance/rules/PEPPOL-EN16931-R130.md). The corrected document passes every layer with no findings.
  - [Download the failing XML](https://ironfang.uk/finance/rule-examples/BR-CL-23-credit-note-invalid.xml)
  - [Download the corrected XML](https://ironfang.uk/finance/rule-examples/credit_note-minimal.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

- The line quantity is named differently in the two document types: `cac:InvoiceLine/cbc:InvoicedQuantity` in an `Invoice`, `cac:CreditNoteLine/cbc:CreditedQuantity` in a `CreditNote`. `cac:Price/cbc:BaseQuantity` is the same in both. The recorded credit note with `unitCode="EACH"` on `cbc:CreditedQuantity` and `C62` on the base quantity reports the same two rules as the invoice.
- A mapper that builds credit notes by renaming invoice elements has to rename the quantity as well: a `cbc:InvoicedQuantity` inside a `cac:CreditNoteLine` is rejected by the XSD layer. Once the element is right, the code list and the behaviour of this rule are identical.
- The code list is the one published with the pinned EN 16931 artefacts. Look codes up in that list or in the UN/ECE recommendations themselves; this page does not reproduce them.

## Related rules

- [PEPPOL-EN16931-R121 checks that the price base quantity carrying this unit is greater than zero](https://ironfang.uk/docs/finance/rules/PEPPOL-EN16931-R121.md)
- [PEPPOL-EN16931-R120 checks the line net amount calculated from the quantity and price](https://ironfang.uk/docs/finance/rules/PEPPOL-EN16931-R120.md)
- [BR-CL-04 is the equivalent code list check for the invoice currency](https://ironfang.uk/docs/finance/rules/BR-CL-04.md)
- [BR-25 requires an item name on the same line](https://ironfang.uk/docs/finance/rules/BR-25.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-20.

[The official definition of BR-CL-23](https://docs.peppol.eu/poacc/billing/3.0/rules/ubl-tc434/BR-CL-23/) 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-CL-23)
- [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
