Why does my Peppol invoice fail validation?

OpenPeppol's own example broken fourteen ways: the rule each mistake trips, the tolerances on amounts, and the national rules the seller's country adds

· 7 min read · Oleksii Babintsev

A Peppol invoice is checked in four layers before anyone reads it: the UBL 2.1 schema, the EN 16931 business rules, OpenPeppol's own rules, and national rules that switch on depending on the seller's country. Access points check invoices against these rules, and a finding flagged fatal means the invoice should not be sent. The error you get back is one line with a rule id, and the id says which layer complained.

To see what each layer catches, I took OpenPeppol's own base-example.xml from the Peppol BIS Billing 3.0 documentation, which passes everything, and broke it in fourteen ways, each one a mistake that is easy to make when an invoice is written by code. The rules are the current release, Peppol BIS 3.0.21 with the EN 16931 rules 1.3.16; every message below is what they report.

The identifiers at the top

The first two elements of the invoice say what it is, and Peppol checks them before anything else:

<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID>
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>

Copy them from an old template, with the Peppol BIS 2 identifier, and the whole invoice is rejected on one rule:

PEPPOL-EN16931-R004  Specification identifier MUST begin with the value 'urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0' and follow the format rules for the identifier.

National profiles (XRechnung, SI-UBL, EHF) use their own CustomizationID. If the receiver expects one of those, they check the invoice against that profile's rules, not plain Peppol's.

References and addresses

The buyer needs some way to match the invoice to its own records, so Peppol wants either a buyer reference or an order reference. Remove cbc:BuyerReference from the example:

PEPPOL-EN16931-R003  A buyer reference or purchase order reference MUST be provided.

Both parties also need an electronic address, the cbc:EndpointID the network routes by. Leave out the buyer's:

PEPPOL-EN16931-R010  Buyer electronic address MUST be provided

The address's schemeID is a code from the EAS list, not a name. Writing GLN instead of 0088 fails twice, once in EN 16931 and once in Peppol:

BR-CL-25              Endpoint identifier scheme identifier MUST belong to the CEF EAS code list
PEPPOL-EN16931-CL008  Electronic address identifier scheme must be from the codelist "Electronic Address Identifier Scheme"

Some schemes have check digits, and Peppol verifies them. A Belgian enterprise number (0208) is ten digits whose last two are 97 minus the first eight modulo 97. 0123456789 has the right length and the wrong check digits:

PEPPOL-COMMON-R043  Belgian enterprise number MUST be stated in the correct format.

0123456749 passes. Since 1 January 2026 Belgian businesses invoice each other on Peppol, so this one has become common.

Empty elements

A template that writes every field, filled or not, produces elements like <cbc:AccountingCost></cbc:AccountingCost>. The schema allows them. Peppol does not:

PEPPOL-EN16931-R008  Document MUST not contain empty elements.

Leave the element out instead. This is the most mechanical rule in the set and one of the easiest to hit when XML is filled from a form.

Codes

Everything coded comes from a fixed list. The unit of measure is UN/ECE Recommendation 20, so pcs is not a unit, H87 (piece) or C62 (one) is:

BR-CL-23  Unit code MUST be coded according to the UN/ECE Recommendation 20 with Rec 21 extension

VAT numbers start with their country code. A Swedish VAT number written without SE:

BR-CO-09  The Seller VAT identifier (BT-31), the Seller tax representative VAT identifier (BT-63) and the Buyer VAT identifier (BT-48) shall have a prefix in accordance with ISO code ISO 3166-1 alpha-2 by which the country of issue may be identified. Nevertheless, Greece may use the prefix 'EL'.

Amounts

Amounts have at most two decimals. A charge of 25.000 is the same number as 25, but it is not allowed:

BR-DEC-05  The allowed maximum number of decimals for the Document level charge amount (BT-99) is 2.
UBL-DT-01  Amounts shall be decimal up to two fraction digits

This often comes from a number formatter set to three places for prices. Prices (cbc:PriceAmount) may have more decimals; amounts may not.

The arithmetic is where one mistake turns into several findings. In the example, line 1 is 7 days at 400.00, so its net amount is 2800.00. Write 2700.00 and leave everything else alone:

