UBL is a syntax, not a rule set. The same OASIS schema carries a plain EN 16931 invoice, a Peppol BIS document, a Dutch NLCIUS one and a Norwegian EHF one, and what decides which rules apply is a single string: cbc:CustomizationID. Two files can be structurally identical and be judged against completely different rulebooks.
That is why the set below varies by rule set rather than by data richness, and why one of the files is a different document type altogether.
One syntax, four rule sets
| File | CustomizationID declares | What it adds over EN 16931 |
|---|---|---|
| ubl-en16931 | urn:cen.eu:en16931:2017 | Nothing. The European core on its own |
| ubl-peppol-bis-3 | Peppol BIS Billing 3.0 | Network routing rules, a narrower electronic address list |
| ubl-nlcius | NLCIUS 1.0 | Dutch rules, including a KVK or OIN identifier for domestic buyers |
| ubl-ehf | Peppol BIS Billing 3.0 | Norwegian rules, which trigger on the supplier's country rather than on the identifier |
EHF is the odd one and worth understanding before you go looking for a missing identifier: the Norwegian profile deliberately declares the Peppol CustomizationID, exactly as its specification says, and the Norwegian rules key off the supplier being in Norway. So you cannot tell an EHF document from a Peppol one by BT-24 alone.
The Dutch and Norwegian files carry genuinely national data rather than the German invoice with the country code swapped, because that is the only way they exercise the national rules. The Norwegian organisation numbers are fictional but carry valid MOD-11 check digits, since the Peppol rules verify them.
The credit note is a different document
This is the one thing on this page most likely to break your integration. In UBL, a credit note is not an invoice with a different type code. It is a separate document type with its own root element and its own namespace:
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<CreditNote xmlns="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2">
If you are arriving from CII this will catch you out, because there a credit note is the same CrossIndustryInvoice message with BT-3 set to 381. The CII sample set has that file, and diffing the two is the fastest way to internalise the difference. Anything that switches on a root element, or an XPath written against /Invoice, needs both branches.
Files that are wrong on purpose
Both break exactly one Peppol rule and nothing else, which is asserted when they are generated. Both stay schema-valid, so an XSD check passes them.
The second is the more instructive one. It uses EM, the email scheme, which is a perfectly valid EN 16931 electronic address code and the usual choice for XRechnung delivery. Peppol's list is narrower and does not carry it, so the document is simultaneously valid EN 16931 and invalid Peppol. A validator that only runs the CEN rules will pass this file and your access point will still reject it. See PEPPOL-EN16931-CL008 and PEPPOL-EN16931-R010 for what each rule checks.
Note that we had to bypass our own API to produce them: the create endpoint pre-checks both conditions and returns a field-level error naming the offending scheme rather than letting the document reach the Schematron.
How these files were generated
One endpoint, with the rule set selected as a profile:
curl -X POST https://api.invoicexml.com/v1/create/ubl \
-H "Authorization: Bearer $INVOICEXML_API_KEY" \
-H "Content-Type: application/json" \
-d @invoice.json \
--output ubl-peppol-bis-3.xml
The default is peppol-bis-3; options.profile takes en16931, nlcius, ehf, xrechnung or pint. The credit note needed no option at all: setting the invoice type code to 381 is what makes the API emit a CreditNote document. Each file was then validated against its own profile before publishing, which is how we know the national ones actually satisfy the national rules.
To check a file of your own, the UBL validator takes an upload and reports the rule identifiers rather than a bare pass or fail.
Why sample files go stale
These were generated on 6 August 2026, against Peppol BIS 3.0.20, NLCIUS 2.0.3.12 and the EN 16931 UBL rules 1.3.16.
Peppol is the busiest release cycle in this family. OpenPeppol ships rule set updates roughly twice a year, each national authority moves its own CIUS on its own schedule, and the PINT profiles for Singapore, Australia, Japan, Malaysia and New Zealand are still settling. If you send across several countries you are not tracking one specification, you are tracking a handful of them with different release calendars.
Building the UBL is the easy part. Following four or five rule sets indefinitely, re-vendoring artefacts and shipping before each in-force date, is the part that never finishes. On the InvoiceXML API the current rule sets are simply what runs, for every profile, with no SDK to upgrade and no redeploy when one of them moves. If you would rather not own that calendar, start with the free trial, or read how the Peppol API handles the profile family end to end.