Skip to main content

Overview

If you already have UBL (Universal Business Language) XML files, you can send them directly via e-invoice.be without converting from JSON. This is useful when:
  • You have an ERP system that generates UBL XML
  • You’re migrating from another Peppol Access Point
  • You have existing UBL files to send
  • You want full control over the UBL structure

Prerequisites

Your UBL XML must:
  • Be valid UBL BIS Billing 3.0 format
  • Comply with Peppol specifications
  • Be either an Invoice or Credit Note
Use the /api/validate/ubl endpoint to validate your UBL XML before creating documents.

Workflow

  1. Validate your UBL XML (recommended)
  2. Create document from UBL
  3. Send via Peppol

Step 1: Validate UBL XML

Before creating a document, validate your UBL file:
curl -X POST "https://api.e-invoice.be/api/validate/ubl" \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/xml" \
     --data-binary @invoice.xml

Validation Response

Valid UBL:
{
  "valid": true,
  "message": "UBL document is valid and complies with Peppol BIS Billing 3.0",
  "document_type": "Invoice",
  "invoice_number": "INV-2024-001"
}
Invalid UBL:
{
  "valid": false,
  "errors": [
    {
      "field": "cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID",
      "message": "Missing required Peppol ID"
    },
    {
      "field": "cbc:ProfileID",
      "message": "Invalid BIS profile. Expected: urn:fdc:peppol.eu:2017:poacc:billing:01:1.0"
    }
  ]
}

Step 2: Create Document from UBL

Once validation passes, create the document:
curl -X POST "https://api.e-invoice.be/api/documents/ubl" \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/xml" \
     --data-binary @invoice.xml

Response

{
  "id": "doc_abc123",
  "type": "INVOICE",
  "state": "DRAFT",
  "invoice_number": "INV-2024-001",
  "issue_date": "2024-10-24",
  "total_amount": 1210.00,
  "tax_amount": 210.00,
  "created_at": 1729468923,
  "updated_at": 1729468923
}

Step 3: Send via Peppol

Send the document using the document ID:
curl -X POST "https://api.e-invoice.be/api/documents/doc_abc123/send" \
     -H "Authorization: Bearer YOUR_API_KEY"

Response

{
  "id": "doc_abc123",
  "state": "TRANSIT",
  "message": "Document queued for transmission via Peppol"
}

Peppol ID Routing

By default, sender and receiver Peppol IDs are automatically derived from the company identifiers in your document, regardless of any endpoint IDs specified in the UBL XML:
  • Derived from company tax IDs (vendor_tax_id / customer_tax_id) OR company IDs (vendor_id / customer_id)
  • For Belgian companies: 0208 scheme is always used (Belgian government requirement)
  • Example: Tax ID BE1018265814 → Peppol ID 0208:1018265814
UBL Endpoint IDs are ignored: Even if your UBL document contains specific endpoint IDs (e.g., <cbc:EndpointID schemeID="0088">1234567890123</cbc:EndpointID>), these are not used for routing. The API derives Peppol IDs from the company identifiers in the document metadata instead.
Best Practice: Always explicitly specify sender and receiver Peppol IDs using query parameters to ensure documents are routed to the correct endpoints. This is especially important when sending UBL documents, as endpoint IDs within the UBL XML are ignored.
To route to a specific Peppol endpoint, explicitly provide the Peppol IDs via query parameters:
curl -X POST "https://api.e-invoice.be/api/documents/doc_abc123/send?sender_peppol_scheme=0208&sender_peppol_id=1018265814&receiver_peppol_scheme=0088&receiver_peppol_id=1234567890123" \
     -H "Authorization: Bearer YOUR_API_KEY"
Available query parameters:
ParameterDescriptionExample
sender_peppol_schemeSender’s Peppol scheme ID0208
sender_peppol_idSender’s Peppol identifier1018265814
receiver_peppol_schemeReceiver’s Peppol scheme ID0088
receiver_peppol_idReceiver’s Peppol identifier1234567890123
Common Peppol schemes:
  • 0208 - Belgian enterprise number (BE)
  • 0088 - Global Location Number (GLN)
  • 0106 - Dutch KVK
Always verify the recipient is registered at the specified Peppol endpoint before sending. Use /api/validate/peppol-id?peppol_id=scheme:identifier to confirm registration and prevent delivery failures.

Example UBL Invoice

