Moving from generating PDF invoices to generating PEPPOL-conformant XML means taking data your ERP already has and putting it into the right XML elements with the right formats and codes. This sounds straightforward until you run into the first Schematron error, discover that your tax totals do not add up the way the standard expects, or find that your customer identifiers are in the wrong format.
This guide maps the most common invoice fields from a typical ERP system to their PEPPOL BIS Billing 3.0 XML equivalents. It focuses on the fields that cause the most confusion in practice.
Document-level fields
| ERP concept | PEPPOL BIS element | XPath | Notes |
|---|---|---|---|
| Invoice number | Invoice ID | cbc:ID | Free text, max 200 chars. Must be unique per biller. |
| Invoice date | Issue date | cbc:IssueDate | ISO 8601: YYYY-MM-DD |
| Due date | Payment due date | cbc:DueDate | In cac:PaymentTerms/cbc:Note or as cbc:PaymentDueDate |
| Currency | Document currency | cbc:DocumentCurrencyCode | ISO 4217 code: CHF, EUR, etc. |
| PO reference | Order reference | cac:OrderReference/cbc:ID | Optional but expected by many buyers |
| Buyer reference | Buyer reference | cbc:BuyerReference | Often a cost centre code or contract number. Mandatory when the buyer requires it. |
The CustomizationID and ProfileID must also be set at the document level and cannot be left to whatever your ERP generates. For a Swiss SwissDIGIN invoice:
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0#conformant#urn:fdc:swissdigin.ch:2018:invoicing:1.0</cbc:CustomizationID>
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:ProfileID>
Supplier (seller) fields
Your ERP's company or legal entity settings feed the supplier block:
<cac:AccountingSupplierParty>
<cac:Party>
<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:Party>
</cac:AccountingSupplierParty>
Key points:
PartyTaxScheme/CompanyIDcarries the UID with theMWSTsuffix (e.g.CHE-116.281.710 MWST)PartyLegalEntity/CompanyIDcarries the UID without the suffix, withschemeID="CHE"- Country code is ISO 3166-1 alpha-2 (
CHnotCHEorSwitzerland) - If you are not VAT-registered (below CHF 100,000 revenue), omit the
PartyTaxSchemeblock entirely
Customer (buyer) fields
The buyer block mirrors the supplier block. The fields your ERP stores for a customer:
| ERP concept | PEPPOL BIS element | Notes |
|---|---|---|
| Customer name | AccountingCustomerParty/Party/PartyName/Name | Legal name, not a trading name |
| Billing address | AccountingCustomerParty/Party/PostalAddress | Street, city, postal code, country |
| Customer UID | AccountingCustomerParty/Party/PartyLegalEntity/CompanyID | With schemeID="CHE" for Swiss customers |
| Customer VAT number | AccountingCustomerParty/Party/PartyTaxScheme/CompanyID | For non-Swiss EU buyers: their VAT number |
| PEPPOL participant ID | AccountingCustomerParty/Party/EndpointID | Required for PEPPOL delivery. Format: 0209:CHE1162817100 |
The endpoint ID (EndpointID) is what the PEPPOL network uses to route the invoice. For Swiss participants, the scheme is 0209 and the identifier is the UID digits without separators. Your access point provider handles the routing — you just need to include the correct endpoint ID so they know where to send it.
Payment details
This is where Swiss invoices differ most from generic PEPPOL invoices:
<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:FinancialInstitutionBranch>
<cbc:ID>RAIFCH22</cbc:ID>
</cac:FinancialInstitutionBranch>
</cac:PayeeFinancialAccount>
</cac:PaymentMeans>
PaymentMeansCode 58= SEPA credit transfer (used for Swiss IBAN-based transfers too)PaymentID= the 27-digit QR reference from your QR-bill. Use the SCOR reference here if you use ISO 11649 creditor references instead.PayeeFinancialAccount/ID= QR-IBAN (without spaces). Use standard IBAN if you do not use QR references.- BIC in
FinancialInstitutionBranch/IDis optional but recommended
Tax totals
Tax is where most mapping projects produce their first Schematron errors. The PEPPOL BIS tax model requires:
- One or more
TaxSubtotalblocks — one per distinct VAT rate - A
TaxTotalthat sums all subtotals - Each subtotal carries a
TaxableAmount, aTaxAmount, and aTaxCategoryblock
<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>
Common mistakes:
- Calculating
TaxAmountby multiplyingTaxableAmount × Percent / 100at the subtotal level and rounding each one independently, then summing. Rounding errors accumulate. Calculate line-level tax, sum without rounding, then round once at the subtotal level. - Including a
TaxSubtotalper invoice line rather than per distinct tax rate. You must consolidate: all lines at 8.1% share one subtotal. - Setting
TaxCategory/IDto the rate value instead of the category code. The category code for standard-rate Swiss VAT is alwaysSregardless of whether the rate is 8.1%, 2.6%, or 3.8%.
Line items
Each invoice line maps to a cac:InvoiceLine:
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="HUR">10</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="CHF">500.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Description>Software development services</cbc:Description>
<cbc:Name>Development</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>
InvoicedQuantityrequires aunitCode— use UN/ECE Recommendation 20 codes (HURfor hours,C62for pieces,KGMfor kilograms,DAYfor days)LineExtensionAmount= quantity × price, before VAT. This is what feeds the tax calculation.- Tax category at line level must match what is in the header
TaxSubtotal
Document totals
The LegalMonetaryTotal block summarises the invoice:
<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 all lineLineExtensionAmountvaluesTaxExclusiveAmount= net total after header-level allowances/charges, before VATTaxInclusiveAmount=TaxExclusiveAmount+ total VATPayableAmount= what the buyer owes (may differ fromTaxInclusiveAmountif a prepayment has been made)
The Schematron checks that these figures are internally consistent. If LineExtensionAmount does not equal the sum of your line amounts, or TaxInclusiveAmount does not equal TaxExclusiveAmount + TaxTotal/TaxAmount, you will get a BR-CO-1x error.
For a full walkthrough of the XML structure including allowances and charges, see the PEPPOL BIS Billing 3.0 line-by-line walkthrough. For the Swiss-specific extensions on top of this mapping, see the post on how SwissDIGIN extends PEPPOL BIS Billing 3.0.