Peppol BIS rules — what applies on top when delivery goes through Peppol
Peppol is a delivery network, not a second norm. These rules check exactly what a network needs and a file on its own does not: where it is going, which process it belongs to, and that it does not fall apart in transit.
23 rules · 23 explained · Rule set v2026-08-31
Addressing is the difference
The rules that fail most often require an electronic address for buyer and seller (BT-49 and BT-34) together with a scheme identifier. An invoice that arrives perfectly well by email or through a portal often does not carry these fields at all — it never needed them.
In Germany the Leitweg-ID is the related case on the public-sector side: the same idea, an address inside the document, but a different field and a different scheme. Confuse the two and you fill in the wrong one.
Stricter than the norm, deliberately
Several Peppol rules forbid what EN 16931 permits — empty elements, for instance. The reason is operational: a network with hundreds of access points cannot afford ambiguity, because every recipient resolves it differently.
In practice that means a file can be clean against EN 16931 and still be refused over Peppol. Validate against the profile you actually deliver through, not against the most general one.
All Peppol rules
With the official rule text. Rules with a written explanation carry their cause and fix directly beneath.
- PEPPOL-EN16931-R001
Business process MUST be provided.
- What the rule requires
- The invoice must name the business process (BT-23) — the identifier of the process it is being sent in. It tells the recipient which rule set to expect the invoice under.
- Why it fires
- The value is a constant and so gets treated as self-evident: the specification identifier (BT-24) is set and the process identifier next to it is forgotten, because the two look alike and neither comes from the invoice data.
- How to fix it
- Set BT-23 to the fixed value urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 — cbc:ProfileID in UBL, ram:BusinessProcessSpecifiedDocumentContextParameter/ram:ID in CII. It does not vary with the invoice.
- PEPPOL-EN16931-R005
VAT accounting currency code MUST be different from invoice currency code when provided.
- What the rule requires
- If a separate VAT accounting currency (BT-6) is given, it must differ from the invoice currency (BT-5). The field exists only for the case where tax has to be reported in another currency.
- Why it fires
- The mapping fills BT-6 with the same currency as BT-5 as a precaution, because an empty field looks like a gap. The invoice then claims a currency conversion that is not happening.
- How to fix it
- Omit BT-6 when tax and invoice share a currency: cbc:TaxCurrencyCode in UBL, ram:TaxCurrencyCode in CII. Set it only where the reporting currency genuinely differs.
- PEPPOL-EN16931-R008
Document MUST not contain empty elements.
- What the rule requires
- The document must contain no empty elements. A tag with no content is not an absent value; it is a claim that something was transmitted.
- Why it fires
- Mappings write fields unconditionally and set the value afterwards. Where the source is null, an empty tag is left behind — most often on note fields, references and optional identifiers that are only sometimes populated.
- How to fix it
- Write an element only once a value exists. In most serialisers that is a condition around the write call rather than a clean-up pass afterwards — stripping empty tags at the end rarely catches all of them.
- PEPPOL-EN16931-R010
Buyer electronic address MUST be provided
- PEPPOL-EN16931-R020
Seller electronic address MUST be provided
- What the rule requires
- The seller electronic address (BT-34) must be provided — the counterpart to BT-49, the address a recipient can reply or send a response message to.
- Why it fires
- The same cause as for the buyer, with an extra reason to skip it: “the recipient knows who the invoice is from”. For automatic processing the sender is an identifier exactly as the recipient is.
- How to fix it
- Set BT-34 with its scheme attribute under cac:AccountingSupplierParty/cac:Party: cbc:EndpointID with schemeID in UBL, ram:SellerTradeParty/ram:URIUniversalCommunication/ram:URIID in CII. The value is constant for your company.
- PEPPOL-EN16931-R040
Allowance/charge amount must equal base amount * percentage/100 if base amount and percentage exists
- What the rule requires
- Where both the base amount and the percentage of an allowance or charge are given, the amount must follow from them exactly: base amount × percentage ÷ 100.
- Why it fires
- The amount comes from pricing, while the base amount and percentage are added afterwards as an explanation. If pricing rounds differently — or the discount was calculated on a different base — the three numbers stop agreeing.
- How to fix it
- Compute the amount from exactly the two values present in the document: cbc:Amount from cbc:BaseAmount and cbc:MultiplierFactorNumeric in the same cac:AllowanceCharge in UBL, ram:ActualAmount from ram:BasisAmount and ram:CalculationPercent in CII. If you cannot substantiate the percentage, leave both explanatory fields out.
- PEPPOL-EN16931-R041
Allowance/charge base amount MUST be provided when allowance/charge percentage is provided.
- What the rule requires
- If the percentage of an allowance or charge is given, the base amount must accompany it. A percentage with nothing to apply it to cannot be recalculated.
- Why it fires
- The percentage is what the contract says, so it gets carried through. The base amount is an intermediate result of pricing and frequently has no field of its own in the invoice object at all.
- How to fix it
- Send both or neither: cbc:MultiplierFactorNumeric together with cbc:BaseAmount in the same cac:AllowanceCharge in UBL, ram:CalculationPercent together with ram:BasisAmount in CII. The amount on its own is always acceptable.
- PEPPOL-EN16931-R042
Allowance/charge percentage MUST be provided when allowance/charge base amount is provided.
- What the rule requires
- If the base amount of an allowance or charge is given, the percentage must accompany it — the counterpart to R041.
- Why it fires
- The base amount is set because it happens to be in the data; the percentage is not, because the discount was really a fixed sum. What remains is a reference figure from which nothing follows.
- How to fix it
- Add cbc:MultiplierFactorNumeric in UBL, or ram:CalculationPercent in CII — or drop the base amount if this is a fixed sum. A fixed sum needs only cbc:Amount.
- PEPPOL-EN16931-R043
Allowance/charge ChargeIndicator value MUST equal 'true' or 'false'
- What the rule requires
- The indicator saying whether this is a charge or an allowance must be exactly true or false — lower case, with no alternatives.
- Why it fires
- Serialisers write booleans differently: 1 and 0 out of many databases, True out of Python, TRUE out of some templates. Each is unambiguous to a human and wrong for the schema.
- How to fix it
- Write the value as the literal string true or false in cbc:ChargeIndicator in UBL. Do not rely on your boolean type's default serialisation; emit the two literals explicitly.
- PEPPOL-EN16931-R043-1
[PEPPOL-EN16931-R043]-Allowance/charge ChargeIndicator value MUST equal 'true' or 'false'
- What the rule requires
- In CII the charge indicator of an allowance or charge at document or line level must be exactly true or false. This rule checks the ram:SpecifiedTradeAllowanceCharge group.
- Why it fires
- In CII the value sits one level deeper than in UBL, inside ram:ChargeIndicator/udt:Indicator. Mappings ported from UBL write it straight into ram:ChargeIndicator, or carry over the database's 1 and 0.
- How to fix it
- Write true or false in lower case into ram:ChargeIndicator/udt:Indicator inside ram:SpecifiedTradeAllowanceCharge. The same requirement applies to price-level allowances in a different place — see R043-2.
- PEPPOL-EN16931-R043-2
[PEPPOL-EN16931-R043]-Allowance/charge ChargeIndicator value MUST equal 'true' or 'false'
- What the rule requires
- The same requirement as R043-1, but for the allowance or charge inside the price: ram:AppliedTradeAllowanceCharge under the gross price must carry an indicator of exactly true or false.
- Why it fires
- The price discount is usually written by a different part of the mapping from the document-level ones, often straight out of item pricing. A different convention there surfaces only here, long after R043-1 is clean.
- How to fix it
- Write true or false in lower case into ram:ChargeIndicator/udt:Indicator inside ram:AppliedTradeAllowanceCharge under ram:GrossPriceProductTradePrice. In the price only false is permitted anyway — see R044.
- PEPPOL-EN16931-R044
Charge on price level is NOT allowed. Only value 'false' allowed.
- What the rule requires
- No charge is permitted inside a line's price. The indicator there may only be false: the price knows nothing but the allowance from the gross price down to the net price.
- Why it fires
- Anyone modelling a surcharge on an item — small-quantity, express, packaging — looks for it in the price, because that is where their own system keeps it. The norm provides for charges only at line or document level.
- How to fix it
- Move the charge into the line's or the document's cac:AllowanceCharge with cbc:ChargeIndicator true. Inside cac:Price only the allowance, with false, may remain — the same applies to ram:AppliedTradeAllowanceCharge in CII.
- PEPPOL-EN16931-R046
Item net price MUST equal (Gross price - Allowance amount) when gross price is provided.
- PEPPOL-EN16931-R053
No more than one tax total amount must be provided where currency id equals document currency code.
- What the rule requires
- The invoice may contain exactly one tax total in the invoice currency — that is, exactly one block carrying a breakdown. Several tax categories belong inside that one block as several sub-blocks.
- Why it fires
- Emitting one tax block per rate reproduces the structure of your own system rather than the structure of the norm. The invoice then looks right and still carries two grand totals.
- How to fix it
- Emit a single cac:TaxTotal with cbc:TaxAmount and one cac:TaxSubtotal inside it per tax category in UBL. CII has no such nesting: there, one ram:ApplicableTradeTax per category is correct.
- PEPPOL-EN16931-R054
Only one tax total amount must be provided where currency id equals tax currency code, if tax currency code (BT-6) is provided.
- What the rule requires
- A second tax block without a breakdown is permitted only where a separate VAT accounting currency (BT-6) is given — and then exactly one. It states the tax total in the reporting currency.
- Why it fires
- The block comes from a template for cross-border invoices and stays behind when BT-6 is not set in a given case. What is left is a tax total in a currency the invoice does not otherwise mention.
- How to fix it
- Emit the extra cac:TaxTotal without a cac:TaxSubtotal only together with cbc:TaxCurrencyCode — both or neither. The actual breakdown stays in the invoice-currency block, see R053.
- PEPPOL-EN16931-R055
Invoice total VAT amount and Invoice total VAT amount in accounting currency MUST have the same operational sign
- What the rule requires
- The tax total in the invoice currency and the tax total in the accounting currency must carry the same operational sign. Both describe the same amount, only in two currencies.
- Why it fires
- On credit notes with negative amounts the sign gets flipped in one place and not the other — typically because the conversion into the reporting currency runs through a service that returns an absolute value.
- How to fix it
- Convert from the signed amount rather than handling sign and magnitude separately. What is checked is the two cbc:TaxAmount values in the cac:TaxTotal blocks in UBL, and the corresponding amounts in CII.
- PEPPOL-EN16931-R061
Mandate reference MUST be provided for direct debit.
- PEPPOL-EN16931-R101
Element Additional referenced document can only be used for Invoice line object.
- What the rule requires
- On an invoice line the only additional document reference permitted is the object identifier — type code 130. Other document references belong at document level.
- Why it fires
- Delivery-note and order numbers are maintained per line and so get written per line. At line level the norm provides only for the object identifier.
- How to fix it
- Set cbc:DocumentTypeCode to 130 on the line (cac:InvoiceLine/cac:DocumentReference in UBL, the line's ram:AdditionalReferencedDocument in CII). Delivery notes and orders belong in cac:DespatchDocumentReference or cac:OrderReference at document level.
- PEPPOL-EN16931-R110
Start date of line period MUST be within invoice period.
- What the rule requires
- If a line has a period of its own, it must fall inside the invoicing period: the line's start date may not precede the invoice's start date.
- Why it fires
- Back-billing is the classic case: a service from the previous month is picked up on the current invoice and keeps its real period, while the invoicing period covers only the current month.
- How to fix it
- Widen the invoicing period (cac:InvoicePeriod in the header) so it spans every line, or put the back-billed item on an invoice of its own. What is compared is the line's cbc:StartDate against the header's cbc:StartDate.
- PEPPOL-EN16931-R111
End date of line period MUST be within invoice period.
- What the rule requires
- A line period's end date may not fall after the end of the invoicing period — the counterpart to R110 at the other end.
- Why it fires
- Prepaid services run past the end of the invoice: an annual licence is billed in August and ends the following July, while the invoicing period covers August.
- How to fix it
- Set the invoicing period so it encloses the longest line, or drop the header period altogether where the lines carry their own. What is compared is the line's cbc:EndDate against the header's cbc:EndDate.
- 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
- What the rule requires
- A line's net amount must follow from the line's own figures: quantity × (net price ÷ price base quantity), plus the line's charges and minus its allowances.
- Why it fires
- The line amount comes from the invoice object, where it was computed long ago, and quantity, price and discounts are written alongside it. As soon as the price base quantity is not 1, or a line discount appears, the two routes diverge.
- How to fix it
- Compute cbc:LineExtensionAmount from exactly the values present on the line — cbc:InvoicedQuantity, cac:Price/cbc:PriceAmount, cac:Price/cbc:BaseQuantity and the line's cac:AllowanceCharge. This rule is a warning rather than a rejection: the invoice stays valid, but its numbers contradict each other.
- PEPPOL-EN16931-R121
Base quantity MUST be a positive number above zero.
- PEPPOL-EN16931-R130
Unit code of price base quantity MUST be same as invoiced quantity.
- What the rule requires
- The unit of the price base quantity must be the same as the unit of the invoiced quantity. Price and quantity have to measure the same thing, or the line amount cannot be recalculated.
- Why it fires
- Quantity and price come from different sources: the quantity from the order, the price base quantity from the item master. Where the master is kept in kilograms and the order is placed in pieces, two different units meet.
- How to fix it
- Set the unitCode attribute of cac:Price/cbc:BaseQuantity to match that of cbc:InvoicedQuantity, or convert the price to the invoiced unit beforehand. The same applies to ram:BasisQuantity and ram:BilledQuantity in CII.
NormAPI provides technical validation, not tax or legal advice.