Here’s a minimal valid UBL invoice:
<?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: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>INV-2024-001</cbc:ID>
  <cbc:IssueDate>2024-10-24</cbc:IssueDate>
  <cbc:DueDate>2024-11-24</cbc:DueDate>
  <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
  <cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>

  <!-- Supplier -->
  <cac:AccountingSupplierParty>
    <cac:Party>
      <cbc:EndpointID schemeID="0208">0123456789</cbc:EndpointID>
      <cac:PartyName>
        <cbc:Name>Your Company</cbc:Name>
      </cac:PartyName>
      <cac:PostalAddress>
        <cbc:StreetName>Main Street</cbc:StreetName>
        <cbc:BuildingNumber>123</cbc:BuildingNumber>
        <cbc:CityName>Brussels</cbc:CityName>
        <cbc:PostalZone>1000</cbc:PostalZone>
        <cac:Country>
          <cbc:IdentificationCode>BE</cbc:IdentificationCode>
        </cac:Country>
      </cac:PostalAddress>
      <cac:PartyLegalEntity>
        <cbc:RegistrationName>Your Company BVBA</cbc:RegistrationName>
        <cbc:CompanyID schemeID="0208">0123456789</cbc:CompanyID>
      </cac:PartyLegalEntity>
    </cac:Party>
  </cac:AccountingSupplierParty>

  <!-- Customer -->
  <cac:AccountingCustomerParty>
    <cac:Party>
      <cbc:EndpointID schemeID="0208">0987654321</cbc:EndpointID>
      <cac:PartyName>
        <cbc:Name>Customer Company</cbc:Name>
      </cac:PartyName>
      <cac:PostalAddress>
        <cbc:StreetName>Customer Lane</cbc:StreetName>
        <cbc:BuildingNumber>456</cbc:BuildingNumber>
        <cbc:CityName>Antwerp</cbc:CityName>
        <cbc:PostalZone>2000</cbc:PostalZone>
        <cac:Country>
          <cbc:IdentificationCode>BE</cbc:IdentificationCode>
        </cac:Country>
      </cac:PostalAddress>
      <cac:PartyLegalEntity>
        <cbc:RegistrationName>Customer Company NV</cbc:RegistrationName>
        <cbc:CompanyID schemeID="0208">0987654321</cbc:CompanyID>
      </cac:PartyLegalEntity>
    </cac:Party>
  </cac:AccountingCustomerParty>

  <!-- Totals -->
  <cac:LegalMonetaryTotal>
    <cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount>
    <cbc:TaxExclusiveAmount currencyID="EUR">1000.00</cbc:TaxExclusiveAmount>
    <cbc:TaxInclusiveAmount currencyID="EUR">1210.00</cbc:TaxInclusiveAmount>
    <cbc:PayableAmount currencyID="EUR">1210.00</cbc:PayableAmount>
  </cac:LegalMonetaryTotal>

  <!-- Tax Total -->
  <cac:TaxTotal>
    <cbc:TaxAmount currencyID="EUR">210.00</cbc:TaxAmount>
    <cac:TaxSubtotal>
      <cbc:TaxableAmount currencyID="EUR">1000.00</cbc:TaxableAmount>
      <cbc:TaxAmount currencyID="EUR">210.00</cbc:TaxAmount>
      <cac:TaxCategory>
        <cbc:ID>S</cbc:ID>
        <cbc:Percent>21.0</cbc:Percent>
        <cac:TaxScheme>
          <cbc:ID>VAT</cbc:ID>
        </cac:TaxScheme>
      </cac:TaxCategory>
    </cac:TaxSubtotal>
  </cac:TaxTotal>

  <!-- Line Items -->
  <cac:InvoiceLine>
    <cbc:ID>1</cbc:ID>
    <cbc:InvoicedQuantity unitCode="C62">10</cbc:InvoicedQuantity>
    <cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount>
    <cac:Item>
      <cbc:Description>Professional Services</cbc:Description>
      <cbc:Name>Professional Services</cbc:Name>
      <cac:ClassifiedTaxCategory>
        <cbc:ID>S</cbc:ID>
        <cbc:Percent>21.0</cbc:Percent>
        <cac:TaxScheme>
          <cbc:ID>VAT</cbc:ID>
        </cac:TaxScheme>
      </cac:ClassifiedTaxCategory>
    </cac:Item>
    <cac:Price>
      <cbc:PriceAmount currencyID="EUR">100.00</cbc:PriceAmount>
    </cac:Price>
  </cac:InvoiceLine>

</Invoice>

Code Examples

Node.js

const axios = require('axios');
const fs = require('fs');

const api = axios.create({
  baseURL: 'https://api.e-invoice.be',
  headers: {
    'Authorization': `Bearer ${process.env.E_INVOICE_API_KEY}`
  }
});

async function sendUBLInvoice(ublFilePath) {
  try {
    // Read UBL file
    const ublXml = fs.readFileSync(ublFilePath, 'utf8');

    // 1. Validate UBL
    console.log('Validating UBL...');
    const validation = await api.post('/api/validate/ubl', ublXml, {
      headers: { 'Content-Type': 'application/xml' }
    });

    if (!validation.data.valid) {
      console.error('UBL validation failed:', validation.data.errors);
      return;
    }

    console.log('✓ UBL is valid');

    // 2. Create document from UBL
    console.log('Creating document...');
    const document = await api.post('/api/documents/ubl', ublXml, {
      headers: { 'Content-Type': 'application/xml' }
    });

    console.log('✓ Document created:', document.data.id);

    // 3. Send via Peppol
    console.log('Sending document...');
    const result = await api.post(`/api/documents/${document.data.id}/send`);
    console.log('✓ Document sent:', result.data.state);

  } catch (error) {
    console.error('Error:', error.response?.data || error.message);
  }
}

