Back to blogSeptember 27, 2026

UBL Invoice Example: XML Fields and Validation Explained

A UBL invoice is an XML document whose element names, order, and namespaces are defined by the OASIS Universal Business Language schemas. Below is a complete UBL 2.1 invoice you can copy, save as ubl-invoice-example.xml, and check against the official schema with one xmllint command. By the end, you'll know what each block does, how the totals reconcile, and which problems schema validation catches and which it lets through. For background on what UBL is and why it's used, see our introduction to the UBL format.

What this example covers

The sample targets base OASIS UBL 2.1, approved as an OASIS Standard in 2013. It isn't the newest UBL release. It's an intentional boundary: UBL 2.1 is the syntax that profiles such as Peppol BIS Billing 3.0 build their own rules on, and it gives you one stable schema to test against.

Everything in the file is synthetic. The supplier and customer are fictional, and the invoice has one line: two units at 50.00 EUR each, with an illustrative 20% VAT rate. The rate is there to demonstrate the arithmetic, not to say how any real transaction should be taxed. Country fields use XX, a code ISO 3166 leaves for user assignment, so the sample doesn't point at a real jurisdiction.

The complete UBL 2.1 invoice

Copy the following and save it as ubl-invoice-example.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="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:UBLVersionID>2.1</cbc:UBLVersionID>
  <cbc:ID>INV-2026-0042</cbc:ID>
  <cbc:IssueDate>2026-09-27</cbc:IssueDate>
  <cbc:DueDate>2026-10-27</cbc:DueDate>
  <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
  <cbc:Note>Synthetic example for education only. Not a real transaction.</cbc:Note>
  <cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
  <cbc:BuyerReference>PO-EXAMPLE-7788</cbc:BuyerReference>
  <cac:AccountingSupplierParty>
    <cac:Party>
      <cac:PartyIdentification>
        <cbc:ID>SUPPLIER-EXAMPLE-001</cbc:ID>
      </cac:PartyIdentification>
      <cac:PartyName>
        <cbc:Name>Harbor Example Labs Ltd.</cbc:Name>
      </cac:PartyName>
      <cac:PostalAddress>
        <cbc:StreetName>Example Street</cbc:StreetName>
        <cbc:BuildingNumber>100</cbc:BuildingNumber>
        <cbc:CityName>Sampleton</cbc:CityName>
        <cbc:PostalZone>EX-1000</cbc:PostalZone>
        <cac:Country>
          <cbc:IdentificationCode>XX</cbc:IdentificationCode>
        </cac:Country>
      </cac:PostalAddress>
      <cac:PartyTaxScheme>
        <cbc:CompanyID>XX-TAX-SUPPLIER-001</cbc:CompanyID>
        <cac:TaxScheme>
          <cbc:ID>VAT</cbc:ID>
        </cac:TaxScheme>
      </cac:PartyTaxScheme>
      <cac:PartyLegalEntity>
        <cbc:RegistrationName>Harbor Example Labs Ltd.</cbc:RegistrationName>
        <cbc:CompanyID>XX-REG-SUPPLIER-001</cbc:CompanyID>
      </cac:PartyLegalEntity>
    </cac:Party>
  </cac:AccountingSupplierParty>
  <cac:AccountingCustomerParty>
    <cac:Party>
      <cac:PartyIdentification>
        <cbc:ID>CUSTOMER-EXAMPLE-001</cbc:ID>
      </cac:PartyIdentification>
      <cac:PartyName>
        <cbc:Name>Meridian Sample Retail Inc.</cbc:Name>
      </cac:PartyName>
      <cac:PostalAddress>
        <cbc:StreetName>Market Road</cbc:StreetName>
        <cbc:BuildingNumber>25</cbc:BuildingNumber>
        <cbc:CityName>Demoville</cbc:CityName>
        <cbc:PostalZone>EX-2000</cbc:PostalZone>
        <cac:Country>
          <cbc:IdentificationCode>XX</cbc:IdentificationCode>
        </cac:Country>
      </cac:PostalAddress>
      <cac:PartyTaxScheme>
        <cbc:CompanyID>XX-TAX-CUSTOMER-001</cbc:CompanyID>
        <cac:TaxScheme>
          <cbc:ID>VAT</cbc:ID>
        </cac:TaxScheme>
      </cac:PartyTaxScheme>
      <cac:PartyLegalEntity>
        <cbc:RegistrationName>Meridian Sample Retail Inc.</cbc:RegistrationName>
        <cbc:CompanyID>XX-REG-CUSTOMER-001</cbc:CompanyID>
      </cac:PartyLegalEntity>
    </cac:Party>
  </cac:AccountingCustomerParty>
  <cac:PaymentMeans>
    <cbc:PaymentMeansCode>30</cbc:PaymentMeansCode>
    <cbc:PaymentID>INV-2026-0042</cbc:PaymentID>
  </cac:PaymentMeans>
  <cac:PaymentTerms>
    <cbc:Note>Payment due within 30 days of the issue date.</cbc:Note>
  </cac:PaymentTerms>
  <cac:TaxTotal>
    <cbc:TaxAmount currencyID="EUR">20.00</cbc:TaxAmount>
    <cac:TaxSubtotal>
      <cbc:TaxableAmount currencyID="EUR">100.00</cbc:TaxableAmount>
      <cbc:TaxAmount currencyID="EUR">20.00</cbc:TaxAmount>
      <cac:TaxCategory>
        <cbc:ID>S</cbc:ID>
        <cbc:Percent>20</cbc:Percent>
        <cac:TaxScheme>
          <cbc:ID>VAT</cbc:ID>
        </cac:TaxScheme>
      </cac:TaxCategory>
    </cac:TaxSubtotal>
  </cac:TaxTotal>
  <cac:LegalMonetaryTotal>
    <cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
    <cbc:TaxExclusiveAmount currencyID="EUR">100.00</cbc:TaxExclusiveAmount>
    <cbc:TaxInclusiveAmount currencyID="EUR">120.00</cbc:TaxInclusiveAmount>
    <cbc:PayableAmount currencyID="EUR">120.00</cbc:PayableAmount>
  </cac:LegalMonetaryTotal>
  <cac:InvoiceLine>
    <cbc:ID>1</cbc:ID>
    <cbc:InvoicedQuantity unitCode="H87">2</cbc:InvoicedQuantity>
    <cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
    <cac:Item>
      <cbc:Description>Environmental sensor kit, synthetic catalog item</cbc:Description>
      <cbc:Name>Sensor kit</cbc:Name>
      <cac:SellersItemIdentification>
        <cbc:ID>SKU-SENSOR-001</cbc:ID>
      </cac:SellersItemIdentification>
      <cac:ClassifiedTaxCategory>
        <cbc:ID>S</cbc:ID>
        <cbc:Percent>20</cbc:Percent>
        <cac:TaxScheme>
          <cbc:ID>VAT</cbc:ID>
        </cac:TaxScheme>
      </cac:ClassifiedTaxCategory>
    </cac:Item>
    <cac:Price>
      <cbc:PriceAmount currencyID="EUR">50.00</cbc:PriceAmount>
      <cbc:BaseQuantity unitCode="H87">1</cbc:BaseQuantity>
    </cac:Price>
  </cac:InvoiceLine>
