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

# Create documents from PDF

> Upload a PDF invoice, get the extracted invoice data and a UBL document, and send the result through Peppol.

## Overview

The API can extract the invoice data from a PDF file and convert it to a UBL document. Use this method when:

* You have PDF invoices that you want to send through Peppol.
* Your system generates PDF invoices but no structured data.
* You migrate from PDF invoicing to e-invoicing.

<Tip>
  If your ERP can only send invoices by email, see [Send invoices by email (Mailbox)](/guides/mailbox). To attach a PDF to a document that you create from JSON, see [Attachments and PDF](/guides/attachments).
</Tip>

## How it works

A PDF upload does not create a document in your company. The API returns the extracted invoice data as JSON, with these fields in addition to the invoice fields:

| Field | Description |
| - | - |
| `success` | `true` if the PDF was converted into a compliant e-invoice. |
| `ubl_document` | The UBL XML as a string. Present only if the API could generate the UBL. |
| `error_message`, `error_type` | The cause when `success` is `false`. |

After the upload, you do these steps:

1. Examine the extracted data. The extraction can contain errors.
2. Create the document: from the `ubl_document` with `POST /api/documents/ubl`, or from the corrected JSON with `POST /api/documents/`.
3. Send the document.

There are two upload methods:

| Method | Endpoint | Use |
| - | - | - |
| Synchronous | `POST /api/documents/pdf` | One request that waits for the result. The request can take some minutes. |
| Asynchronous | `POST /api/conversion/pdf` | Returns a task ID immediately. You poll for the result. Use it when your client cannot keep a connection open for a long time. |

All upload requests use `multipart/form-data` with one form field with the name `file`.

## Synchronous upload

`POST /api/documents/pdf` has two optional query parameters.

<ParamField query="vendor_tax_id" type="string">
  Tax ID of the vendor. If you do not give it, the API uses the tax ID of your company.
</ParamField>

<ParamField query="customer_tax_id" type="string">
  Tax ID of the customer. The API uses it as input for the extraction.
</ParamField>

The samples use the house example: E-INVOICE BV (`BE1018265814`) as vendor and OpenPeppol VZW (`BE0848934496`) as customer. Replace the vendor tax ID with that of your company.

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

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

  const query = new URLSearchParams({
    vendor_tax_id: "BE1018265814",
    customer_tax_id: "BE0848934496",
  });

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

  // 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/pdf?${query}`, {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.E_INVOICE_API_KEY}` },
    body: form,
  });

  const extracted = await response.json();
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${JSON.stringify(extracted)}`);
  }
  console.log(extracted.success, extracted.invoice_id, extracted.invoice_total);
  ```

  ```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.pdf", "rb") as f:
      response = requests.post(
          "https://api.e-invoice.be/api/documents/pdf",
          headers={"Authorization": f"Bearer {os.environ['E_INVOICE_API_KEY']}"},
          params={"vendor_tax_id": "BE1018265814", "customer_tax_id": "BE0848934496"},
          files={"file": ("invoice.pdf", f, "application/pdf")},
          timeout=600,
      )

  response.raise_for_status()
  extracted = response.json()
  print(extracted["success"], extracted.get("invoice_id"), extracted.get("invoice_total"))
  ```

  ```php PHP theme={null}
  <?php
  $query = http_build_query([
      "vendor_tax_id" => "BE1018265814",
      "customer_tax_id" => "BE0848934496",
  ]);

  $ch = curl_init("https://api.e-invoice.be/api/documents/pdf?" . $query);
  curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_TIMEOUT => 600,
      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.pdf", "application/pdf", "invoice.pdf"),
      ],
  ]);

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

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

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

  using var client = new HttpClient { Timeout = TimeSpan.FromMinutes(10) };
  client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
      "Bearer", Environment.GetEnvironmentVariable("E_INVOICE_API_KEY"));

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

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

  var response = await client.PostAsync(
      "https://api.e-invoice.be/api/documents/pdf?vendor_tax_id=BE1018265814&customer_tax_id=BE0848934496",
      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). This block is a response and not a request body. The UBL string is shortened, so do not use the block as input.

```json theme={null}
{
  "success": true,
  "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",
  "items": [
    {
      "description": "Professional services",
      "quantity": "10",
      "unit_price": "100.00",
      "amount": "1000.00",
      "tax_rate": "21.00"
    }
  ],
  "ubl_document": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Invoice>(shortened)</Invoice>"
}
```

<Note>
  The response has no `id`, because the upload does not store a document. The values `state` and `direction` are the values that the document gets when you create it.
</Note>

If the API cannot extract the line items, the response has `success: false`, `error_type: "ExtractionError"` and one placeholder line with the description `Line items could not be extracted from PDF`.

## Create and send the document

If the response has a `ubl_document`, save the string to a file and create the document from it. `POST /api/documents/ubl` accepts `multipart/form-data` with a `file` field, not a raw XML body.

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

The response is the created document in state `DRAFT`, with an `id` such as `doc-1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d`. For this request in five languages and for the response, see [Send UBL documents](/guides/ubl-documents#create-the-document-from-ubl).

If the response has no `ubl_document`, or if the extracted data is not correct, correct the JSON and create the document with `POST /api/documents/`. See [Create e-invoices](/guides/creating-invoices).

Then send the document. The samples read the document 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"
}
```

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

