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

# Send UBL documents

> Validate, create and send an invoice or credit note from a UBL XML file that your system generates.

## Overview

If your system generates [UBL](/glossary#ubl) XML files, you can send them through e-invoice.be without a conversion to JSON. Use this method when:

* Your ERP system generates UBL XML.
* You migrate from a different Peppol Access Point.
* You have existing UBL files to send.
* You want full control of the UBL structure.

<Tip>
  If your ERP can only send invoices by email, see [Send invoices by email (Mailbox)](/guides/mailbox).
</Tip>

## Prerequisites

Your UBL XML must:

* Be in the Peppol BIS Billing 3.0 format.
* Be an invoice (`Invoice`) or a [credit note](/guides/credit-notes) (`CreditNote`).
* Have your company as the supplier. The send request returns `409` if the sender Peppol ID is not one of the `peppol_ids` of your company.

The three steps are:

1. Validate the UBL XML (recommended while you develop).
2. Create the document from the UBL file.
3. Send the document.

<Note>
  `POST /api/validate/ubl` and `POST /api/documents/ubl` accept `multipart/form-data` with one form field with the name `file`. A raw XML body (`Content-Type: application/xml`) gives `422` with the message `Field required` for `body.file`.
</Note>

## Validate the UBL XML

Validation does not create a document. `POST /api/validate/ubl` accepts only a file part with the content type `application/xml` or `text/xml`.

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

The [validation guide](/guides/validation#validate-a-ubl-xml-file) has this request in Node.js, Python, PHP and C#.

Response for a valid file (`201`):

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

Response for a file that is not valid (`201`, `is_valid` is `false`):

```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-UBL"
    }
  ]
}
```

## Create the document from UBL

`POST /api/documents/ubl` validates the file again and creates the document in state `DRAFT`. If the file has a fatal validation issue, the request returns `415` and no issue list. Thus use `POST /api/validate/ubl` to find the cause.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.e-invoice.be/api/documents/ubl" \
    -H "Authorization: Bearer $E_INVOICE_API_KEY" \
    -F "file=@invoice.xml;type=application/xml"
  ```

  ```javascript Node.js theme={null}
  import { readFile } from "node:fs/promises";

  const form = new FormData();
  form.append(
    "file",
    new Blob([await readFile("invoice.xml")], { type: "application/xml" }),
    "invoice.xml",
  );

  // Do not set the Content-Type header. fetch adds it with the multipart boundary.
  const response = await fetch("https://api.e-invoice.be/api/documents/ubl", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.E_INVOICE_API_KEY}` },
    body: form,
  });

  const document = await response.json();
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${JSON.stringify(document)}`);
  }
  console.log(document.id, document.state);
  ```

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

  import requests

  # Do not set the Content-Type header. requests adds it with the multipart boundary.
  with open("invoice.xml", "rb") as f:
      response = requests.post(
          "https://api.e-invoice.be/api/documents/ubl",
          headers={"Authorization": f"Bearer {os.environ['E_INVOICE_API_KEY']}"},
          files={"file": ("invoice.xml", f, "application/xml")},
          timeout=60,
      )

  response.raise_for_status()
  document = response.json()
  print(document["id"], document["state"])
  ```

  ```php PHP theme={null}
  <?php
  $ch = curl_init("https://api.e-invoice.be/api/documents/ubl");
  curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("E_INVOICE_API_KEY")],
      // An array as body makes the request multipart/form-data.
      CURLOPT_POSTFIELDS => [
          "file" => new CURLFile("invoice.xml", "application/xml", "invoice.xml"),
      ],
  ]);

  $body = curl_exec($ch);
  $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);

  if ($status !== 201) {
      throw new RuntimeException("HTTP $status: $body");
  }
  $document = json_decode($body, true);
  echo $document["id"] . " " . $document["state"] . PHP_EOL;
  ```

  ```csharp C# theme={null}
  using System.Net.Http.Headers;

  using var client = new HttpClient();
  client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
      "Bearer", Environment.GetEnvironmentVariable("E_INVOICE_API_KEY"));

  var file = new ByteArrayContent(await File.ReadAllBytesAsync("invoice.xml"));
  file.Headers.ContentType = new MediaTypeHeaderValue("application/xml");

  using var form = new MultipartFormDataContent();
  form.Add(file, "file", "invoice.xml");

  var response = await client.PostAsync("https://api.e-invoice.be/api/documents/ubl", form);
  var body = await response.Content.ReadAsStringAsync();
  if (!response.IsSuccessStatusCode)
  {
      throw new HttpRequestException($"HTTP {(int)response.StatusCode}: {body}");
  }
  Console.WriteLine(body);
  ```