</Invoice>

How cbc and cac elements work

UBL splits its shared vocabulary across two namespaces, and the prefix tells you what kind of element you're reading:

  • cbc: (Common Basic Components) elements hold a single value: an identifier, a date, a code, an amount, or a quantity. cbc:IssueDate and cbc:PayableAmount are basic components.
  • cac: (Common Aggregate Components) elements are containers for other elements. cac:AccountingSupplierParty wraps a cac:Party, which wraps a name, an address, and tax details.

The root Invoice element lives in its own document namespace, declared here as the default so it needs no prefix. The prefixes are only labels: a validator checks the namespace URI each prefix maps to. cac and cbc are the convention, not a requirement.

Some basic components carry attributes. Amounts carry currencyID, and quantities can carry a unitCode such as H87, the UN/ECE Recommendation 20 code for a piece. Amount values are plain decimals with a dot separator: 50.00, never 50,00.

Field-by-field walkthrough

Area Elements Sample value Purpose
Header cbc:UBLVersionID 2.1 UBL version the document follows
Header cbc:ID, cbc:IssueDate, cbc:DueDate INV-2026-0042, 2026-09-27, 2026-10-27 Invoice number, issue date, payment due date
Header cbc:InvoiceTypeCode 380 Commercial invoice, from the UNTDID 1001 code list
Header cbc:DocumentCurrencyCode EUR Currency of the document's amounts
Parties cac:AccountingSupplierParty, cac:AccountingCustomerParty Harbor Example Labs, Meridian Sample Retail Who bills and who is billed
Payment cac:PaymentMeans, cac:PaymentTerms Code 30 (credit transfer), 30 days How and when to pay
Line cac:InvoiceLine 2 × 50.00 Item, quantity, unit price, line net amount
Tax cac:TaxTotal, cac:TaxSubtotal 20.00 on 100.00 Tax per category and in total
Totals cac:LegalMonetaryTotal 100.00 net, 120.00 payable Document-level amounts

Header. Dates use the YYYY-MM-DD form the schema's date type expects. The sample deliberately leaves out cbc:CustomizationID. Profiles use that element to name the specification a document claims to follow, and a profile validator then holds the document to all of that profile's rules. Base UBL 2.1 doesn't require it.

Parties. Each party has an identifier, a name, a postal address, a cac:PartyTaxScheme for tax registration, and a cac:PartyLegalEntity for the registered legal name. All identifiers are placeholders, not real tax or company numbers.

Line and tax. The line's cac:ClassifiedTaxCategory and the document's cac:TaxSubtotal both use category S (standard rate, from the UNCL 5305 code list) under the VAT scheme, so each subtotal traces back to the lines it summarizes. Base UBL 2.1 types values like 380, 30, S, and H87 as codes but doesn't check them against their code lists. Profiles do.

How the totals add up

