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"
}

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