// Usage
sendUBLInvoice('./invoices/invoice.xml');

Python

import requests
import os

API_KEY = os.environ.get('E_INVOICE_API_KEY')
BASE_URL = 'https://api.e-invoice.be'

headers = {
    'Authorization': f'Bearer {API_KEY}'
}

def send_ubl_invoice(ubl_file_path):
    try:
        # Read UBL file
        with open(ubl_file_path, 'r', encoding='utf-8') as file:
            ubl_xml = file.read()

        # 1. Validate UBL
        print('Validating UBL...')
        validation = requests.post(
            f'{BASE_URL}/api/validate/ubl',
            data=ubl_xml,
            headers={**headers, 'Content-Type': 'application/xml'}
        )

        if not validation.json().get('valid'):
            print('UBL validation failed:', validation.json().get('errors'))
            return

        print('✓ UBL is valid')

        # 2. Create document
        print('Creating document...')
        document = requests.post(
            f'{BASE_URL}/api/documents/ubl',
            data=ubl_xml,
            headers={**headers, 'Content-Type': 'application/xml'}
        )

        doc_id = document.json()['id']
        print(f'✓ Document created: {doc_id}')

        # 3. Send via Peppol
        print('Sending document...')
        result = requests.post(
            f'{BASE_URL}/api/documents/{doc_id}/send',
            headers=headers
        )

        print(f'✓ Document sent: {result.json()["state"]}')

    except Exception as error:
        print(f'Error: {error}')

# Usage
send_ubl_invoice('./invoices/invoice.xml')

Retrieving UBL from Created Documents

If you created a document via JSON and want to retrieve the generated UBL:
curl -X GET "https://api.e-invoice.be/api/documents/{document_id}/ubl" \
     -H "Authorization: Bearer YOUR_API_KEY"
This returns the UBL XML that was generated from your JSON payload.

Common UBL Validation Errors

Missing Peppol Profile

Error:
{
  "field": "cbc:ProfileID",
  "message": "Missing or invalid ProfileID"
}
Fix: Add the correct profile ID:
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>

Invalid Customization ID

Error:
{
  "field": "cbc:CustomizationID",
  "message": "Invalid CustomizationID for Peppol BIS Billing 3.0"
}
Fix: Use the correct customization ID:
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID>

Missing Endpoint ID

Error:
{
  "field": "cac:AccountingSupplierParty/cac:Party/cbc:EndpointID",
  "message": "Missing Peppol endpoint ID"
}
Fix: Add endpoint IDs for both parties:
<cbc:EndpointID schemeID="0208">0123456789</cbc:EndpointID>

Invalid Tax Category

Error:
{
  "field": "cac:TaxCategory/cbc:ID",
  "message": "Invalid tax category code"
}
Fix: Use valid UNCL5305 codes (S, E, Z, etc.):
<cac:TaxCategory>
  <cbc:ID>S</cbc:ID>
  <cbc:Percent>21.0</cbc:Percent>
</cac:TaxCategory>

Best Practices

Use /api/validate/ubl to catch errors early:
# Validate first
curl -X POST "https://api.e-invoice.be/api/validate/ubl" \
     --data-binary @invoice.xml

# Only create if validation passes
curl -X POST "https://api.e-invoice.be/api/documents/ubl" \
     --data-binary @invoice.xml
Ensure all UBL namespaces are correctly declared:
<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">
Before sending, verify customer Peppol IDs:
curl "https://api.e-invoice.be/api/validate/peppol-id?peppol_id=0208:0987654321"
For large UBL files:
  • Use streaming when reading files
  • Consider compressing before transmission
  • Check file size limits in your HTTP client
Keep a copy of the original UBL for audit purposes:
// Before sending
const backup = `./backup/${invoiceNumber}.xml`;
fs.copyFileSync(ublFilePath, backup);

UBL Credit Notes

Credit notes follow the same process but use the CreditNote element:
<?xml version="1.0" encoding="UTF-8"?>
<CreditNote xmlns="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-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:01:1.0</cbc:ProfileID>

  <cbc:ID>CN-2024-001</cbc:ID>
  <cbc:IssueDate>2024-10-24</cbc:IssueDate>
  <cbc:CreditNoteTypeCode>381</cbc:CreditNoteTypeCode>
  <cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>

  <!-- Reference to original invoice -->
  <cac:BillingReference>
    <cac:InvoiceDocumentReference>
      <cbc:ID>INV-2024-001</cbc:ID>
    </cac:InvoiceDocumentReference>
  </cac:BillingReference>

  <!-- Rest of credit note structure -->

</CreditNote>

Resources

Next Steps