Step Calculation Element
Line net amount 2 × 50.00 = 100.00 cbc:LineExtensionAmount on the line
Sum of line net amounts 100.00 cbc:LineExtensionAmount in cac:LegalMonetaryTotal
Tax-exclusive total 100.00 (no document-level allowances or charges) cbc:TaxExclusiveAmount
Tax 100.00 × 20% = 20.00 cbc:TaxAmount in the subtotal and the total
Tax-inclusive total 100.00 + 20.00 = 120.00 cbc:TaxInclusiveAmount
Amount due 120.00 (nothing prepaid, no rounding) cbc:PayableAmount

Keep money in a decimal type from calculation through serialization. If your system stores amounts as binary floating-point numbers, convert and round before writing XML, or you'll eventually emit something like 19.999999999999996.

Four levels of validation

"Valid" means different things depending on which check you ran. Treat these as separate gates:

  1. Well-formed XML. Tags close and nest correctly, there's one root, and attributes are quoted. Any XML parser checks this, for example xmllint --noout ubl-invoice-example.xml.
  2. Base XSD structure. The document uses the right namespaces, includes required elements in the order the schema defines, and uses valid data types for dates, decimals, and amounts. The UBL 2.1 Invoice schema checks this.
  3. Business, profile, and country rules. Arithmetic consistency, extra mandatory fields, allowed code values, and tax category logic. These come from the EN 16931 semantic model, profiles such as Peppol BIS Billing 3.0, and country specifications, usually enforced with Schematron rules published alongside each profile.
  4. Delivery and recipient acceptance. Whether the transport network, the recipient's endpoint, and the buyer's own processes accept the document. A file can pass every rule above and still be rejected for going to the wrong address or not matching a purchase order.

The XML above was checked against the official OASIS UBL 2.1 Invoice XSD on September 27, 2026. That check covers level 2 only; the sample is not built to pass EN 16931, Peppol, or any country profile.

Run the base UBL 2.1 schema check

You need xmllint installed locally. It ships with libxml2; on Debian and Ubuntu it's in the libxml2-utils package. Download UBL-2.1.zip from the official OASIS UBL 2.1 release index and extract it, then run:

xmllint --nonet --noout --schema /path/to/UBL-2.1/xsd/maindoc/UBL-Invoice-2.1.xsd ubl-invoice-example.xml

Replace /path/to/UBL-2.1 with the directory where you actually extracted the archive. The Invoice schema in xsd/maindoc imports the shared component schemas from xsd/common through relative paths, so keep the extracted xsd folder together. Copying UBL-Invoice-2.1.xsd on its own breaks those imports.

--nonet blocks network fetches, so every schema must resolve from your local copy, and --noout stops xmllint from echoing the document. When the file matches the schema, xmllint reports that it validates and exits with status 0. When it doesn't, xmllint names the line and element that failed, reports that the file fails to validate, and exits non-zero, which makes the command easy to drop into a CI step.

Common mistakes and what catches them

  • Elements in the wrong order. UBL schemas define sequences, so position matters. cac:TaxTotal must come before cac:LegalMonetaryTotal, and inside cac:Item, cbc:Description comes before cbc:Name. The XSD catches this. If you build XML from maps or dictionaries, serialize in a fixed schema order rather than relying on key order.
  • Wrong or missing namespace. Writing cac:ID instead of cbc:ID, or dropping the default namespace from the root, produces "element not expected" or "no matching global declaration" errors. The XSD catches these.
  • Locale-formatted numbers or missing attributes. 50,00 isn't a valid decimal, and the UBL 2.1 amount type requires currencyID. The XSD catches both.
  • Totals that don't reconcile. Change cbc:PayableAmount to 125.00 and the file still passes the base schema, because the XSD checks that each amount is a decimal, not that the amounts add up. Profile rules and your own pre-send checks catch this.
  • Codes that aren't on the list. A cbc:InvoiceTypeCode of 999 passes the base schema. So does this sample's XX country code, which a profile's code-list checks would reject by design.
  • Missing profile requirements. Profiles typically require their own specification identifier, electronic addresses for the parties, more mandatory fields, and specific tax category rules. Adding the identifier alone doesn't make a document conform; it tells validators which rules to apply, and the document still has to meet them.

From a base sample to production e-invoices

A schema-valid sample shows you the structure, but production invoicing asks for more. Each issuing entity needs correct legal names, tax identifiers, and addresses. Each invoice needs the tax treatment that actually applies, the profile the recipient or country expects, and a delivery route the recipient accepts. Those requirements vary by country and change over time.

Space Invoices provides embedded invoicing and compliance infrastructure with entity-aware invoicing and country-specific e-invoicing workflows. Supported formats and countries are listed in the current documentation, so check there for the country you need rather than assuming a particular format is available. This article isn't legal or tax advice; confirm your obligations with a qualified advisor or the relevant tax authority.

Next step

To see which e-invoicing formats and countries are supported today, read the Space Invoices e-invoicing documentation, then sign up for the sandbox to try the workflow with synthetic data.

Start with one workflow, not a whole rewrite

Free sandbox with no time limit. Prove one entity and one invoice flow before deciding how much UI, compliance, and rollout surface you want to own.