## Asynchronous upload

The asynchronous upload has no tax ID parameters. The API uses the tax ID of your company as the vendor tax ID.

<Steps>
  <Step title="Start the conversion">
    ```bash cURL theme={null}
    curl -X POST "https://api.e-invoice.be/api/conversion/pdf" \
      -H "Authorization: Bearer $E_INVOICE_API_KEY" \
      -F "file=@invoice.pdf;type=application/pdf"
    ```

    Response (`201`):

    ```json theme={null}
    {
      "task_id": "0b5f4b0e-7c1d-4e7a-9d57-2f6c1a3e8b90",
      "status": "pending"
    }
    ```
  </Step>

  <Step title="Poll the status">
    ```bash cURL theme={null}
    curl "https://api.e-invoice.be/api/conversion/pdf/$TASK_ID" \
      -H "Authorization: Bearer $E_INVOICE_API_KEY"
    ```

    While the task is in progress, this request returns `404` with the detail `PDF conversion task not found or expired`. Continue to poll. When the task is complete, the response is `200`:

    ```json theme={null}
    {
      "task_id": "0b5f4b0e-7c1d-4e7a-9d57-2f6c1a3e8b90",
      "status": "completed"
    }
    ```

    The `status` is `completed` or `failed`.
  </Step>

  <Step title="Get the result">
    ```bash cURL theme={null}
    curl "https://api.e-invoice.be/api/conversion/pdf/$TASK_ID/result" \
      -H "Authorization: Bearer $E_INVOICE_API_KEY"
    ```

    The response (`200`) has the same structure as the response of the synchronous upload. For a failed task, `success` is `false` and `error_message` and `error_type` give the cause.
  </Step>
</Steps>

<Warning>
  The API keeps the result for 5 minutes after the task is complete. After that time, the status request and the result request return `404`. Stop the poll loop after a maximum time, because a `404` can mean "not complete" or "expired".
</Warning>

