EN 16931, UBL & PEPPOL

Three standards stack to make one interoperable invoice: EN 16931 says what an invoice means, UBL 2.1 says how to write it in XML, and PEPPOL BIS 3.0 says how parties agree to exchange it. Here's what each does, in plain language.

Why standards at all

A tax authority — or a buyer's accounting system — can't process a PDF or a bespoke JSON shape automatically. Electronic invoicing mandates require a structured, machine-readable invoice in an agreed format, so the data can be validated and posted without a human re-keying it. The three standards below are the ones the European and PEPPOL-aligned world has settled on. TaxLens produces all three from a single booking.

How the three layers relate
EN 16931 — the semantic modelWhat an invoice means: Business Terms (BT-*), groups (BG-*), VAT rules (BR-*)UBL 2.1 — the XML syntaxHow to write each Business Term as XML elementsPEPPOL BIS 3.0 — the exchange profileWhich subset + extra rules trading partners agree on (CustomizationID)
Each layer constrains the one above it: a meaning, a syntax, and an exchange profile.

EN 16931 — the meaning

The European standard for the semantic content of an invoice. It is format-agnostic: it defines the information, not the file.

EN 16931 breaks an invoice into named Business Terms — each a single piece of information with a stable identifier:

  • BT-1 — invoice number; BT-2 — issue date; BT-5 — currency
  • BT-27 — seller name; BT-31 — seller VAT identifier; BT-40 — seller country
  • BT-44 — buyer name; BT-55 — buyer country
  • BG-23 — VAT breakdown group; BT-118/119/116/117 — each VAT subtotal's category, rate, base, and tax

It also carries Business Rules (BR-*) that an invoice must satisfy — for example, every VAT line must reconcile against a matching VAT subtotal. The TaxLens GET …/document response is exactly this model in JSON. The pre-check enforces the mandatory subset; see Send-readiness.

UBL 2.1 — the syntax

Universal Business Language: the XML vocabulary that binds each EN 16931 Business Term to concrete XML elements.

EN 16931 says "there is a seller VAT identifier (BT-31)." UBL 2.1 says where that goes in the XML tree (cac:AccountingSupplierParty cac:PartyTaxSchemecbc:CompanyID). TaxLens serializes to UBL with a dependency-free serializer, so the file is structurally correct by construction; XSD-conformance is verified in our test suite, not validated at request time. The runtime gate is the EN 16931 / PEPPOL mandatory pre-check — see Send-readiness.

Annotated UBL fragment
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
         xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents"
         xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents">

  <!-- PEPPOL BIS 3.0 profile: this is the "exchange agreement" layer -->
  <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>

  <cbc:ID>A-2026-000145</cbc:ID>          <!-- BT-1  invoice number -->
  <cbc:IssueDate>2026-06-06</cbc:IssueDate>   <!-- BT-2  issue date -->
  <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>  <!-- 380 = commercial invoice -->
  <cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>  <!-- BT-5 -->

  <cac:AccountingSupplierParty>
    <cac:Party>
      <cac:PartyLegalEntity>
        <cbc:RegistrationName>Hotel Adler GmbH</cbc:RegistrationName>  <!-- BT-27 seller name -->
      </cac:PartyLegalEntity>
      <cac:PartyTaxScheme>
        <cbc:CompanyID>DE123456789</cbc:CompanyID>   <!-- BT-31 seller VAT id -->
        <cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme>
      </cac:PartyTaxScheme>
    </cac:Party>
  </cac:AccountingSupplierParty>

  <cac:TaxTotal>
    <cac:TaxSubtotal>                       <!-- BG-23 one VAT subtotal per (category, rate) -->
      <cbc:TaxableAmount currencyID="EUR">600.00</cbc:TaxableAmount>
      <cbc:TaxAmount currencyID="EUR">60.00</cbc:TaxAmount>
      <cac:TaxCategory>
        <cbc:ID>S</cbc:ID>                  <!-- S = standard-rated -->
        <cbc:Percent>10</cbc:Percent>       <!-- BT-119 the VAT rate -->
      </cac:TaxCategory>
    </cac:TaxSubtotal>
  </cac:TaxTotal>
</Invoice>

PEPPOL BIS 3.0 — the exchange agreement

PEPPOL BIS Billing 3.0 is a profile on top of EN 16931 + UBL: it picks a subset of fields, adds a few extra rules trading partners need, and stamps the document with a CustomizationID so a receiver instantly knows which rulebook applies. The CustomizationID you saw above is what tells a PEPPOL Access Point "this is a PEPPOL BIS 3.0 invoice — validate it against that profile."

Format ready, transmission deferred
TaxLens produces the PEPPOL-conformant document. Actually delivering it over the PEPPOL network requires an Access Point, which is on the roadmap, not built yet — see What's covered vs deferred.

In one sentence each

  • EN 16931 — the agreed meaning of every field on an invoice.
  • UBL 2.1 — how to write those fields as XML.
  • PEPPOL BIS 3.0 — the profile two parties agree to exchange.

Next: make sure your document actually satisfies these rules in Send-readiness & fixing violations, or see why occupancy tax is left off in VAT-only invoices.