> ## Documentation Index
> Fetch the complete documentation index at: https://docs.e-invoice.be/llms.txt
> Use this file to discover all available pages before exploring further.

# Sending UBL Documents

> Learn how to send invoices from pre-generated UBL XML files

## 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

<Note>
  Use the `/api/validate/ubl` endpoint to validate your UBL XML before creating documents.
</Note>

## 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. **Both `/api/validate/ubl` and `/api/documents/ubl` accept `multipart/form-data` with a `file` field — not a raw XML body.**

```bash theme={null}
curl -X POST "https://api.e-invoice.be/api/validate/ubl" \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -F "file=@invoice.xml"
```

<Warning>
  Do not use `-H "Content-Type: application/xml" --data-binary @invoice.xml` — that returns `422 Field required: body.file`. See the [validation guide](/guides/validation#validating-ubl-xml) for the full request contract and copy-paste examples in Node.js, Python, C#, and PHP.
</Warning>

### Validation Response

**Valid UBL**:

```json theme={null}
{
  "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
  "file_name": "invoice.xml",
  "is_valid": true,
  "issues": []
}
```

**Invalid UBL**:

```json theme={null}
{
  "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
  "file_name": "invoice.xml",
  "is_valid": false,
  "issues": [
    {
      "message": "Belgian enterprise number MUST be stated in the correct format.",
      "type": "error",
      "rule_id": "PEPPOL-COMMON-R043",
      "flag": "fatal",
      "schematron": "PEPPOL-EN16931"
    }
  ]
}
```

## Step 2: Create Document from UBL

Once validation passes, create the document. Same contract: `multipart/form-data` with a `file` field.

```bash theme={null}
curl -X POST "https://api.e-invoice.be/api/documents/ubl" \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -F "file=@invoice.xml"
```

### Response

```json theme={null}
{
  "id": "doc_abc123",
  "document_type": "INVOICE",
  "state": "DRAFT",
  "direction": "OUTBOUND",
  "invoice_id": "INV-2024-001",
  "invoice_date": "2024-10-24",
  "invoice_total": "1210.00",
  "total_tax": "210.00",
  "created_at": "2024-10-24T12:00:00Z"
}
```

## Step 3: Send via Peppol

Send the document using the document ID:

```bash theme={null}
curl -X POST "https://api.e-invoice.be/api/documents/doc_abc123/send" \
     -H "Authorization: Bearer YOUR_API_KEY"
```

### Response

```json theme={null}
{
  "id": "doc_abc123",
  "document_type": "INVOICE",
  "state": "TRANSIT",
  "direction": "OUTBOUND"
}
```

### 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`

<Note>
  **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.
</Note>

### Explicitly Specifying Peppol IDs (Recommended)

<Warning>
  **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.
</Warning>

To route to a specific Peppol endpoint, explicitly provide the Peppol IDs via query parameters:

```bash theme={null}
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**:

| Parameter                | Description                  | Example         |
| ------------------------ | ---------------------------- | --------------- |
| `sender_peppol_scheme`   | Sender's Peppol scheme ID    | `0208`          |
| `sender_peppol_id`       | Sender's Peppol identifier   | `1018265814`    |
| `receiver_peppol_scheme` | Receiver's Peppol scheme ID  | `0088`          |
| `receiver_peppol_id`     | Receiver's Peppol identifier | `1234567890123` |

**Common Peppol schemes**:

* `0208` - Belgian enterprise number (BE)
* `0088` - Global Location Number (GLN)
* `0106` - Dutch KVK

<Tip>
  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.
</Tip>

## Example UBL Invoice

Here's a minimal valid UBL invoice:

```xml theme={null}
<?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

```javascript theme={null}
const axios = require('axios');
const fs = require('fs');
const FormData = require('form-data');

const BASE_URL = 'https://api.e-invoice.be';
const authHeader = { Authorization: `Bearer ${process.env.E_INVOICE_API_KEY}` };

// Both /api/validate/ubl and /api/documents/ubl take multipart/form-data
// with a single "file" field. Always create a fresh FormData per request —
// the underlying stream is consumed on send.
function uploadXml(path) {
  const form = new FormData();
  form.append('file', fs.createReadStream(path));
  return form;
}

async function sendUBLInvoice(ublFilePath) {
  try {
    // 1. Validate UBL
    console.log('Validating UBL...');
    const validateForm = uploadXml(ublFilePath);
    const validation = await axios.post(
      `${BASE_URL}/api/validate/ubl`,
      validateForm,
      { headers: { ...authHeader, ...validateForm.getHeaders() } }
    );

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

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

    // 2. Create document from UBL
    console.log('Creating document...');
    const createForm = uploadXml(ublFilePath);
    const document = await axios.post(
      `${BASE_URL}/api/documents/ubl`,
      createForm,
      { headers: { ...authHeader, ...createForm.getHeaders() } }
    );

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

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

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

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

### Python

```python theme={null}
import os
import requests

API_KEY = os.environ['E_INVOICE_API_KEY']
BASE_URL = 'https://api.e-invoice.be'
HEADERS = {'Authorization': f'Bearer {API_KEY}'}

# Both /api/validate/ubl and /api/documents/ubl take multipart/form-data
# with a single "file" field. requests builds the multipart envelope when
# you pass `files=`; do NOT also set Content-Type — requests sets it
# (with the boundary) for you.
def send_ubl_invoice(ubl_file_path):
    try:
        # 1. Validate UBL
        print('Validating UBL...')
        with open(ubl_file_path, 'rb') as f:
            validation = requests.post(
                f'{BASE_URL}/api/validate/ubl',
                headers=HEADERS,
                files={'file': (os.path.basename(ubl_file_path), f, 'application/xml')},
            )

        if not validation.json().get('is_valid'):
            print('UBL validation failed:', validation.json().get('issues'))
            return

        print('✓ UBL is valid')

        # 2. Create document
        print('Creating document...')
        with open(ubl_file_path, 'rb') as f:
            document = requests.post(
                f'{BASE_URL}/api/documents/ubl',
                headers=HEADERS,
                files={'file': (os.path.basename(ubl_file_path), f, '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:

```bash theme={null}
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**:

```json theme={null}
{
  "field": "cbc:ProfileID",
  "message": "Missing or invalid ProfileID"
}
```

**Fix**: Add the correct profile ID:

```xml theme={null}
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
```

### Invalid Customization ID

**Error**:

```json theme={null}
{
  "field": "cbc:CustomizationID",
  "message": "Invalid CustomizationID for Peppol BIS Billing 3.0"
}
```

**Fix**: Use the correct customization ID:

```xml theme={null}
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID>
```

### Missing Endpoint ID

**Error**:

```json theme={null}
{
  "field": "cac:AccountingSupplierParty/cac:Party/cbc:EndpointID",
  "message": "Missing Peppol endpoint ID"
}
```

**Fix**: Add endpoint IDs for both parties:

```xml theme={null}
<cbc:EndpointID schemeID="0208">0123456789</cbc:EndpointID>
```

### Invalid Tax Category

**Error**:

```json theme={null}
{
  "field": "cac:TaxCategory/cbc:ID",
  "message": "Invalid tax category code"
}
```

**Fix**: Use valid UNCL5305 codes (S, E, Z, etc.):

```xml theme={null}
<cac:TaxCategory>
  <cbc:ID>S</cbc:ID>
  <cbc:Percent>21.0</cbc:Percent>
</cac:TaxCategory>
```

## Best Practices

<AccordionGroup>
  <Accordion title="Always Validate Before Creating">
    Use `/api/validate/ubl` to catch errors early. Both endpoints take `multipart/form-data` with a `file` field — use `-F` (curl) / `files=` (requests) / `FormData` (axios), not raw XML bodies.

    ```bash theme={null}
    # Validate first
    curl -X POST "https://api.e-invoice.be/api/validate/ubl" \
         -H "Authorization: Bearer YOUR_API_KEY" \
         -F "file=@invoice.xml"

    # Only create if validation passes
    curl -X POST "https://api.e-invoice.be/api/documents/ubl" \
         -H "Authorization: Bearer YOUR_API_KEY" \
         -F "file=@invoice.xml"
    ```
  </Accordion>

  <Accordion title="Use Correct Namespaces">
    Ensure all UBL namespaces are correctly declared:

    ```xml theme={null}
    <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">
    ```
  </Accordion>

  <Accordion title="Verify Peppol IDs">
    Before sending, verify customer Peppol IDs:

    ```bash theme={null}
    curl "https://api.e-invoice.be/api/validate/peppol-id?peppol_id=0208:0987654321"
    ```
  </Accordion>

  <Accordion title="Handle Large Files Efficiently">
    For large UBL files:

    * Use streaming when reading files
    * Consider compressing before transmission
    * Check file size limits in your HTTP client
  </Accordion>

  <Accordion title="Store Original UBL">
    Keep a copy of the original UBL for audit purposes:

    ```javascript theme={null}
    // Before sending
    const backup = `./backup/${invoiceNumber}.xml`;
    fs.copyFileSync(ublFilePath, backup);
    ```
  </Accordion>
</AccordionGroup>

## UBL Credit Notes

Credit notes follow the same process but use the CreditNote element:

```xml theme={null}
<?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

* [Peppol BIS Billing 3.0 Specification](https://docs.peppol.eu/poacc/billing/3.0/)
* [UBL 2.1 Technical Documentation](http://docs.oasis-open.org/ubl/UBL-2.1.html)
* [EN 16931 Standard](https://ec.europa.eu/digital-building-blocks/sites/spaces/DIGITAL/pages/467108950/EN+16931+compliance)

## Next Steps

<CardGroup cols={2}>
  <Card title="Creating from PDF" icon="file-pdf" href="/guides/pdf-documents">
    Learn how to create documents from PDF files
  </Card>

  <Card title="Creating Invoices" icon="file-invoice" href="/guides/creating-invoices">
    Create invoices from JSON
  </Card>

  <Card title="Validation Guide" icon="check-circle" href="/guides/validation">
    Test invoice data
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference">
    Explore all endpoints
  </Card>
</CardGroup>