PEPPOL-EN16931-R120  Invoice line net amount MUST equal (Invoiced quantity * (Item net price/item price base quantity) + Sum of invoice line charge amount - sum of invoice line allowance amount
BR-CO-10             Sum of Invoice line net amount (BT-106) = Σ Invoice line net amount (BT-131).
BR-S-08              For each different value of VAT category rate (BT-119) where the VAT category code (BT-118) is "Standard rated", the VAT category taxable amount (BT-116) in a VAT breakdown (BG-23) shall equal the sum of Invoice line net amounts (BT-131) ...

Three errors, one cause. The line no longer matches its quantity and price, and the totals no longer match the lines. Fix the line, not the totals; the other two go away with it.

The tolerances are worth knowing, because they are not the same everywhere:

  • PEPPOL-EN16931-R120 allows a line to be off by 0.02 from quantity × price. A line of 2799.97 fails; 2799.98 passes.
  • BR-CO-17, the VAT amount of a category against its taxable amount × rate, accepts anything less than one whole unit of the currency away. Rounding VAT per line and adding the results up is fine.
  • BR-CO-10, BR-CO-15 and the other sums of the document's own amounts have no tolerance at all. The totals must add up exactly.

VAT categories

A 0 % rate with category S (standard rated) is a contradiction, and the rules say so for every line and charge that has it:

BR-S-05  In an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "Standard rated" the Invoiced item VAT rate (BT-152) shall be greater than zero.
BR-S-07  In a Document level charge (BG-21) where the Document level charge VAT category code (BT-102) is "Standard rated" the Document level charge VAT rate (BT-103) shall be greater than zero.

A rate of zero needs the category that explains it: Z for zero rated goods, E for exempt (with an exemption reason), AE for reverse charge (with both parties' VAT numbers), K for intra-community supplies, G for exports. With Z the same invoice passes. Each category has its own block of rules, BR-Z-*, BR-E-*, BR-AE-* and so on, with the requirements that come with it.

Dates

xs:date is YYYY-MM-DD. A due date of 01/12/2017 is stopped by the schema before any rule runs:

Element 'cbc:DueDate': '01/12/2017' is not a valid value of the atomic type 'xs:date'.

Peppol repeats the check as PEPPOL-EN16931-F001 ("A date MUST be formatted YYYY-MM-DD") for tools that skip the schema.

The seller's country changes the rules

Peppol BIS includes national rules for some countries, and they apply when the seller is in that country. The example's seller is in the United Kingdom. Move it to the Netherlands, with a Dutch VAT number, and three errors appear that were not there before:

NL-R-003  For suppliers in the Netherlands, the legal entity identifier MUST be either a KVK or OIN number (schemeID 0106 or 0190)
NL-R-009  For suppliers in the Netherlands, if an order line reference (cac:OrderLineReference/cbc:LineID) is used, there must be an order reference on the document level (cac:OrderReference/cbc:ID)

(NL-R-009 fires once per line.) Move it to Norway instead and the invoice passes with a warning, NO-R-002, about the word "Foretaksregisteret". Peppol BIS 3.0.21 has such blocks for Denmark, Germany, Greece, Iceland, Italy, the Netherlands, Norway and Sweden. An invoice that validates for a seller in one country can fail for a seller in another.

Fatal and warning

Each Peppol finding carries a flag. fatal (shown as an error) means the access point must not send the invoice. warning means it goes through and the receiver may still complain. A clean validation report has no errors; warnings are worth reading but do not block anything.

A short checklist

You see Usually
PEPPOL-EN16931-R004, R007 Wrong or outdated CustomizationID, unknown ProfileID
PEPPOL-EN16931-R003 No buyer reference and no order reference
PEPPOL-EN16931-R010, R020, BR-CL-25, CL008 Electronic address missing, or its scheme written as a name
PEPPOL-COMMON-R0xx An identifier with wrong check digits or form
PEPPOL-EN16931-R008 An empty element left by a template
BR-CL-* A code that is not on its list (units, currencies, countries)
BR-DEC-*, UBL-DT-01 More than two decimals in an amount
PEPPOL-EN16931-R120 with BR-CO-10 A line that does not equal quantity × price
BR-S-*, BR-Z-*, BR-E-*, BR-AE-* VAT category and rate do not fit together
NL-R-*, DK-R-*, NO-R-*, SE-R-* ... The seller's country adds rules

Every rule id has a page with what it checks, the usual causes and a fix: PEPPOL-EN16931-R003, R008, R120, BR-CO-09, BR-S-05, and the rest from the rule index.

Check before you send

The Peppol BIS invoice entry in the catalog runs all four layers: the UBL schema, EN 16931, the Peppol rules and the national ones. Open your invoice in the validator and it picks Peppol from the CustomizationID on its own. It runs in the browser, so the invoice is not uploaded, and the results come with the line number and the path of each finding.

More from the blog