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

# Quickstart

> Create a sandbox company, then validate, create and send your first invoice with the API in about five minutes.

This page takes you from a new account to a sent invoice. You use a **sandbox company**, which runs in test mode: the API does not transmit the document on the Peppol network, but sends the generated UBL XML to you by email.

You need:

* An account at [app.e-invoice.be](https://app.e-invoice.be).
* A terminal with `curl` (or Node.js 18+, Python with `requests`, PHP with the cURL extension, or .NET 6+).

<Note>
  A sandbox company needs no VAT verification, no contact verification and no payment. It is free to use. See [Test mode and sandbox companies](/environments) for the details of test mode.
</Note>

<Steps>
  <Step title="Create an account">
    Sign up at [app.e-invoice.be](https://app.e-invoice.be) and log in.
  </Step>

  <Step title="Create a sandbox company">
    1. Open **Companies** and select **Create sandbox company**.
    2. Keep the country **Belgium** and select **Suggest valid VAT** to get a synthetic Belgian VAT number.
    3. Make sure that **Contact email** is an address that you can read. The API sends the test document to this address.
    4. Create the company, open **API Settings** and copy the API key of the sandbox company.

    Each company has its own API key. Use the key of the sandbox company for all commands on this page.

    ```bash theme={null}
    export E_INVOICE_API_KEY="paste-the-sandbox-api-key-here"
    ```
  </Step>

  <Step title="Check the API key">
    Request the details of the company that owns the key.

    <CodeGroup>
      ```bash cURL theme={null}
      curl "https://api.e-invoice.be/api/me/" \
        -H "Authorization: Bearer $E_INVOICE_API_KEY"
      ```

      ```javascript Node.js theme={null}
      const response = await fetch("https://api.e-invoice.be/api/me/", {
        headers: { Authorization: `Bearer ${process.env.E_INVOICE_API_KEY}` },
      });
      console.log(response.status, await response.json());
      ```

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

      response = requests.get(
          "https://api.e-invoice.be/api/me/",
          headers={"Authorization": f"Bearer {os.environ['E_INVOICE_API_KEY']}"},
      )
      print(response.status_code, response.json())
      ```

      ```php PHP theme={null}
      <?php
      $ch = curl_init("https://api.e-invoice.be/api/me/");
      curl_setopt_array($ch, [
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("E_INVOICE_API_KEY")],
      ]);
      echo curl_exec($ch);
      curl_close($ch);
      ```

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

      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 response = await client.GetAsync("/api/me/");
      Console.WriteLine(await response.Content.ReadAsStringAsync());
      ```
    </CodeGroup>

    The API returns `200 OK`:

    ```json theme={null}
    {
      "name": "Sandbox Demo BV",
      "description": null,
      "plan": "starter",
      "credit_balance": 10,
      "peppol_ids": ["0208:0999123457"],
      "ibans": null,
      "company_number": "0999123457",
      "company_tax_id": null,
      "company_name": "Sandbox Demo BV",
      "company_address": "Teststraat 1",
      "company_zip": "1000",
      "company_city": "Brussel",
      "company_country": "Belgium",
      "company_email": "you@example.com",
      "smp_registration": false,
      "smp_registration_date": null,
      "bcc_recipient_email": null
    }
    ```

    The vendor of each document that you create must be a Peppol ID of your company. For a Belgian company, the Peppol ID is `0208:` followed by the VAT number without the `BE` prefix. Take the number after `0208:` in `peppol_ids`, put `BE` in front of it and keep it in a second environment variable:

    ```bash theme={null}
    export E_INVOICE_VENDOR_VAT="BE0999123457"
    ```

    <Warning>
      If the vendor VAT number does not match a Peppol ID of your company, `POST /api/documents/` returns `406` with the message `Derived sender '0208:...' is not in tenant peppol_ids`.
    </Warning>
  </Step>

  <Step title="Write the invoice">
    Save the invoice as `invoice.json`. The command puts your vendor VAT number in the file. The customer is OpenPeppol VZW (`BE0848934496`).

    ```bash theme={null}
    cat > invoice.json <<EOF
    {
      "document_type": "INVOICE",
      "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": "${E_INVOICE_VENDOR_VAT}",
      "vendor_address": "Brusselsesteenweg 119/A, 1980 Zemst, BE",
      "customer_name": "OpenPeppol VZW",
      "customer_tax_id": "BE0848934496",
      "customer_address": "Robert Schumanplein 6 bus 5, 1040 Brussel, BE",
      "items": [
        {
          "description": "Professional services",
          "quantity": 10,
          "unit": "C62",
          "unit_price": 100.00,
          "amount": 1000.00,
          "tax_rate": "21.00"
        }
      ]
    }
    EOF
    ```
  </Step>

  <Step title="Validate the invoice">
    <Note>
      Validation is not a separate mandatory call. `POST /api/documents/` rejects a payload that does not pass the same rules. Use `POST /api/validate/json` while you develop, because it returns all rule failures and the generated UBL.
    </Note>

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST "https://api.e-invoice.be/api/validate/json" \
        -H "Authorization: Bearer $E_INVOICE_API_KEY" \
        -H "Content-Type: application/json" \
        --data @invoice.json
      ```

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

      const response = await fetch("https://api.e-invoice.be/api/validate/json", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.E_INVOICE_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: await readFile("invoice.json"),
      });
      const result = await response.json();
      console.log(response.status, result.is_valid, result.issues);
      ```

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

      with open("invoice.json") as f:
          invoice = json.load(f)

      response = requests.post(
          "https://api.e-invoice.be/api/validate/json",
          headers={"Authorization": f"Bearer {os.environ['E_INVOICE_API_KEY']}"},
          json=invoice,
      )
      result = response.json()
      print(response.status_code, result["is_valid"], result["issues"])
      ```

      ```php PHP theme={null}
      <?php
      $ch = curl_init("https://api.e-invoice.be/api/validate/json");
      curl_setopt_array($ch, [
          CURLOPT_POST => true,
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_HTTPHEADER => [
              "Authorization: Bearer " . getenv("E_INVOICE_API_KEY"),
              "Content-Type: application/json",
          ],
          CURLOPT_POSTFIELDS => file_get_contents("invoice.json"),
      ]);
      echo curl_exec($ch);
      curl_close($ch);
      ```

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

      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 body = new StringContent(
          await File.ReadAllTextAsync("invoice.json"), Encoding.UTF8, "application/json");
      var response = await client.PostAsync("/api/validate/json", body);
      Console.WriteLine(await response.Content.ReadAsStringAsync());
      ```
    </CodeGroup>

    The API returns `201 Created`. This call does not store a document. The `ubl_document` field contains the full UBL XML; it is shortened here.

    ```json theme={null}
    {
      "id": "3f2b8c1e-5d4a-4e6f-9a7b-1c2d3e4f5a6b",
      "file_name": "3f2b8c1e-5d4a-4e6f-9a7b-1c2d3e4f5a6b.xml",
      "is_valid": true,
      "issues": [],
      "ubl_document": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Invoice ...>...</Invoice>"
    }
    ```

    If `is_valid` is `false`, read the `issues` array, correct `invoice.json` and validate again. See [Validation during development](/guides/validation).
  </Step>

  <Step title="Create the document">
    Send the same file to `POST /api/documents/`. The API stores the document in the state `DRAFT`.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST "https://api.e-invoice.be/api/documents/" \
        -H "Authorization: Bearer $E_INVOICE_API_KEY" \
        -H "Content-Type: application/json" \
        --data @invoice.json
      ```

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

      const response = await fetch("https://api.e-invoice.be/api/documents/", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.E_INVOICE_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: await readFile("invoice.json"),
      });
      const document = await response.json();
      console.log(response.status, document.id, document.state);
      ```

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

      with open("invoice.json") as f:
          invoice = json.load(f)

      response = requests.post(
          "https://api.e-invoice.be/api/documents/",
          headers={"Authorization": f"Bearer {os.environ['E_INVOICE_API_KEY']}"},
          json=invoice,
      )
      document = response.json()
      print(response.status_code, document["id"], document["state"])
      ```

      ```php PHP theme={null}
      <?php
      $ch = curl_init("https://api.e-invoice.be/api/documents/");
      curl_setopt_array($ch, [
          CURLOPT_POST => true,
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_HTTPHEADER => [
              "Authorization: Bearer " . getenv("E_INVOICE_API_KEY"),
              "Content-Type: application/json",
          ],
          CURLOPT_POSTFIELDS => file_get_contents("invoice.json"),
      ]);
      $document = json_decode(curl_exec($ch), true);
      curl_close($ch);
      echo $document["id"] . " " . $document["state"] . PHP_EOL;
      ```

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

      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 body = new StringContent(
          await File.ReadAllTextAsync("invoice.json"), Encoding.UTF8, "application/json");
      var response = await client.PostAsync("/api/documents/", body);
      Console.WriteLine(await response.Content.ReadAsStringAsync());
      ```
    </CodeGroup>

    The API returns `201 Created` with the complete document. The response is shortened here.

    ```json theme={null}
    {
      "id": "doc-4f8a2c6e9b1d3a5f7c0e2b4d6f8a1c3e",
      "created_at": "2026-10-01T09:15:23.482913Z",
      "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": "BE0999123457",
      "customer_name": "OpenPeppol VZW",
      "customer_tax_id": "BE0848934496",
      "subtotal": "1000.00",
      "total_tax": "210.00",
      "invoice_total": "1210.00",
      "amount_due": "1210.00"
    }
    ```

    Keep the `id` for the next step:

    ```bash theme={null}
    export E_INVOICE_DOCUMENT_ID="doc-4f8a2c6e9b1d3a5f7c0e2b4d6f8a1c3e"
    ```
  </Step>

  <Step title="Send the document">
    Send the draft. The request has no body.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST "https://api.e-invoice.be/api/documents/$E_INVOICE_DOCUMENT_ID/send" \
        -H "Authorization: Bearer $E_INVOICE_API_KEY"
      ```

      ```javascript Node.js theme={null}
      const id = process.env.E_INVOICE_DOCUMENT_ID;

      const response = await fetch(`https://api.e-invoice.be/api/documents/${id}/send`, {
        method: "POST",
        headers: { Authorization: `Bearer ${process.env.E_INVOICE_API_KEY}` },
      });
      const document = await response.json();
      console.log(response.status, document.id, document.state);
      ```

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

      document_id = os.environ["E_INVOICE_DOCUMENT_ID"]

      response = requests.post(
          f"https://api.e-invoice.be/api/documents/{document_id}/send",
          headers={"Authorization": f"Bearer {os.environ['E_INVOICE_API_KEY']}"},
      )
      document = response.json()
      print(response.status_code, document["id"], document["state"])
      ```

      ```php PHP theme={null}
      <?php
      $id = getenv("E_INVOICE_DOCUMENT_ID");

      $ch = curl_init("https://api.e-invoice.be/api/documents/{$id}/send");
      curl_setopt_array($ch, [
          CURLOPT_POST => true,
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("E_INVOICE_API_KEY")],
          CURLOPT_POSTFIELDS => "",
      ]);
      echo curl_exec($ch);
      curl_close($ch);
      ```

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

      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 id = Environment.GetEnvironmentVariable("E_INVOICE_DOCUMENT_ID");
      var response = await client.PostAsync($"/api/documents/{id}/send", null);
      Console.WriteLine(await response.Content.ReadAsStringAsync());
      ```
    </CodeGroup>

    The API returns `200 OK` with the document. The state is now `TRANSIT`, because delivery runs in the background. The response is shortened here.

    ```json theme={null}
    {
      "id": "doc-4f8a2c6e9b1d3a5f7c0e2b4d6f8a1c3e",
      "created_at": "2026-10-01T09:15:23.482913Z",
      "document_type": "INVOICE",
      "state": "TRANSIT",
      "direction": "OUTBOUND",
      "invoice_id": "INV-2026-001",
      "invoice_date": "2026-10-01",
      "due_date": "2026-10-31",
      "currency": "EUR",
      "invoice_total": "1210.00"
    }
    ```

    <Note>
      If the sandbox company has no contact email address, this call returns `400` with the message `Tenant is in test mode but no company email is set`. Add a contact email address in the company settings, then create and send a new document.
    </Note>
  </Step>

  <Step title="Check the result">
    Open the mailbox of the contact email address of the sandbox company. You get an email with the subject `Document sent via Peppol // e-invoice.be [TEST-MODE]` and the UBL XML as an attachment. This is the file that a production company transmits on the Peppol network.

    When the email is sent, the state of the document changes to `SENT`:

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

    ```json theme={null}
    {
      "id": "doc-4f8a2c6e9b1d3a5f7c0e2b4d6f8a1c3e",
      "document_type": "INVOICE",
      "state": "SENT",
      "direction": "OUTBOUND",
      "invoice_id": "INV-2026-001"
    }
    ```

    The API also sends a `document.sent` event to each webhook that subscribes to it. See [Webhooks](/essentials/webhooks).
  </Step>
</Steps>

## Go live

Test mode is fixed when a company is created. To send documents on the Peppol network, create a production company and use its API key. The API host and your code stay the same. Follow the [go-live checklist](/going-live) before you send the first production document.

If you prefer a typed client, see [SDKs](/sdks). If you prefer a terminal, see the [peppol CLI](/cli).

## Next Steps

<CardGroup cols={2}>
  <Card title="Go-live checklist" icon="list-check" href="/going-live">
    Move from a sandbox company to a production company
  </Card>

  <Card title="Test mode and sandbox companies" icon="flask" href="/environments">
    Learn how test mode and sandbox companies work
  </Card>

  <Card title="Create e-invoices" icon="file-invoice" href="/guides/creating-invoices">
    Use all invoice fields, payment details and tax options
  </Card>

  <Card title="Webhooks" icon="webhook" href="/essentials/webhooks">
    Get a notification for each sent and received document
  </Card>
</CardGroup>


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