PEPPOL BIS Billing 3.0 is the invoice format used across the PEPPOL network. It is based on UBL 2.1 XML, constrained by the European standard EN 16931, and extended for country-specific requirements by national profiles like SwissDIGIN. The full specification document is long. This post works through the key sections of an actual invoice XML, explaining what each part does and what the common mistakes are.
If you want the Swiss-specific extensions on top of this structure, see the SwissDIGIN post. This post focuses on the core PEPPOL BIS structure that applies everywhere.
The document envelope
Every PEPPOL BIS invoice starts with the UBL document declaration and two mandatory identifier elements:
<?xml version="1.0" encoding="UTF-8"?>
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<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:3.0</cbc:ProfileID>
CustomizationID identifies the specification profile. Validation tools check this to know which Schematron rules to apply. If you use SwissDIGIN, the value changes to include the Swiss profile extension. ProfileID identifies the procurement process profile — for invoices it is always the billing 3.0 string shown above.
Document-level fields
<cbc:ID>INV-2024-0047</cbc:ID>
<cbc:IssueDate>2024-03-15</cbc:IssueDate>
<cbc:DueDate>2024-04-14</cbc:DueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>CHF</cbc:DocumentCurrencyCode>
<cbc:BuyerReference>PO-9981</cbc:BuyerReference>
ID is your invoice number — any string up to 200 characters, must be unique per supplier.
IssueDate and DueDate use ISO 8601 format (YYYY-MM-DD). Time components are not used.
InvoiceTypeCode uses UN/CEFACT 1001 codes. 380 is a commercial invoice. 381 is a credit note. Do not use 380 for a credit note — validators will fail it.
DocumentCurrencyCode is the three-letter ISO 4217 currency code. CHF for Swiss franc invoices.
BuyerReference is free text — often a purchase order number, contract number, or cost centre code the buyer has asked you to include. It is not mandatory in the base spec but many buyers require it and will reject invoices without it.
Order and contract references
<cac:OrderReference>
<cbc:ID>PO-9981</cbc:ID>
</cac:OrderReference>
<cac:ContractDocumentReference>
<cbc:ID>CONTRACT-2024-007</cbc:ID>
</cac:ContractDocumentReference>
These are optional but frequently expected by public sector and larger corporate buyers. Include the purchase order number in OrderReference whenever you have one — it is the most common piece of information that AP teams use to match invoices to commitments.
Supplier (seller) party
<cac:AccountingSupplierParty>
<cac:Party>
<cac:EndpointID schemeID="0209">CHE1162817100</cac:EndpointID>
<cac:PartyName><cbc:Name>Muster AG</cbc:Name></cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Bahnhofstrasse 12</cbc:StreetName>
<cbc:CityName>Zürich</cbc:CityName>
<cbc:PostalZone>8001</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>CH</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>CHE-116.281.710 MWST</cbc:CompanyID>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Muster AG</cbc:RegistrationName>
<cbc:CompanyID schemeID="CHE">CHE-116.281.710</cbc:CompanyID>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>Anna Brunner</cbc:Name>
<cbc:ElectronicMail>[email protected]</cbc:ElectronicMail>
</cac:Contact>
</cac:Party>
</cac:AccountingSupplierParty>
EndpointID with schemeID="0209" is the PEPPOL participant identifier — the UID digits without separators. This is used for PEPPOL routing, not for display.
PartyTaxScheme/CompanyID is the VAT registration number. For Swiss companies it is the UID with the MWST suffix. For non-Swiss sellers it is their country's VAT number.
PartyLegalEntity/CompanyID is the legal entity identifier — for Swiss companies, the UID with schemeID="CHE".
The Contact block is optional but useful for the buyer to know who to call about the invoice.
Buyer (customer) party
The buyer party structure mirrors the seller. The key difference is BuyerReference and the endpoint identifier for routing:
<cac:AccountingCustomerParty>
<cac:Party>
<cac:EndpointID schemeID="0209">CHE3456789012</cac:EndpointID>
<cac:PartyName><cbc:Name>Kunde GmbH</cbc:Name></cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Industriestrasse 7</cbc:StreetName>
<cbc:CityName>Bern</cbc:CityName>
<cbc:PostalZone>3001</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>CH</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Kunde GmbH</cbc:RegistrationName>
<cbc:CompanyID schemeID="CHE">CHE-345.678.901</cbc:CompanyID>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingCustomerParty>
The buyer's EndpointID is what the PEPPOL network uses to route the document to the correct access point. If you do not have the buyer's PEPPOL participant ID, ask your access point provider — they can often look it up in the SMP directory.
Payment means
<cac:PaymentMeans>
<cbc:PaymentMeansCode>58</cbc:PaymentMeansCode>
<cbc:PaymentID>210000000003139471430009017</cbc:PaymentID>
<cac:PayeeFinancialAccount>
<cbc:ID>CH4431999123000889012</cbc:ID>
<cbc:Name>Muster AG</cbc:Name>
</cac:PayeeFinancialAccount>
</cac:PaymentMeans>
PaymentMeansCode 58 means IBAN-based credit transfer. This is used for both SEPA and Swiss domestic transfers in PEPPOL.
PaymentID is the payment reference — the 27-digit QR reference for Swiss invoices using QR-bills, or an ISO creditor reference (RF18539007547034) if using SCOR.
The account ID in PayeeFinancialAccount is the IBAN (no spaces). For Swiss QR-bill payments, this should be the QR-IBAN. For payments without a QR reference, use the standard IBAN.
Tax total
<cac:TaxTotal>
<cbc:TaxAmount currencyID="CHF">81.00</cbc:TaxAmount>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="CHF">1000.00</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="CHF">81.00</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>8.1</cbc:Percent>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>
There must be exactly one TaxSubtotal per distinct tax rate. If your invoice has lines at 8.1% and lines at 2.6%, you need two TaxSubtotal blocks — one for each rate — and the TaxAmount in the parent TaxTotal must equal the sum of both.
Tax category S means standard-rated VAT. The rate value (8.1, 2.6, or 3.8) is what distinguishes Swiss rates. For zero-rated lines, use category Z. For exempt lines, use E. For reverse charge, use AE. The tax category codes post covers all categories.
Document totals
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="CHF">1000.00</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="CHF">1000.00</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="CHF">1081.00</cbc:TaxInclusiveAmount>
<cbc:PayableAmount currencyID="CHF">1081.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
LineExtensionAmount: sum of allInvoiceLine/LineExtensionAmountvaluesTaxExclusiveAmount: net total after any header-level allowances or charges, before VATTaxInclusiveAmount:TaxExclusiveAmount+TaxTotal/TaxAmountPayableAmount: the amount the buyer owes. EqualsTaxInclusiveAmountunless a prepayment has been applied.
The Schematron checks consistency between all these values. Any rounding inconsistency here will produce a BR-CO-1x error.
Invoice lines
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="HUR">20</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="CHF">1000.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Description>Web development — March 2024</cbc:Description>
<cbc:Name>Development services</cbc:Name>
<cac:ClassifiedTaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>8.1</cbc:Percent>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme>
</cac:ClassifiedTaxCategory>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="CHF">50.00</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>
ID is the line number — must be unique within the invoice, typically 1, 2, 3 and so on.
InvoicedQuantity requires a unitCode from UN/ECE Recommendation 20. Common codes: HUR (hours), DAY (days), C62 (pieces/units), KGM (kilograms), MTR (metres).
LineExtensionAmount = quantity × unit price. This is the net line amount before VAT.
ClassifiedTaxCategory at the line level tells the receiving system what VAT applies to this line. It must be consistent with the TaxSubtotal in the header — the same category code and percentage must appear in both places.
Price/PriceAmount is the unit price excluding VAT.
Closing the document
</ubl:Invoice>
There is no footer element. The document ends after the last InvoiceLine block and the closing root element tag.
For a guide on mapping your ERP fields to these XML elements, see how to map your ERP data to PEPPOL BIS Billing 3.0. To validate the resulting XML against the official rules, the Schematron validation post explains how the validation pipeline works.