</CodeGroup>

Response (`201`, shortened; the full response also has the line items, the tax details and the attachments):

```json theme={null}
{
  "id": "doc-1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
  "created_at": "2026-10-01T12:00:00Z",
  "document_type": "INVOICE",
  "state": "DRAFT",
  "direction": "OUTBOUND",
  "invoice_id": "INV-2026-001",
  "invoice_date": "2026-10-01",
  "due_date": "2026-10-31",
  "currency": "EUR",
  "vendor_name": "E-INVOICE BV",
  "vendor_tax_id": "BE1018265814",
  "customer_name": "OpenPeppol VZW",
  "customer_tax_id": "BE0848934496",
  "subtotal": "1000.00",
  "total_tax": "210.00",
  "invoice_total": "1210.00"
}
```

Files that are embedded in the UBL become attachments of the document. See [Attachments and PDF](/guides/attachments).

## Send the document

Send the document with the `id` from the create response. The samples read the ID from the environment variable `DOCUMENT_ID`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.e-invoice.be/api/documents/$DOCUMENT_ID/send?sender_peppol_scheme=0208&sender_peppol_id=1018265814&receiver_peppol_scheme=0208&receiver_peppol_id=0848934496" \
    -H "Authorization: Bearer $E_INVOICE_API_KEY"
  ```

  ```javascript Node.js theme={null}
  const documentId = process.env.DOCUMENT_ID; // the "id" from the create response

  const query = new URLSearchParams({
    sender_peppol_scheme: "0208",
    sender_peppol_id: "1018265814",
    receiver_peppol_scheme: "0208",
    receiver_peppol_id: "0848934496",
  });

  const response = await fetch(
    `https://api.e-invoice.be/api/documents/${documentId}/send?${query}`,
    {
      method: "POST",
      headers: { Authorization: `Bearer ${process.env.E_INVOICE_API_KEY}` },
    },
  );

  const result = await response.json();
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${JSON.stringify(result)}`);
  }
  console.log(result.state);
  ```

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

  import requests

  document_id = os.environ["DOCUMENT_ID"]  # the "id" from the create response

  response = requests.post(
      f"https://api.e-invoice.be/api/documents/{document_id}/send",
      headers={"Authorization": f"Bearer {os.environ['E_INVOICE_API_KEY']}"},
      params={
          "sender_peppol_scheme": "0208",
          "sender_peppol_id": "1018265814",
          "receiver_peppol_scheme": "0208",
          "receiver_peppol_id": "0848934496",
      },
      timeout=30,
  )
  if not response.ok:
      raise SystemExit(f"HTTP {response.status_code}: {response.text}")
  print(response.json()["state"])
  ```

  ```php PHP theme={null}
  <?php
  $documentId = getenv("DOCUMENT_ID"); // the "id" from the create response

  $query = http_build_query([
      "sender_peppol_scheme" => "0208",
      "sender_peppol_id" => "1018265814",
      "receiver_peppol_scheme" => "0208",
      "receiver_peppol_id" => "0848934496",
  ]);

  $ch = curl_init("https://api.e-invoice.be/api/documents/$documentId/send?$query");
  curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
          "Authorization: Bearer " . getenv("E_INVOICE_API_KEY"),
      ],
  ]);

  $body = curl_exec($ch);
  $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);

  if ($status >= 400) {
      throw new RuntimeException("HTTP $status: $body");
  }
  echo json_decode($body, true)["state"], PHP_EOL;
  ```

  ```csharp C# theme={null}
  using System.Net.Http.Headers;

  var documentId = Environment.GetEnvironmentVariable("DOCUMENT_ID"); // the "id" from the create response

  using var client = new HttpClient { BaseAddress = new Uri("https://api.e-invoice.be") };
  client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
      "Bearer", Environment.GetEnvironmentVariable("E_INVOICE_API_KEY"));

  var query = "sender_peppol_scheme=0208&sender_peppol_id=1018265814&receiver_peppol_scheme=0208&receiver_peppol_id=0848934496";

  var response = await client.PostAsync($"/api/documents/{documentId}/send?{query}", null);

  var body = await response.Content.ReadAsStringAsync();
  if (!response.IsSuccessStatusCode)
  {
      throw new HttpRequestException($"HTTP {(int)response.StatusCode}: {body}");
  }
  Console.WriteLine(body);
  ```
</CodeGroup>

Response (`200`, shortened):

```json theme={null}
{
  "id": "doc-1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
  "created_at": "2026-10-01T12:00:00Z",
  "document_type": "INVOICE",
  "state": "TRANSIT",
  "direction": "OUTBOUND",
  "invoice_id": "INV-2026-001"
}
```

To follow the document after the send, see [Document lifecycle and delivery tracking](/guides/document-lifecycle).

### Peppol ID routing

When you create a document from UBL, the API stores the `cbc:EndpointID` values of the supplier and the customer with the document. The send request uses these values if you do not give query parameters.

When you send a document, the API finds the sender and receiver Peppol IDs in this order:

1. The query parameters of the send request.
2. The Peppol IDs that the API stored with the UBL of the document.
3. The identifiers in the document: `vendor_tax_id` or `vendor_company_id` for the sender, and `customer_peppol_id`, `customer_tax_id` or `customer_company_id` for the receiver.

For a Belgian party, the API derives scheme `0208` and the enterprise number: the tax ID `BE1018265814` gives the Peppol ID `0208:1018265814`. For other countries, the derived scheme can be different from the scheme that the receiver registered. Thus the best practice is to give all four query parameters:

```bash cURL theme={null}
curl -X POST "https://api.e-invoice.be/api/documents/$DOCUMENT_ID/send?sender_peppol_scheme=0208&sender_peppol_id=1018265814&receiver_peppol_scheme=0208&receiver_peppol_id=0848934496" \
  -H "Authorization: Bearer $E_INVOICE_API_KEY"
