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

# Admin API

> Create tenants, issue API keys and register tenants on Peppol with the organisation API key of a reseller.

<Warning>
  The Admin API is available only to resellers that have an [organisation API key](/glossary#organisation-api-key). For invoice operations with a tenant API key, see [Create e-invoices](/guides/creating-invoices).
</Warning>

## Overview

A reseller uses the Admin API to manage [tenants](/glossary#tenant). A tenant is a company that a reseller manages. With the Admin API you can:

* create, read, update and delete tenants
* issue, read, rename and revoke the API keys of a tenant
* register a tenant on the Peppol network, or keep the tenant [send-only](/glossary#send-only-tenant)
* put a test document in the inbox of a test-mode tenant

The admin operations are not in the OpenAPI specification, so the generated API reference has no pages for them. This page is the reference.

## Endpoint overview

All paths are on the host `https://api.e-invoice.be`.

| Method | Path | Purpose |
| - | - | - |
| `GET` | `/api/admin/ping` | [Health check](#health-check) (no authentication) |
| `GET` | `/api/admin/tenants` | [List tenants](#list-tenants) |
| `POST` | `/api/admin/tenants` | [Create a tenant](#create-a-tenant) |
| `GET` | `/api/admin/tenants/{tenant_id}` | [Get a tenant](#get-a-tenant) |
| `PUT` | `/api/admin/tenants/{tenant_id}` | [Update a tenant](#update-a-tenant) |
| `DELETE` | `/api/admin/tenants/{tenant_id}` | [Delete a tenant](#delete-a-tenant) |
| `GET` | `/api/admin/tenants/{tenant_id}/api-keys` | [List API keys](#list-api-keys) |
| `POST` | `/api/admin/tenants/{tenant_id}/api-keys` | [Create an API key](#create-an-api-key) |
| `GET` | `/api/admin/tenants/{tenant_id}/api-keys/latest` | [Get the latest API key](#get-the-latest-api-key) |
| `GET` | `/api/admin/tenants/{tenant_id}/api-keys/{api_key_id}` | [Get one API key](#get-one-api-key) |
| `PUT` | `/api/admin/tenants/{tenant_id}/api-keys/{api_key_id}` | [Update an API key](#update-an-api-key) |
| `DELETE` | `/api/admin/tenants/{tenant_id}/api-keys/{api_key_id}` | [Revoke an API key](#revoke-an-api-key) |
| `GET` | `/api/admin/tenants/{tenant_id}/peppol/` | [Check the registration status](#check-the-registration-status) |
| `POST` | `/api/admin/tenants/{tenant_id}/peppol/register` | [Register on Peppol](#register-on-peppol) |
| `PUT` | `/api/admin/tenants/{tenant_id}/peppol/business-card` | [Update the business card](#update-the-business-card) |
| `DELETE` | `/api/admin/tenants/{tenant_id}/peppol/unregister` | [Unregister from Peppol](#unregister-from-peppol) |
| `POST` | `/api/admin/tenants/{tenant_id}/simulate-inbound` | [Simulate an inbound document](#simulate-an-inbound-document) |

## Authentication

Send the organisation API key as a bearer token. A tenant API key does not give access to the Admin API.

```bash theme={null}
curl -X GET "https://api.e-invoice.be/api/admin/tenants" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY"
```

The samples on this page read the organisation API key from the environment variable `E_INVOICE_ORG_API_KEY`. Samples that use a tenant API key read it from `E_INVOICE_API_KEY`.

An organisation API key gives access only to the tenants of that organisation. A request for a tenant of a different organisation returns `404 Not Found`.

<Note>
  Only approved resellers get an organisation API key. Send an email to [support@e-invoice.be](mailto:support@e-invoice.be) to apply for the [reseller programme](/reseller-programme).
</Note>

## Health check

`GET /api/admin/ping` shows that the Admin API is available. This request needs no authentication.

```bash theme={null}
curl -X GET "https://api.e-invoice.be/api/admin/ping"
```

```json theme={null}
{
  "message": "Pong",
  "status": "ok",
  "timestamp": "2026-04-16T10:00:00.123456"
}
```

## Tenant management

### List tenants

Get the tenants of your organisation, newest first. Deleted tenants are not in the list.

```bash theme={null}
curl -X GET "https://api.e-invoice.be/api/admin/tenants?skip=0&limit=100" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY"
```

<ParamField query="skip" type="integer" default="0">
  Number of tenants to skip.
</ParamField>

<ParamField query="limit" type="integer" default="100">
  Maximum number of tenants to return.
</ParamField>

```json theme={null}
{
  "tenants": [
    {
      "name": "e-invoice-bv",
      "description": "E-INVOICE BV",
      "plan": "enterprise",
      "credit_balance": 999999999,
      "peppol_ids": ["0208:1018265814"],
      "ibans": null,
      "company_number": "1018265814",
      "company_tax_id": "BE1018265814",
      "company_name": "E-INVOICE BV",
      "company_address": null,
      "company_zip": null,
      "company_city": null,
      "company_country": null,
      "company_email": null,
      "smp_registration": false,
      "smp_registration_date": null,
      "bcc_recipient_email": null,
      "id": "ten-9k2m4p7q3w5x8r",
      "created_at": "2026-04-16T10:00:00.000000Z",
      "updated_at": "2026-04-16T10:00:00.000000Z",
      "enrollment_state": "not_required",
      "status_message": null,
      "route_override": null
    }
  ],
  "total": 1
}
```

`total` is the number of tenants that are not deleted, not the number of tenants in this page.

### Create a tenant

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.e-invoice.be/api/admin/tenants" \
       -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY" \
       -H "Content-Type: application/json" \
       -d '{
         "name": "e-invoice-bv",
         "description": "E-INVOICE BV",
         "company_number": "1018265814",
         "company_tax_id": "BE1018265814",
         "company_name": "E-INVOICE BV",
         "peppol_ids": ["0208:1018265814"]
       }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch('https://api.e-invoice.be/api/admin/tenants', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.E_INVOICE_ORG_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name: 'e-invoice-bv',
      description: 'E-INVOICE BV',
      company_number: '1018265814',
      company_tax_id: 'BE1018265814',
      company_name: 'E-INVOICE BV',
      peppol_ids: ['0208:1018265814'],
    }),
  });

  if (!response.ok) {
    throw new Error(`${response.status}: ${await response.text()}`);
  }

  const tenant = await response.json();
  console.log(tenant.id);
  ```

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

  import requests

  response = requests.post(
      "https://api.e-invoice.be/api/admin/tenants",
      headers={"Authorization": f"Bearer {os.environ['E_INVOICE_ORG_API_KEY']}"},
      json={
          "name": "e-invoice-bv",
          "description": "E-INVOICE BV",
          "company_number": "1018265814",
          "company_tax_id": "BE1018265814",
          "company_name": "E-INVOICE BV",
          "peppol_ids": ["0208:1018265814"],
      },
      timeout=30,
  )
  response.raise_for_status()

  tenant = response.json()
  print(tenant["id"])
  ```

  ```php PHP theme={null}
  <?php
  $body = json_encode([
      'name' => 'e-invoice-bv',
      'description' => 'E-INVOICE BV',
      'company_number' => '1018265814',
      'company_tax_id' => 'BE1018265814',
      'company_name' => 'E-INVOICE BV',
      'peppol_ids' => ['0208:1018265814'],
  ]);

  $ch = curl_init('https://api.e-invoice.be/api/admin/tenants');
  curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
          'Authorization: Bearer ' . getenv('E_INVOICE_ORG_API_KEY'),
          'Content-Type: application/json',
      ],
      CURLOPT_POSTFIELDS => $body,
  ]);

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

  if ($status !== 201) {
      throw new RuntimeException("$status: $result");
  }

  $tenant = json_decode($result, true);
  echo $tenant['id'];
  ```

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

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

  var response = await client.PostAsJsonAsync("/api/admin/tenants", new
  {
      name = "e-invoice-bv",
      description = "E-INVOICE BV",
      company_number = "1018265814",
      company_tax_id = "BE1018265814",
      company_name = "E-INVOICE BV",
      peppol_ids = new[] { "0208:1018265814" },
  });
  response.EnsureSuccessStatusCode();

  var tenant = await response.Content.ReadFromJsonAsync<JsonElement>();
  Console.WriteLine(tenant.GetProperty("id").GetString());
  ```
</CodeGroup>

<ParamField body="name" type="string" required>
  Name of the tenant. The name must be unique in your organisation. A name that a tenant of your organisation already has gives `409 Conflict`.
</ParamField>

<ParamField body="description" type="string">
  Free text that describes the tenant.
</ParamField>

<ParamField body="peppol_ids" type="string[]">
  The Peppol ID of the tenant in the form `scheme:identifier`. The tenant cannot send documents without it. The field is an array, but the platform uses one Peppol ID for each tenant.
</ParamField>

<ParamField body="company_number" type="string">
  Company registration number from the national business register. For Belgium this is the CBE number, for example `1018265814`.
</ParamField>

<ParamField body="company_tax_id" type="string">
  VAT or tax number with the country prefix, for example `BE1018265814`.
</ParamField>

<ParamField body="ibans" type="string[]">
  IBANs of the tenant. No default.
</ParamField>

<ParamField body="plan" type="string" default="enterprise">
  Plan of the tenant: `starter`, `pro` or `enterprise`. See [Plans and credits](#plans-and-credits).
</ParamField>

<ParamField body="credit_balance" type="integer" default="999999999">
  Credit balance of the tenant. See [Plans and credits](#plans-and-credits).
</ParamField>

The request also accepts the company fields `company_name`, `company_address`, `company_zip`, `company_city`, `company_country` and `company_email`. All of them are optional strings.

The response has the status `201 Created`:

```json theme={null}
{
  "name": "e-invoice-bv",
  "description": "E-INVOICE BV",
  "plan": "enterprise",
  "credit_balance": 999999999,
  "peppol_ids": ["0208:1018265814"],
  "ibans": null,
  "company_number": "1018265814",
  "company_tax_id": "BE1018265814",
  "company_name": "E-INVOICE BV",
  "company_address": null,
  "company_zip": null,
  "company_city": null,
  "company_country": null,
  "company_email": null,
  "smp_registration": false,
  "smp_registration_date": null,
  "bcc_recipient_email": "e-invoice-bv+3f9a1c7e@outbox.email.e-invoice.be",
  "id": "ten-9k2m4p7q3w5x8r",
  "created_at": "2026-04-16T10:00:00.000000Z",
  "updated_at": "2026-04-16T10:00:00.000000Z",
  "enrollment_state": "not_required",
  "status_message": null,
  "route_override": null
}
```

<Warning>
  You must find and set the correct Peppol ID for each tenant. The API does not validate `peppol_ids` when you create or update a tenant. Creating a tenant does not register the Peppol ID on the Peppol network; see [Register on Peppol](#register-on-peppol).
</Warning>

A tenant that you create with the organisation API key of a test-mode organisation is a test-mode tenant. You cannot change the test mode of a tenant after you create it. See [Test mode and sandbox companies](/environments).

#### Tenant response fields

Each tenant operation returns the request fields above and these fields:

<ResponseField name="id" type="string">
  Identifier of the tenant. Use it as `tenant_id` in the paths.
</ResponseField>

<ResponseField name="created_at" type="string">
  Date and time when the tenant was created.
</ResponseField>

<ResponseField name="updated_at" type="string">
  Date and time of the last change.
</ResponseField>

<ResponseField name="smp_registration" type="boolean">
  Whether the tenant is registered on the [SMP](/glossary#smp) (Service Metadata Publisher) of e-invoice.be.
</ResponseField>

<ResponseField name="smp_registration_date" type="string | null">
  Date and time of the registration on the SMP.
</ResponseField>

<ResponseField name="bcc_recipient_email" type="string | null">
  Email address of the tenant at `outbox.email.e-invoice.be`. The create and get operations fill this field.
</ResponseField>

<ResponseField name="enrollment_state" type="string">
  Activation state of the tenant for its destination network: `not_required`, `not_enrolled`, `pending`, `enrolled` or `failed`. A tenant that sends through Peppol has `not_required` and can send immediately.
</ResponseField>

<ResponseField name="status_message" type="string | null">
  The reason when `enrollment_state` is `failed`.
</ResponseField>

<ResponseField name="route_override" type="string | null">
  `null` when the tenant uses the default delivery route for its country.
</ResponseField>

#### Company number and tax ID

* `company_number` is the registration number from the national business register (Belgium: CBE number, Netherlands: KvK number).
* `company_tax_id` is the VAT or tax number with the country prefix (`BE1018265814`, `NL123456789B01`).

For a Belgian company, the Peppol ID is `0208:` plus the CBE number:

| Field | Value |
| - | - |
| `company_number` | `1018265814` |
| `company_tax_id` | `BE1018265814` |
| `peppol_ids` | `["0208:1018265814"]` |

For the schemes of other countries, see [Look up Peppol participants](/guides/lookup-participants) and [Supported schemes](#supported-schemes).

#### Why you set the Peppol ID when you create the tenant

The API uses the Peppol ID in `peppol_ids` for three functions:

* **Sending.** The API accepts a send request only when the sender Peppol ID is in the `peppol_ids` of the tenant. The comparison ignores letter case and spaces at the start and the end.
* **Receiving.** Documents for the Peppol ID go to the tenant, after the ID is registered.
* **Registration.** [Register on Peppol](#register-on-peppol) publishes the ID on the SMP and in the Peppol Directory.

<Note>
  Tenant create and tenant update do not write to the SMP or the [SML](/glossary#sml) (Service Metadata Locator). Registration is a separate step. Do it when the tenant must receive documents, or do not do it for a [send-only tenant](#send-only-tenants).
</Note>

### Get a tenant

```bash theme={null}
curl -X GET "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY"
```

The response has the same shape as the response of [Create a tenant](#create-a-tenant). This operation also returns a tenant that is deleted.

```json theme={null}
{
  "name": "e-invoice-bv",
  "description": "E-INVOICE BV",
  "plan": "enterprise",
  "credit_balance": 999999999,
  "peppol_ids": ["0208:1018265814"],
  "ibans": null,
  "company_number": "1018265814",
  "company_tax_id": "BE1018265814",
  "company_name": "E-INVOICE BV",
  "company_address": null,
  "company_zip": null,
  "company_city": null,
  "company_country": null,
  "company_email": null,
  "smp_registration": false,
  "smp_registration_date": null,
  "bcc_recipient_email": "e-invoice-bv+3f9a1c7e@outbox.email.e-invoice.be",
  "id": "ten-9k2m4p7q3w5x8r",
  "created_at": "2026-04-16T10:00:00.000000Z",
  "updated_at": "2026-04-16T10:00:00.000000Z",
  "enrollment_state": "not_required",
  "status_message": null,
  "route_override": null
}
```

### Update a tenant

Send only the fields that you want to change. The API keeps the value of each field that is not in the request body. The request accepts the same fields as [Create a tenant](#create-a-tenant); `name` is optional here.

```bash theme={null}
curl -X PUT "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "description": "E-INVOICE BV - accounting integration",
       "ibans": ["BE68539007547034"]
     }'
```

The response has the status `200 OK` and contains the full tenant:

```json theme={null}
{
  "name": "e-invoice-bv",
  "description": "E-INVOICE BV - accounting integration",
  "plan": "enterprise",
  "credit_balance": 999999999,
  "peppol_ids": ["0208:1018265814"],
  "ibans": ["BE68539007547034"],
  "company_number": "1018265814",
  "company_tax_id": "BE1018265814",
  "company_name": "E-INVOICE BV",
  "company_address": null,
  "company_zip": null,
  "company_city": null,
  "company_country": null,
  "company_email": null,
  "smp_registration": false,
  "smp_registration_date": null,
  "bcc_recipient_email": null,
  "id": "ten-9k2m4p7q3w5x8r",
  "created_at": "2026-04-16T10:00:00.000000Z",
  "updated_at": "2026-04-16T11:30:00.000000Z",
  "enrollment_state": "not_required",
  "status_message": null,
  "route_override": null
}
```

A new `name` that a different tenant of your organisation already has gives `409 Conflict`.

### Delete a tenant

This operation marks the tenant as deleted. It does not remove the data of the tenant.

```bash theme={null}
curl -X DELETE "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY"
```

The response has the status `204 No Content` and no body:

```http theme={null}
HTTP/1.1 204 No Content
```

<Warning>
  Delete does not unregister the tenant from Peppol. Call [Unregister from Peppol](#unregister-from-peppol) first when the tenant is registered.
</Warning>

### Plans and credits

You set the plan and the credit balance of a tenant with the fields `plan` and `credit_balance` of [Create a tenant](#create-a-tenant) and [Update a tenant](#update-a-tenant). These two fields are the only plan and billing functions in the Admin API.

| Field | Values | Default |
| - | - | - |
| `plan` | `starter`, `pro`, `enterprise` | `enterprise` |
| `credit_balance` | Integer | `999999999` |

```bash theme={null}
curl -X PUT "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "plan": "pro",
       "credit_balance": 5000
     }'
```

The response is the full tenant with the new values (shortened here):

```json theme={null}
{
  "name": "e-invoice-bv",
  "plan": "pro",
  "credit_balance": 5000,
  "id": "ten-9k2m4p7q3w5x8r"
}
```

The tenant sees these values in `GET /api/me/`. `GET /api/stats` with the API key of the tenant returns the usage of the tenant and uses `credit_balance` to calculate `budget_estimation_days`. See [Usage statistics and credits](/guides/usage-statistics).

## API key management

### Create an API key

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r/api-keys" \
       -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY" \
       -H "Content-Type: application/json" \
       -d '{
         "name": "production-key",
         "description": "Production API key for E-INVOICE BV"
       }'
  ```

  ```javascript Node.js theme={null}
  const tenantId = 'ten-9k2m4p7q3w5x8r';

  const response = await fetch(
    `https://api.e-invoice.be/api/admin/tenants/${tenantId}/api-keys`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.E_INVOICE_ORG_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        name: 'production-key',
        description: 'Production API key for E-INVOICE BV',
      }),
    },
  );

  if (!response.ok) {
    throw new Error(`${response.status}: ${await response.text()}`);
  }

  // The id is the bearer token of the tenant. Store it in a secure location.
  const apiKey = await response.json();
  ```

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

  import requests

  tenant_id = "ten-9k2m4p7q3w5x8r"

  response = requests.post(
      f"https://api.e-invoice.be/api/admin/tenants/{tenant_id}/api-keys",
      headers={"Authorization": f"Bearer {os.environ['E_INVOICE_ORG_API_KEY']}"},
      json={
          "name": "production-key",
          "description": "Production API key for E-INVOICE BV",
      },
      timeout=30,
  )
  response.raise_for_status()

  # The id is the bearer token of the tenant. Store it in a secure location.
  api_key = response.json()
  ```

  ```php PHP theme={null}
  <?php
  $tenantId = 'ten-9k2m4p7q3w5x8r';

  $ch = curl_init("https://api.e-invoice.be/api/admin/tenants/$tenantId/api-keys");
  curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
          'Authorization: Bearer ' . getenv('E_INVOICE_ORG_API_KEY'),
          'Content-Type: application/json',
      ],
      CURLOPT_POSTFIELDS => json_encode([
          'name' => 'production-key',
          'description' => 'Production API key for E-INVOICE BV',
      ]),
  ]);

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

  if ($status !== 201) {
      throw new RuntimeException("$status: $result");
  }

  // The id is the bearer token of the tenant. Store it in a secure location.
  $apiKey = json_decode($result, true);
  ```

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

  var tenantId = "ten-9k2m4p7q3w5x8r";

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

  var response = await client.PostAsJsonAsync($"/api/admin/tenants/{tenantId}/api-keys", new
  {
      name = "production-key",
      description = "Production API key for E-INVOICE BV",
  });
  response.EnsureSuccessStatusCode();

  // The id is the bearer token of the tenant. Store it in a secure location.
  var apiKey = await response.Content.ReadFromJsonAsync<JsonElement>();
  ```
</CodeGroup>

<ParamField body="name" type="string" required>
  Name of the API key. The name must be unique among the active API keys of the tenant; a name that is in use gives `409 Conflict`.
</ParamField>

<ParamField body="description" type="string">
  Free text, for example the purpose of the key.
</ParamField>

The response has the status `201 Created`:

```json theme={null}
{
  "id": "api-3h8f5j2k9l4m7n6p1q5r8s2t4v6w9x3y",
  "tenant_id": "ten-9k2m4p7q3w5x8r",
  "name": "production-key",
  "description": "Production API key for E-INVOICE BV",
  "created_at": "2026-04-16T10:00:00.000000Z",
  "updated_at": "2026-04-16T10:00:00.000000Z",
  "is_deleted": false
}
```

<Warning>
  The `id` field is the API key. There is no separate `key` field. Your customer sends the value of `id` as the bearer token:

  ```
  Authorization: Bearer api-3h8f5j2k9l4m7n6p1q5r8s2t4v6w9x3y
  ```

  Keep the `id` secret. Store it encrypted and give it to your customer through a secure channel. Do not write it to logs, to version control or to client-side code.
</Warning>

### List API keys

Get the active API keys of a tenant, newest first. Revoked keys are not in the list.

```bash theme={null}
curl -X GET "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r/api-keys?skip=0&limit=100" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY"
```

<ParamField query="skip" type="integer" default="0">
  Number of API keys to skip.
</ParamField>

<ParamField query="limit" type="integer" default="100">
  Maximum number of API keys to return.
</ParamField>

```json theme={null}
{
  "api_keys": [
    {
      "id": "api-3h8f5j2k9l4m7n6p1q5r8s2t4v6w9x3y",
      "tenant_id": "ten-9k2m4p7q3w5x8r",
      "name": "production-key",
      "description": "Production API key for E-INVOICE BV",
      "created_at": "2026-04-16T10:00:00.000000Z",
      "updated_at": "2026-04-16T10:00:00.000000Z",
      "is_deleted": false
    }
  ],
  "total": 1
}
```

<Warning>
  The `id` is the bearer token, so the operations that read API keys return live credentials. Limit who can call them, do not write the response body to logs, and do not show it to end users.
</Warning>

### Get the latest API key

Get the active API key of a tenant that was created last.

```bash theme={null}
curl -X GET "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r/api-keys/latest" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY"
```

```json theme={null}
{
  "id": "api-3h8f5j2k9l4m7n6p1q5r8s2t4v6w9x3y",
  "tenant_id": "ten-9k2m4p7q3w5x8r",
  "name": "production-key",
  "description": "Production API key for E-INVOICE BV",
  "created_at": "2026-04-16T10:00:00.000000Z",
  "updated_at": "2026-04-16T10:00:00.000000Z",
  "is_deleted": false
}
```

If the tenant has no active API key, the response is `404 Not Found`:

```json theme={null}
{
  "detail": "No active API keys found for tenant ten-9k2m4p7q3w5x8r"
}
```

### Get one API key

Get one API key by its ID. Use this operation to see if a key is active or revoked: unlike the list operation, it also returns a revoked key, with `is_deleted: true`.

```bash theme={null}
curl -X GET "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r/api-keys/api-3h8f5j2k9l4m7n6p1q5r8s2t4v6w9x3y" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY"
```

Response for an active key:

```json theme={null}
{
  "id": "api-3h8f5j2k9l4m7n6p1q5r8s2t4v6w9x3y",
  "tenant_id": "ten-9k2m4p7q3w5x8r",
  "name": "production-key",
  "description": "Production API key for E-INVOICE BV",
  "created_at": "2026-04-16T10:00:00.000000Z",
  "updated_at": "2026-04-16T10:00:00.000000Z",
  "is_deleted": false
}
```

Response for a revoked key:

```json theme={null}
{
  "id": "api-3h8f5j2k9l4m7n6p1q5r8s2t4v6w9x3y",
  "tenant_id": "ten-9k2m4p7q3w5x8r",
  "name": "production-key",
  "description": "Production API key for E-INVOICE BV",
  "created_at": "2026-04-16T10:00:00.000000Z",
  "updated_at": "2026-04-16T10:00:00.000000Z",
  "is_deleted": true
}
```

If the key does not exist for this tenant, the response is `404 Not Found`:

```json theme={null}
{
  "detail": "API key with ID api-3h8f5j2k9l4m7n6p1q5r8s2t4v6w9x3y not found for tenant ten-9k2m4p7q3w5x8r"
}
```

### Update an API key

Change the name or the description of an API key. The `id` (the bearer token) does not change.

```bash theme={null}
curl -X PUT "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r/api-keys/api-3h8f5j2k9l4m7n6p1q5r8s2t4v6w9x3y" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "name": "production-key-v2",
       "description": "Production API key, second version"
     }'
```

<ParamField body="name" type="string">
  New name. A name that a different active key of the tenant has gives `409 Conflict`.
</ParamField>

<ParamField body="description" type="string">
  New description.
</ParamField>

```json theme={null}
{
  "id": "api-3h8f5j2k9l4m7n6p1q5r8s2t4v6w9x3y",
  "tenant_id": "ten-9k2m4p7q3w5x8r",
  "name": "production-key-v2",
  "description": "Production API key, second version",
  "created_at": "2026-04-16T10:00:00.000000Z",
  "updated_at": "2026-04-16T11:30:00.000000Z",
  "is_deleted": false
}
```

### Revoke an API key

```bash theme={null}
curl -X DELETE "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r/api-keys/api-3h8f5j2k9l4m7n6p1q5r8s2t4v6w9x3y" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY"
```

The response has the status `204 No Content` and no body:

```http theme={null}
HTTP/1.1 204 No Content
```

After this call, [Get one API key](#get-one-api-key) returns the key with `is_deleted: true`.

### Rotate an API key

<Steps>
  <Step title="Create a new API key">
    Call [Create an API key](#create-an-api-key) with a new name. Keep the returned `id`.
  </Step>

  <Step title="Give the new key to your customer">
    Send the key through a secure channel, or change the configuration that you keep for the customer.
  </Step>

  <Step title="Revoke the old key">
    When the customer uses the new key, call [Revoke an API key](#revoke-an-api-key) for the old key. The two keys are valid together until this step, so there is no downtime.
  </Step>
</Steps>

## Peppol registration

[Peppol registration](/glossary#peppol-registration) publishes the Peppol ID of a tenant on the SMP of e-invoice.be, so that the tenant can **receive** documents through e-invoice.be. A tenant that only sends documents does not need a registration; see [Send-only tenants](#send-only-tenants).

<Warning>
  The Peppol operations are not available with the organisation API key of a test-mode organisation. The four operations in this section then return `403 Forbidden`:

  ```json theme={null}
  {
    "detail": "Peppol operations are not available for organizations in test mode. Please contact support to enable production mode."
  }
  ```
</Warning>

### Check the registration status

```bash theme={null}
curl -X GET "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r/peppol/" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY"
```

<ParamField query="verify" type="boolean" default="false">
  Set to `true` to do a live check on the network. See [Verify a registration](#verify-a-registration).
</ParamField>

```json theme={null}
{
  "peppol_id": "0208:1018265814",
  "state": "e-invoice",
  "smp": {
    "service_group": {
      "participant_identifier": "1018265814",
      "participant_scheme": "0208",
      "service_metadata_references": []
    },
    "business_card": {
      "participant_identifier": "1018265814",
      "participant_scheme": "0208",
      "business_entity": {
        "name": "E-INVOICE BV",
        "country_code": "BE",
        "vat_identifier": "BE1018265814",
        "website_uri": "https://e-invoice.be/company/1018265814",
        "contact": {
          "name": null,
          "email": "support@e-invoice.be"
        }
      }
    },
    "document_types": [
      {
        "document_type_code": "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1",
        "process_identifier": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0",
        "endpoints": []
      }
    ]
  },
  "last_checked_at": "2026-04-16T10:00:00.000000",
  "verified": false,
  "publications": [
    {
      "peppol_id": "0208:1018265814",
      "state": "published",
      "latest_event": "directory_entry_created",
      "source": "phoss",
      "detail": null,
      "observed_at": "2026-04-16T09:55:00.000000Z"
    }
  ]
}
```

<ResponseField name="state" type="string">
  `e-invoice`: the Peppol ID is on the SMP of e-invoice.be. `other`: the Peppol ID is found on the network, but not on the SMP of e-invoice.be. `not_registered`: the Peppol ID is not found. A tenant with `other` or `not_registered` can send documents.
</ResponseField>

<ResponseField name="smp" type="object">
  The service group, the business card and the document types on the SMP of e-invoice.be. The three fields are `null` when the Peppol ID is not on this SMP.
</ResponseField>

<ResponseField name="verified" type="boolean">
  `true` when this request did a live check (`verify=true`).
</ResponseField>

<ResponseField name="publications" type="object[]">
  One entry for each Peppol ID of the tenant. `state` is `not_published`, `pending` or `published` and comes from the last recorded event for the ID. `latest_event`, `source`, `detail` and `observed_at` describe that event; they are `null` when the ID has no events.
</ResponseField>

If the tenant has no Peppol ID, the response is `400 Bad Request`:

```json theme={null}
{
  "detail": "Tenant does not have a Peppol ID"
}
```

### Verify a registration

Without `verify`, the `publications` field comes from the events that the API recorded before (for example at registration). With `verify=true`, the API first does a live DNS lookup for each Peppol ID of the tenant, records the result as a new event, and then returns the status.

```bash theme={null}
curl -X GET "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r/peppol/?verify=true" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY"
```

The response has the same shape as [Check the registration status](#check-the-registration-status). `verified` is `true`, and each publication shows the result of the live check (the `smp` object is shortened here):

```json theme={null}
{
  "peppol_id": "0208:1018265814",
  "state": "e-invoice",
  "smp": {
    "service_group": {
      "participant_identifier": "1018265814",
      "participant_scheme": "0208",
      "service_metadata_references": []
    },
    "business_card": null,
    "document_types": []
  },
  "last_checked_at": "2026-04-16T10:00:00.000000",
  "verified": true,
  "publications": [
    {
      "peppol_id": "0208:1018265814",
      "state": "published",
      "latest_event": "publication_verified",
      "source": "dns",
      "detail": null,
      "observed_at": "2026-04-16T10:00:00.000000Z"
    }
  ]
}
```

When the live check does not find the Peppol ID on the SMP of e-invoice.be, the publication has `state: "not_published"` and `latest_event: "not_found_on_smp"`.

<Note>
  A request with `verify=true` adds events to the registration history of the tenant. Use it after a registration or when you examine a problem, not for frequent polling.
</Note>

### Register on Peppol

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r/peppol/register" \
       -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY" \
       -H "Content-Type: application/json" \
       -d '{
         "peppol_id": "0208:1018265814"
       }'
  ```

  ```javascript Node.js theme={null}
  const tenantId = 'ten-9k2m4p7q3w5x8r';

  const response = await fetch(
    `https://api.e-invoice.be/api/admin/tenants/${tenantId}/peppol/register`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.E_INVOICE_ORG_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ peppol_id: '0208:1018265814' }),
    },
  );

  if (!response.ok) {
    throw new Error(`${response.status}: ${await response.text()}`);
  }

  const registration = await response.json();
  console.log(registration.success, registration.registration.method);
  ```

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

  import requests

  tenant_id = "ten-9k2m4p7q3w5x8r"

  response = requests.post(
      f"https://api.e-invoice.be/api/admin/tenants/{tenant_id}/peppol/register",
      headers={"Authorization": f"Bearer {os.environ['E_INVOICE_ORG_API_KEY']}"},
      json={"peppol_id": "0208:1018265814"},
      timeout=60,
  )
  response.raise_for_status()

  registration = response.json()
  print(registration["success"], registration["registration"]["method"])
  ```

  ```php PHP theme={null}
  <?php
  $tenantId = 'ten-9k2m4p7q3w5x8r';

  $ch = curl_init("https://api.e-invoice.be/api/admin/tenants/$tenantId/peppol/register");
  curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
          'Authorization: Bearer ' . getenv('E_INVOICE_ORG_API_KEY'),
          'Content-Type: application/json',
      ],
      CURLOPT_POSTFIELDS => json_encode(['peppol_id' => '0208:1018265814']),
  ]);

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

  if ($status !== 200) {
      throw new RuntimeException("$status: $result");
  }

  $registration = json_decode($result, true);
  echo $registration['registration']['method'];
  ```

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

  var tenantId = "ten-9k2m4p7q3w5x8r";

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

  var response = await client.PostAsJsonAsync(
      $"/api/admin/tenants/{tenantId}/peppol/register",
      new { peppol_id = "0208:1018265814" });
  response.EnsureSuccessStatusCode();

  var registration = await response.Content.ReadFromJsonAsync<JsonElement>();
  Console.WriteLine(registration.GetProperty("registration").GetProperty("method").GetString());
  ```
</CodeGroup>

<ParamField body="peppol_id" type="string" required>
  Peppol ID to register, in the form `scheme:identifier`. The scheme must be one of the [supported schemes](#supported-schemes). The API changes the ID to lower case and removes spaces at the start and the end.
</ParamField>

<ParamField body="dry_run" type="boolean" default="false">
  Set to `true` to do all checks and make no change on the SMP. See [Dry run](#dry-run).
</ParamField>

The API does these steps:

1. It checks the scheme and the format of the identifier.
2. It looks for the Peppol ID on the network. An ID that is active at a different access point gives `409 Conflict`.
3. It creates the service group on the SMP.
4. It adds the six default document types: invoice, credit note, self-billing invoice, self-billing credit note, invoice response and message level response.
5. It adds a business card. For a Belgian Peppol ID, the company data comes from the KBO (Crossroads Bank for Enterprises).

The response has the status `200 OK`:

```json theme={null}
{
  "success": true,
  "peppol_id": "0208:1018265814",
  "company_data": {
    "company_number": "1018265814",
    "company_name": "E-INVOICE BV",
    "company_country": "BE",
    "company_address": "Brusselsesteenweg 119",
    "company_zip": "1980",
    "company_city": "Zemst"
  },
  "registration": {
    "registered": true,
    "method": "direct_registration",
    "migration_result": null,
    "message": null
  },
  "business_card": {
    "success": true,
    "business_card_data": {
      "name": "E-INVOICE BV",
      "country_code": "BE",
      "geographical_information": "Zemst, BE",
      "vat_number": "BE1018265814",
      "website_url": "https://e-invoice.be/company/1018265814",
      "contact_email": "support@e-invoice.be"
    },
    "message": null,
    "error": null,
    "error_type": null,
    "dry_run": null
  },
  "document_endpoints": {
    "success": true,
    "message": "Document endpoints added successfully",
    "error": null,
    "error_type": null,
    "dry_run": null
  }
}
```

For a Belgian Peppol ID, `company_data` also contains the KBO fields `status`, `juridical_situation`, `type_of_enterprise`, `juridical_form`, `start_date` and `address_type`. For other countries, `company_data` contains only `company_number`; the other fields are `null`.

The operation is idempotent. If the Peppol ID is already on the SMP of e-invoice.be, `registration.method` is `already_exists` and the API makes no new service group.

| Status | Cause |
| - | - |
| `400 Bad Request` | The Peppol ID has an incorrect format, the scheme is not supported, or the identifier does not obey the rules of the scheme. |
| `403 Forbidden` | The organisation is in test mode. |
| `409 Conflict` | The Peppol ID is active at a different access point. |
| `502 Bad Gateway` | An external service (the SMP) failed. Try again later. |

### Dry run

With `"dry_run": true`, the API does the checks of a registration (scheme, identifier format, lookup on the network, KBO lookup for Belgium) and makes no change on the SMP. Use it to find an incorrect or already registered Peppol ID before the real registration.

```bash theme={null}
curl -X POST "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r/peppol/register" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "peppol_id": "0208:1018265814",
       "dry_run": true
     }'
```

```json theme={null}
{
  "success": true,
  "peppol_id": "0208:1018265814",
  "company_data": {
    "company_number": "1018265814",
    "company_name": "E-INVOICE BV",
    "company_country": "BE",
    "company_address": "Brusselsesteenweg 119",
    "company_zip": "1980",
    "company_city": "Zemst"
  },
  "registration": {
    "registered": true,
    "method": "direct_registration",
    "migration_result": null,
    "message": null
  },
  "business_card": {
    "success": true,
    "business_card_data": null,
    "message": "DRY RUN: Would add business card",
    "error": null,
    "error_type": null,
    "dry_run": true
  },
  "document_endpoints": {
    "success": true,
    "message": "DRY RUN: Would add document endpoints",
    "error": null,
    "error_type": null,
    "dry_run": true
  }
}
```

<Note>
  In a dry run, `registration.registered` is `true` although nothing is registered. Read `business_card.dry_run` and `document_endpoints.dry_run` to identify a dry-run response. A dry run does not add events to `publications`.
</Note>

For a test-mode tenant, the API always does a dry run, whatever the value of `dry_run`. [Update the business card](#update-the-business-card) and [Unregister from Peppol](#unregister-from-peppol) return `{"success": true}` for a test-mode tenant and make no change on the SMP.

### Supported schemes

Registration accepts a Peppol ID only when its scheme is in this list. The list is smaller than the list of schemes that you can send to.

| Country | Scheme | Identifier |
| - | - | - |
| Belgium (BE) | `0208` | Enterprise number (CBE), 10 digits with a valid check digit |
| Netherlands (NL) | `0106`, `0190`, `9944` | KvK number, OIN (government), VAT number |
| Finland (FI) | `0216` | OVT code |
| Norway (NO) | `0192` | Organisasjonsnummer |
| Sweden (SE) | `0007` | Organisationsnummer |
| Denmark (DK) | `0184`, `0198` | CVR number, SE number |
| Iceland (IS) | `0196` | Kennitala |
| Spain (ES) | `9920` | VAT number |
| Ireland (IE) | `9935` | VAT number |
| Greece (GR) | `9933` | VAT number |
| Poland (PL) | `9945` | VAT number |
| Portugal (PT) | `9946` | VAT number |
| Bulgaria (BG) | `9926` | VAT number |
| Cyprus (CY) | `9928` | VAT number |
| Czech Republic (CZ) | `9929` | VAT number |
| Croatia (HR) | `9934` | VAT number |
| Hungary (HU) | `9910` | VAT number |
| Malta (MT) | `9943` | VAT number |
| Romania (RO) | `9947` | VAT number |
| Slovenia (SI) | `9949` | VAT number |
| Slovakia (SK) | `9950` | VAT number |
| Liechtenstein (LI) | `9936` | VAT number |
| All countries | `0088` | Global Location Number (GLN) |

Schemes of other countries (for example Germany, Austria, Italy and Luxembourg) and the Belgian VAT scheme `9925` are not accepted. French schemes need a separate enrolment; send an email to [support@e-invoice.be](mailto:support@e-invoice.be). A scheme that is not supported gives `400 Bad Request`:

```json theme={null}
{
  "detail": "This Peppol ID is currently not supported and cannot be registered. Contact support@e-invoice.be"
}
```

The company data lookup (KBO) is for Belgium only. For a Peppol ID of a different country, the API has no company name, so the business card gets only the country of the scheme. The register request has no field for the company name. Set the name with [Update the business card](#update-the-business-card) after the registration.

The scheme codes are from the Peppol participant identifier scheme code list, version 9.1. This is the version that the API uses. For the table of the most frequent schemes that you can send to, see [Look up Peppol participants](/guides/lookup-participants).

### Update the business card

Change the company name on the business card of a registered Peppol ID. The name is the only field that you can change.

```bash theme={null}
curl -X PUT "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r/peppol/business-card" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "peppol_id": "0208:1018265814",
       "name": "E-INVOICE BV"
     }'
```

<ParamField body="peppol_id" type="string" required>
  Peppol ID of the business card. It must be in the `peppol_ids` of the tenant; if not, the response is `403 Forbidden`.
</ParamField>

<ParamField body="name" type="string">
  New company name. Include the company type, for example `BV` or `NV`.
</ParamField>

```json theme={null}
{
  "success": true
}
```

If the Peppol ID has no business card on the SMP, the response is `400 Bad Request`:

```json theme={null}
{
  "detail": "No existing business card found for this Peppol ID"
}
```

### Unregister from Peppol

Remove the document types, the business card and the service group of a Peppol ID from the SMP of e-invoice.be. The tenant can then not receive documents through e-invoice.be. It can continue to send documents as a [send-only tenant](#send-only-tenants).

```bash theme={null}
curl -X DELETE "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r/peppol/unregister" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "peppol_id": "0208:1018265814"
     }'
```

<ParamField body="peppol_id" type="string" required>
  Peppol ID to unregister. It must be in the `peppol_ids` of the tenant; if not, the response is `403 Forbidden`.
</ParamField>

```json theme={null}
{
  "success": true,
  "warnings": null
}
```

The operation is idempotent. If the Peppol ID was already removed, the response is:

```json theme={null}
{
  "success": true,
  "warnings": ["Participant was already unregistered"]
}
```

### Send-only tenants

A tenant can send documents through Peppol without a registration on the SMP and the SML. Use a send-only tenant when the company receives its documents through a different access point, or does not want to receive documents at this time.

There is no `send_only` field. A tenant is send-only because [Register on Peppol](#register-on-peppol) was not called for it.

#### Create a send-only tenant

<Steps>
  <Step title="Create the tenant">
    Call [Create a tenant](#create-a-tenant) and put the Peppol ID in `peppol_ids`.
  </Step>

  <Step title="Create an API key">
    Call [Create an API key](#create-an-api-key) for the tenant. The returned `id` is the bearer token of the tenant.
  </Step>

  <Step title="Send documents with the tenant API key">
    Create a document with `POST /api/documents/` and send it. See [Create e-invoices](/guides/creating-invoices). Do not call the register operation.

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

    The response has the status `200 OK` and contains the document. This example shows the first fields:

    ```json theme={null}
    {
      "id": "doc-7h3k9m2p4q6r8t",
      "state": "TRANSIT",
      "direction": "OUTBOUND"
    }
    ```
  </Step>
</Steps>

<Warning>
  The API does not validate `peppol_ids` when you create or update a tenant. It accepts an incorrect Peppol ID, for example a `0208` number with 9 digits or with an incorrect check digit. The register operation is the only step that validates the ID, and you do not call it for a send-only tenant. Validate the Peppol ID in your system before you create the tenant.
</Warning>

<Note>
  A French send-only tenant must be enrolled, but it can have zero publications. Send an email to [support@e-invoice.be](mailto:support@e-invoice.be) for the enrolment procedure.
</Note>

#### What a send-only tenant can do

| Capability | Send-only tenant |
| - | - |
| Send invoices and credit notes | Possible. The API only checks that the sender Peppol ID is in the `peppol_ids` of the tenant. |
| Send [self-billing](/guides/self-billing) documents | Possible. The check applies to the **receiver** Peppol ID, because the tenant is the receiver. |
| Receive documents through e-invoice.be | Not possible. The Peppol ID is not published, so senders cannot find it, or they find the registration at a different access point. |

If the sender Peppol ID (for self-billing documents: the receiver Peppol ID) is not in the `peppol_ids` of the tenant, the send request fails with `409 Conflict`:

```json theme={null}
{
  "detail": "Derived sender '0208:0987654321' is not in tenant peppol_ids"
}
```

#### Status of a send-only tenant

[Check the registration status](#check-the-registration-status) shows the Peppol ID of a send-only tenant as not published:

```json theme={null}
{
  "peppol_id": "0208:1018265814",
  "state": "not_registered",
  "smp": {
    "service_group": null,
    "business_card": null,
    "document_types": null
  },
  "last_checked_at": "2026-04-16T10:00:00.000000",
  "verified": false,
  "publications": [
    {
      "peppol_id": "0208:1018265814",
      "state": "not_published",
      "latest_event": null,
      "source": null,
      "detail": null,
      "observed_at": null
    }
  ]
}
```

`state` is `other` when the Peppol ID is registered at a different access point.

#### Become a receiver later

Call [Register on Peppol](#register-on-peppol) when the tenant is ready to receive documents. No tenant change is necessary.

## Testing

Use the organisation API key of a test-mode organisation to test your integration. Each tenant that you create with this key is a test-mode tenant: its sends go to email, and no Peppol traffic occurs. See [Test mode and sandbox companies](/environments).

### Simulate an inbound document

Put a UBL document in the inbox of a test-mode tenant, as if it was received through Peppol. The API creates the document in the `RECEIVED` state and sends the `document.received` [webhook](/essentials/webhooks) event to the webhooks of the tenant.

```bash theme={null}
curl -X POST "https://api.e-invoice.be/api/admin/tenants/ten-9k2m4p7q3w5x8r/simulate-inbound" \
     -H "Authorization: Bearer $E_INVOICE_ORG_API_KEY" \
     -F "ubl_file=@invoice.xml"
```

<ParamField body="ubl_file" type="file" required>
  UBL invoice or credit note as an XML file. The request is `multipart/form-data`; a raw XML request body is not accepted. The maximum size is 25 MB.
</ParamField>

The response has the status `201 Created`:

```json theme={null}
{
  "document_id": "doc-7h3k9m2p4q6r8t",
  "state": "RECEIVED"
}
```

| Status | Cause |
| - | - |
| `400 Bad Request` | The file is empty, or the tenant is not in test mode. |
| `413 Request Entity Too Large` | The file is larger than 25 MB. |

The API reads the sender and receiver Peppol IDs, the document type and the line items from the UBL. Read the result with `GET /api/documents/{document_id}` and the tenant API key.

For the procedure in the app and for the tenant-side view, see [Testing received documents](/environments#testing-received-documents) and [Receive documents](/guides/receiving-documents).

## Onboarding example

The script below creates a tenant, creates an API key and registers the tenant on Peppol. Remove the registration step for a [send-only tenant](#send-only-tenants).

<Accordion title="Complete onboarding script (Node.js and Python)">
  <CodeGroup>
    ```javascript Node.js theme={null}
    const BASE_URL = 'https://api.e-invoice.be';

    async function admin(method, path, body) {
      const response = await fetch(`${BASE_URL}${path}`, {
        method,
        headers: {
          Authorization: `Bearer ${process.env.E_INVOICE_ORG_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: body ? JSON.stringify(body) : undefined,
      });
      if (!response.ok) {
        throw new Error(`${method} ${path}: ${response.status} ${await response.text()}`);
      }
      return response.status === 204 ? null : response.json();
    }

    async function onboardCustomer(customer) {
      // 1. Create the tenant with its Peppol ID.
      const tenant = await admin('POST', '/api/admin/tenants', {
        name: customer.slug,
        description: customer.companyName,
        company_name: customer.companyName,
        company_number: customer.companyNumber,
        company_tax_id: customer.vatNumber,
        peppol_ids: [customer.peppolId],
      });

      // 2. Create an API key. The id is the bearer token of the tenant.
      const apiKey = await admin('POST', `/api/admin/tenants/${tenant.id}/api-keys`, {
        name: 'production-key',
      });

      // 3. Register on Peppol, so that the tenant can receive documents.
      const registration = await admin(
        'POST',
        `/api/admin/tenants/${tenant.id}/peppol/register`,
        { peppol_id: customer.peppolId },
      );

      return {
        tenantId: tenant.id,
        apiKey: apiKey.id,
        registered: registration.success,
      };
    }

    const result = await onboardCustomer({
      slug: 'e-invoice-bv',
      companyName: 'E-INVOICE BV',
      companyNumber: '1018265814',
      vatNumber: 'BE1018265814',
      peppolId: '0208:1018265814',
    });

    console.log('Tenant:', result.tenantId, 'registered:', result.registered);
    ```

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

    import requests

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


    def admin(method, path, body=None):
        response = requests.request(
            method, f"{BASE_URL}{path}", headers=HEADERS, json=body, timeout=60
        )
        response.raise_for_status()
        return None if response.status_code == 204 else response.json()


    def onboard_customer(customer):
        # 1. Create the tenant with its Peppol ID.
        tenant = admin(
            "POST",
            "/api/admin/tenants",
            {
                "name": customer["slug"],
                "description": customer["company_name"],
                "company_name": customer["company_name"],
                "company_number": customer["company_number"],
                "company_tax_id": customer["vat_number"],
                "peppol_ids": [customer["peppol_id"]],
            },
        )

        # 2. Create an API key. The id is the bearer token of the tenant.
        api_key = admin(
            "POST",
            f"/api/admin/tenants/{tenant['id']}/api-keys",
            {"name": "production-key"},
        )

        # 3. Register on Peppol, so that the tenant can receive documents.
        registration = admin(
            "POST",
            f"/api/admin/tenants/{tenant['id']}/peppol/register",
            {"peppol_id": customer["peppol_id"]},
        )

        return {
            "tenant_id": tenant["id"],
            "api_key": api_key["id"],
            "registered": registration["success"],
        }


    result = onboard_customer(
        {
            "slug": "e-invoice-bv",
            "company_name": "E-INVOICE BV",
            "company_number": "1018265814",
            "vat_number": "BE1018265814",
            "peppol_id": "0208:1018265814",
        }
    )

    print("Tenant:", result["tenant_id"], "registered:", result["registered"])
    ```
  </CodeGroup>
</Accordion>

## Errors

The Admin API returns errors as JSON with a `detail` field. For the general error model, see [Errors and troubleshooting](/guides/errors).

| Status | `detail` (example) | Cause and solution |
| - | - | - |
| `401 Unauthorized` | `Admin authentication required` | The request has no `Authorization` header. |
| `401 Unauthorized` | `Invalid admin authentication` | The key is not an organisation API key. Make sure that you do not send a tenant API key. |
| `403 Forbidden` | `This Peppol ID does not belong to this tenant.` | The Peppol ID in the request body is not in the `peppol_ids` of the tenant. |
| `404 Not Found` | `Tenant with ID ten-... not found` | The tenant does not exist or is a tenant of a different organisation. For the list, create and latest API key operations, the Peppol operations and Simulate inbound: also when the tenant is deleted. |
| `409 Conflict` | `Tenant with name 'e-invoice-bv' already exists` | Use a different tenant name, or find the existing tenant with [List tenants](#list-tenants). |
| `422 Unprocessable Entity` | Array of validation errors | A mandatory field is missing or a field has an incorrect type. |

Example of a `422` response:

```json theme={null}
{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "name"],
      "msg": "Field required",
      "input": {}
    }
  ]
}
```

## Best practices

<AccordionGroup>
  <Accordion title="Keep keys secure">
    * The `id` of an API key is the bearer token. Keep it secret from the moment that you receive it.
    * Do not write organisation API keys or tenant API keys to logs.
    * Store tenant API keys encrypted.
    * Give keys to customers only through a secure channel.
    * Rotate keys at regular intervals. See [Rotate an API key](#rotate-an-api-key).
  </Accordion>

  <Accordion title="Use a naming convention for tenants">
    Use tenant names that you can calculate from your own data, for example `cust-<your customer ID>` or a slug of the company name. The API refuses a second active tenant with the same name (`409 Conflict`), which helps you to prevent duplicates.
  </Accordion>

  <Accordion title="Make onboarding safe to repeat">
    If [Create a tenant](#create-a-tenant) returns `409 Conflict`, the tenant exists. Get it from [List tenants](#list-tenants) and continue with the next step. [Register on Peppol](#register-on-peppol) and [Unregister from Peppol](#unregister-from-peppol) are idempotent, so you can repeat them after a network error.
  </Accordion>

  <Accordion title="Keep an audit log">
    Record each admin operation in your system: the operation, the tenant ID, the user and the time. Do not record API keys.
  </Accordion>

  <Accordion title="Check before you register">
    Validate the Peppol ID in your system, then call [Register on Peppol](#register-on-peppol) with `"dry_run": true`. Do the real registration only when the dry run is successful.
  </Accordion>
</AccordionGroup>

## Rate limits

Rate limits apply to each API key on write and validation endpoints. See [Errors and troubleshooting](/guides/errors). When you exceed a limit, the API returns `429 Too Many Requests` with a `Retry-After` header. Obey `Retry-After` and use exponential backoff when you do operations for many tenants.

## Support

For access to the Admin API or for technical questions, send an email to [support@e-invoice.be](mailto:support@e-invoice.be).

## Next Steps

<CardGroup cols={2}>
  <Card title="Reseller programme" icon="handshake" href="/reseller-programme">
    Learn how to become a reseller and get an organisation API key.
  </Card>

  <Card title="Create e-invoices" icon="file-invoice" href="/guides/creating-invoices">
    Send documents with the API key of a tenant.
  </Card>

  <Card title="Test mode and sandbox companies" icon="flask" href="/environments">
    Test mode, sandbox companies and simulated inbound documents.
  </Card>

  <Card title="Usage statistics and credits" icon="chart-line" href="/guides/usage-statistics">
    Read the usage of a tenant with its API key.
  </Card>
</CardGroup>


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