<Accordion title="Upload and poll (Node.js and Python)">
  <CodeGroup>
    ```javascript Node.js theme={null}
    import { readFile } from "node:fs/promises";

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

    async function convertPdf(pdfPath, { intervalMs = 5000, maxWaitMs = 10 * 60 * 1000 } = {}) {
      // 1. Start the conversion
      const form = new FormData();
      form.append(
        "file",
        new Blob([await readFile(pdfPath)], { type: "application/pdf" }),
        "invoice.pdf",
      );
      const start = await fetch(`${BASE_URL}/api/conversion/pdf`, {
        method: "POST",
        headers,
        body: form,
      });
      if (!start.ok) {
        throw new Error(`HTTP ${start.status}: ${await start.text()}`);
      }
      const { task_id: taskId } = await start.json();
      console.log("Conversion started:", taskId);

      // 2. Poll the status. 404 means that the task is not complete.
      const deadline = Date.now() + maxWaitMs;
      while (Date.now() < deadline) {
        await new Promise((resolve) => setTimeout(resolve, intervalMs));

        const status = await fetch(`${BASE_URL}/api/conversion/pdf/${taskId}`, { headers });
        if (status.status === 404) continue;
        if (!status.ok) {
          throw new Error(`HTTP ${status.status}: ${await status.text()}`);
        }

        // 3. Get the result (also for a failed task: it contains the cause)
        const result = await fetch(`${BASE_URL}/api/conversion/pdf/${taskId}/result`, { headers });
        if (!result.ok) {
          throw new Error(`HTTP ${result.status}: ${await result.text()}`);
        }
        return result.json();
      }
      throw new Error(`Conversion ${taskId} was not complete after ${maxWaitMs} ms`);
    }

    const extracted = await convertPdf("./invoice.pdf");
    if (extracted.success) {
      console.log("OK:", extracted.invoice_id, extracted.invoice_total);
    } else {
      console.error("Failed:", extracted.error_type, extracted.error_message);
    }
    ```

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

    import requests

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


    def convert_pdf(pdf_path, interval=5, max_wait=600):
        # 1. Start the conversion
        with open(pdf_path, "rb") as f:
            start = requests.post(
                f"{BASE_URL}/api/conversion/pdf",
                headers=HEADERS,
                files={"file": ("invoice.pdf", f, "application/pdf")},
                timeout=60,
            )
        start.raise_for_status()
        task_id = start.json()["task_id"]
        print("Conversion started:", task_id)

        # 2. Poll the status. 404 means that the task is not complete.
        deadline = time.monotonic() + max_wait
        while time.monotonic() < deadline:
            time.sleep(interval)
            status = requests.get(
                f"{BASE_URL}/api/conversion/pdf/{task_id}", headers=HEADERS, timeout=60
            )
            if status.status_code == 404:
                continue
            status.raise_for_status()

            # 3. Get the result (also for a failed task: it contains the cause)
            result = requests.get(
                f"{BASE_URL}/api/conversion/pdf/{task_id}/result", headers=HEADERS, timeout=60
            )
            result.raise_for_status()
            return result.json()

        raise TimeoutError(f"Conversion {task_id} was not complete after {max_wait} s")


    extracted = convert_pdf("./invoice.pdf")
    if extracted["success"]:
        print("OK:", extracted.get("invoice_id"), extracted.get("invoice_total"))
    else:
        print("Failed:", extracted.get("error_type"), extracted.get("error_message"))
    ```
  </CodeGroup>
</Accordion>

## Complete example

The example uploads a PDF, creates the document from the generated UBL and sends it. It needs only the environment variable `E_INVOICE_API_KEY`. In a production integration, examine the extracted data before the send step.

<Accordion title="From PDF to sent document (Node.js and Python)">
  <CodeGroup>
    ```javascript Node.js theme={null}
    import { readFile } from "node:fs/promises";

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

    async function processAndSendPdf(pdfPath, vendorTaxId, customerTaxId) {
      // 1. Upload the PDF and extract the data
      const query = new URLSearchParams();
      if (vendorTaxId) query.set("vendor_tax_id", vendorTaxId);
      if (customerTaxId) query.set("customer_tax_id", customerTaxId);

      const pdfForm = new FormData();
      pdfForm.append(
        "file",
        new Blob([await readFile(pdfPath)], { type: "application/pdf" }),
        "invoice.pdf",
      );
      const extracted = await call(`/api/documents/pdf?${query}`, {
        method: "POST",
        body: pdfForm,
      });
      console.log("OK: PDF processed:", extracted.invoice_id, extracted.currency, extracted.invoice_total);

      // 2. Stop if no UBL was generated
      if (!extracted.success || !extracted.ubl_document) {
        console.error("Failed: no UBL document.", extracted.error_type, extracted.error_message);
        return null;
      }

      // 3. Create the document from the UBL string (no temporary file is necessary)
      const ublForm = new FormData();
      ublForm.append(
        "file",
        new Blob([extracted.ubl_document], { type: "application/xml" }),
        "invoice.xml",
      );
      const document = await call("/api/documents/ubl", { method: "POST", body: ublForm });
      console.log("OK: document created:", document.id);

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

    // Replace the vendor tax ID with that of your company.
    await processAndSendPdf("./invoice.pdf", "BE1018265814", "BE0848934496");
    ```

    ```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 process_and_send_pdf(pdf_path, vendor_tax_id=None, customer_tax_id=None):
        # 1. Upload the PDF and extract the data
        params = {}
        if vendor_tax_id:
            params["vendor_tax_id"] = vendor_tax_id
        if customer_tax_id:
            params["customer_tax_id"] = customer_tax_id

        with open(pdf_path, "rb") as f:
            response = requests.post(
                f"{BASE_URL}/api/documents/pdf",
                headers=HEADERS,
                params=params,
                files={"file": ("invoice.pdf", f, "application/pdf")},
                timeout=600,
            )
        response.raise_for_status()
        extracted = response.json()
        print("OK: PDF processed:", extracted.get("invoice_id"), extracted.get("invoice_total"))

        # 2. Stop if no UBL was generated
        if not extracted.get("success") or not extracted.get("ubl_document"):
            print("Failed: no UBL document.", extracted.get("error_type"), extracted.get("error_message"))
            return None

        # 3. Create the document from the UBL string (no temporary file is necessary)
        response = requests.post(
            f"{BASE_URL}/api/documents/ubl",
            headers=HEADERS,
            files={
                "file": ("invoice.xml", extracted["ubl_document"].encode("utf-8"), "application/xml")
            },
            timeout=60,
        )
        response.raise_for_status()
        document = response.json()
        print("OK: document created:", document["id"])

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


    # Replace the vendor tax ID with that of your company.
    process_and_send_pdf("./invoice.pdf", "BE1018265814", "BE0848934496")
    ```
  </CodeGroup>
</Accordion>

## PDF requirements

* The file must be a PDF that is not empty and not damaged.
* A text-based PDF (generated by software) gives better results than a scanned document.
* Put one invoice in each PDF.
* Use a clear layout with separate sections for the vendor, the customer and the line items.
* All mandatory invoice fields must be visible in the PDF.

<Note>
  The API does not publish a size limit for these two upload endpoints. Keep the files small, and use the asynchronous upload if your client or proxy has a short timeout.
</Note>

## Troubleshooting

| Symptom | Cause and solution |
| - | - |
| `422` with detail `PDF could not be OCRed` | The API could not read the file. Make sure that the file is a PDF that is not empty and not damaged. |
| `422` with detail `PDF could not be converted to a document` | The API could not make an invoice from the content. The detail can list the fields that are missing. Use a PDF with complete invoice data, or create the document from JSON. |
| `422` with `Field required` for `body.file` | The request is not `multipart/form-data` with a `file` field. |
| `success` is `false`, or there is no `ubl_document` | The PDF did not have sufficient data for a compliant document. Read `error_message`, give `vendor_tax_id` and `customer_tax_id`, or correct the extracted JSON and create the document with `POST /api/documents/`. |
| Amounts, dates or tax IDs are not correct | Give `vendor_tax_id` and `customer_tax_id`, use a text-based PDF, and examine the extracted data before you create the document. |
| `404` on the status or result request | The task is not complete, or the result expired (5 minutes). |
| `401` | The API key is missing or not correct. See [Authentication](/authentication). |

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

## Best practices

* Give `vendor_tax_id` and `customer_tax_id` with the synchronous upload.
* Check `success` and `ubl_document` before you continue. If one of them is missing, a person must examine the data.
* Examine the important fields (`invoice_id`, `invoice_total`, the tax IDs and the line items) before you send.
* Use the asynchronous upload when you convert many PDF files, and limit the number of conversions that run at the same time.
* Keep the original PDF files for your audit records.
* Develop and test with a sandbox company. See [Test mode and sandbox companies](/environments).

## Next Steps

<CardGroup cols={2}>
  <Card title="Send UBL documents" icon="file-code" href="/guides/ubl-documents">
    Create and send a document from UBL XML
  </Card>

  <Card title="Attachments and PDF" icon="paperclip" href="/guides/attachments">
    Add a PDF or other files to a JSON document
  </Card>

  <Card title="Send invoices by email (Mailbox)" icon="envelope" href="/guides/mailbox">
    Let your ERP email invoices to e-invoice.be
  </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.