```

<ParamField query="sender_peppol_scheme" type="string">
  Scheme of the sender Peppol ID. Example: `0208`.
</ParamField>

<ParamField query="sender_peppol_id" type="string">
  Identifier of the sender, without the scheme. Example: `1018265814`.
</ParamField>

<ParamField query="receiver_peppol_scheme" type="string">
  Scheme of the receiver Peppol ID. Example: `0208`.
</ParamField>

<ParamField query="receiver_peppol_id" type="string">
  Identifier of the receiver, without the scheme. Example: `0848934496`.
</ParamField>

The API returns `400` if it cannot find a complete sender and receiver Peppol ID. It returns `409` if the sender Peppol ID is not one of the `peppol_ids` of your company.

<Tip>
  Before you send, make sure that the receiver is registered on the Peppol network with the Peppol ID that you use. See [Look up Peppol participants](/guides/lookup-participants).
</Tip>

## Example UBL invoice

This is a minimal invoice in the Peppol BIS Billing 3.0 format. Replace the supplier data with the data of your company.

```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-2026-001</cbc:ID>
  <cbc:IssueDate>2026-10-01</cbc:IssueDate>
  <cbc:DueDate>2026-10-31</cbc:DueDate>
  <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
  <cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
  <cbc:BuyerReference>PO-12345</cbc:BuyerReference>

  <!-- Supplier -->
  <cac:AccountingSupplierParty>
    <cac:Party>
      <cbc:EndpointID schemeID="0208">1018265814</cbc:EndpointID>
      <cac:PartyName>
        <cbc:Name>E-INVOICE BV</cbc:Name>
      </cac:PartyName>
      <cac:PostalAddress>
        <cbc:StreetName>Brusselsesteenweg 119/A</cbc:StreetName>
        <cbc:CityName>Zemst</cbc:CityName>
        <cbc:PostalZone>1980</cbc:PostalZone>
        <cac:Country>
          <cbc:IdentificationCode>BE</cbc:IdentificationCode>
        </cac:Country>
      </cac:PostalAddress>
      <cac:PartyTaxScheme>
        <cbc:CompanyID>BE1018265814</cbc:CompanyID>
        <cac:TaxScheme>
          <cbc:ID>VAT</cbc:ID>
        </cac:TaxScheme>
      </cac:PartyTaxScheme>
      <cac:PartyLegalEntity>
        <cbc:RegistrationName>E-INVOICE BV</cbc:RegistrationName>
        <cbc:CompanyID schemeID="0208">1018265814</cbc:CompanyID>
      </cac:PartyLegalEntity>
    </cac:Party>
  </cac:AccountingSupplierParty>

  <!-- Customer -->
  <cac:AccountingCustomerParty>
    <cac:Party>
      <cbc:EndpointID schemeID="0208">0848934496</cbc:EndpointID>
      <cac:PartyName>
        <cbc:Name>OpenPeppol VZW</cbc:Name>
      </cac:PartyName>
      <cac:PostalAddress>
        <cbc:StreetName>Robert Schumanplein 6 bus 5</cbc:StreetName>
        <cbc:CityName>Brussel</cbc:CityName>
        <cbc:PostalZone>1040</cbc:PostalZone>
        <cac:Country>
          <cbc:IdentificationCode>BE</cbc:IdentificationCode>
        </cac:Country>
      </cac:PostalAddress>
      <cac:PartyTaxScheme>
        <cbc:CompanyID>BE0848934496</cbc:CompanyID>
        <cac:TaxScheme>
          <cbc:ID>VAT</cbc:ID>
        </cac:TaxScheme>
      </cac:PartyTaxScheme>
      <cac:PartyLegalEntity>
        <cbc:RegistrationName>OpenPeppol VZW</cbc:RegistrationName>
        <cbc:CompanyID schemeID="0208">0848934496</cbc:CompanyID>
      </cac:PartyLegalEntity>
    </cac:Party>
  </cac:AccountingCustomerParty>

  <!-- 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</cbc:Percent>
        <cac:TaxScheme>
          <cbc:ID>VAT</cbc:ID>
        </cac:TaxScheme>
      </cac:TaxCategory>
    </cac:TaxSubtotal>
  </cac:TaxTotal>

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

  <!-- Invoice lines -->
  <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:Name>Professional services</cbc:Name>
      <cac:ClassifiedTaxCategory>
        <cbc:ID>S</cbc:ID>
        <cbc:Percent>21</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>
```

## UBL credit notes

A credit note uses the same three steps. The root element is `CreditNote`, and the type code element is `cbc:CreditNoteTypeCode`:

```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-2026-001</cbc:ID>
  <cbc:IssueDate>2026-10-15</cbc:IssueDate>
  <cbc:CreditNoteTypeCode>381</cbc:CreditNoteTypeCode>
  <cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>

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

  <!-- The parties, totals and cac:CreditNoteLine elements go here -->

</CreditNote>
```

## Complete example

The example validates a UBL file, creates the document and sends it. It needs only the environment variable `E_INVOICE_API_KEY`.

<Accordion title="Validate, create and send a UBL file (Node.js and Python)">
  <CodeGroup>
    ```javascript Node.js theme={null}
    import { readFile } from "node:fs/promises";
    import { basename } from "node:path";

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

    async function call(path, options = {}) {
      const response = await fetch(`${BASE_URL}${path}`, { ...options, headers });
      const body = await response.json();
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${JSON.stringify(body)}`);
      }
      return body;
    }

    // Make a new FormData object for each request.
    async function ublForm(filePath) {
      const form = new FormData();
      form.append(
        "file",
        new Blob([await readFile(filePath)], { type: "application/xml" }),
        basename(filePath),
      );
      return form;
    }

    async function sendUblInvoice(filePath) {
      // 1. Validate the UBL
      const validation = await call("/api/validate/ubl", {
        method: "POST",
        body: await ublForm(filePath),
      });
      if (!validation.is_valid) {
        console.error("UBL validation failed:", validation.issues);
        return null;
      }
      console.log("OK: UBL is valid");

      // 2. Create the document
      const document = await call("/api/documents/ubl", {
        method: "POST",
        body: await ublForm(filePath),
      });
      console.log("OK: document created:", document.id);

      // 3. Send the document
      const sent = await call(`/api/documents/${document.id}/send`, { method: "POST" });
      console.log("OK: document sent, state:", sent.state);
      return sent;
    }

    await sendUblInvoice("./invoices/invoice.xml");
    ```

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

    import requests

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


    def upload(path, ubl_file_path):
        # requests makes the multipart body. Do not set the Content-Type header.
        with open(ubl_file_path, "rb") as f:
            response = requests.post(
                f"{BASE_URL}{path}",
                headers=HEADERS,
                files={"file": (os.path.basename(ubl_file_path), f, "application/xml")},
                timeout=60,
            )
        response.raise_for_status()
        return response.json()


    def send_ubl_invoice(ubl_file_path):
        # 1. Validate the UBL
        validation = upload("/api/validate/ubl", ubl_file_path)
        if not validation["is_valid"]:
            print("UBL validation failed:", validation["issues"])
            return None
        print("OK: UBL is valid")

        # 2. Create the document
        document = upload("/api/documents/ubl", ubl_file_path)
        print("OK: document created:", document["id"])

        # 3. Send the document
        response = requests.post(
            f"{BASE_URL}/api/documents/{document['id']}/send",
            headers=HEADERS,
            timeout=60,
        )
        response.raise_for_status()
        sent = response.json()
        print("OK: document sent, state:", sent["state"])
        return sent


    send_ubl_invoice("./invoices/invoice.xml")
    ```
  </CodeGroup>
</Accordion>

## Get the UBL of a document

`GET /api/documents/{document_id}/ubl` gives the UBL of a document, also for a document that you created from JSON. The response is JSON with a `signed_url` for the download of the XML file, not the XML itself.

```bash cURL theme={null}
curl "https://api.e-invoice.be/api/documents/$DOCUMENT_ID/ubl" \
  -H "Authorization: Bearer $E_INVOICE_API_KEY"
```

For the response and the lifetime of the URL, see [Attachments and PDF](/guides/attachments#download-the-ubl).

## Errors and frequent validation issues

| Status | `detail` | Cause |
| - | - | - |
| `422` | `Field required` (`body.file`) | The request is not `multipart/form-data` with a `file` field. |
| `422` | `Invalid file type` | `POST /api/validate/ubl`: the content type of the file part is not `application/xml` or `text/xml`. |
| `415` | `UBL file is empty` | `POST /api/documents/ubl`: the file has no content. |
| `415` | `UBL file is not valid and contains errors` | `POST /api/documents/ubl`: the file has a fatal validation issue. Use `POST /api/validate/ubl` to get the issues. |
| `422` | `UBL File could not be parsed` | `POST /api/documents/ubl`: the API could not read the document data from the file. |

Frequent causes of validation issues:

* **Profile ID**: `cbc:ProfileID` must be `urn:fdc:peppol.eu:2017:poacc:billing:01:1.0`.
* **Customisation ID**: `cbc:CustomizationID` must be `urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0`.
* **Endpoint ID**: the supplier party and the customer party each need a `cbc:EndpointID` with a `schemeID` attribute, for example `<cbc:EndpointID schemeID="0208">1018265814</cbc:EndpointID>`.
* **Tax category**: `cac:TaxCategory/cbc:ID` must be a code from the UNCL5305 list (`S`, `E`, `Z`, `AE` and others).
* **Namespaces**: the root element must declare the three UBL namespaces that the example shows.

For all status codes and error formats, see [Errors and troubleshooting](/guides/errors).

## Best practices

* Validate with `POST /api/validate/ubl` while you develop. The create request rejects a file that is not valid, but it does not return the issues.
* Give the four Peppol ID query parameters on the send request, and make sure that the receiver is registered before you send. See [Look up Peppol participants](/guides/lookup-participants).
* Develop and test with a sandbox company. See [Test mode and sandbox companies](/environments).
* Keep a copy of the original UBL file for your audit records.

## 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="Create documents from PDF" icon="file-pdf" href="/guides/pdf-documents">
    Extract the invoice data from a PDF file
  </Card>

  <Card title="Validation during development" icon="circle-check" href="/guides/validation">
    Validate JSON and UBL while you develop
  </Card>

  <Card title="Look up Peppol participants" icon="magnifying-glass" href="/guides/lookup-participants">
    Make sure that the receiver is registered on Peppol
  </Card>

  <Card title="Errors and troubleshooting" icon="triangle-exclamation" href="/guides/errors">
    Status codes, error formats and retry rules
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.