# Envelopify API

Verstuur echte brieven met postzegels vanuit je eigen software. Eén aanroep per brief. Wij printen, plakken echte postzegels en posten via bpost. bpost bezorgt binnen 2 tot 3 werkdagen na afgifte. Privacy: pdf’s en ontvangersgegevens worden standaard 30 dagen na posten gewist (klantkeuze 7 tot 90 dagen, dagelijkse opruiming). Daarna blijft de status beschikbaar, maar zijn ontvangersvelden leeg en geeft de pdf-download 404. Invulvelden worden alleen verwerkt in de brief, niet nog eens als losse gegevens bewaard.

Basis-URL: `https://envelopify.lovable.app/api/v1`

Documentatie: https://envelopify.lovable.app/developers. OpenAPI: https://envelopify.lovable.app/openapi.json.

## In 5 minuten starten

1. **Sleutel aanmaken.** Maak een gratis account, ga naar API-sleutels en kies Testsleutel maken. Vervang in de voorbeelden `test_JOUW_SLEUTEL` door je sleutel.
2. **Testbrief sturen.** De aanroep bij Brief versturen gebruikt onze voorbeeld-pdf en werkt zoals hij er staat. Er wordt niets geprint of aangerekend.
3. **Status opvragen.** Neem het `id` uit het antwoord en vraag de brief op met `GET /api/v1/letters/{id}`. Met `/pdf` erachter zie je de pdf zoals wij hem printen.
4. **Statusmeldingen instellen.** Registreer een https-link met `POST /api/v1/webhooks`, dan laten we je weten wanneer je brief geprint en gepost is.
5. **Live gaan.** Laad tegoed op in het portaal en klik bij je sleutel op Zet live. Nieuwe sleutels starten altijd in testmodus. Je sleutel zelf verandert niet.

## Inloggen met sleutel

Stuur je sleutel mee in elke aanvraag via de header `Authorization: Bearer <sleutel>`. Nieuwe sleutels starten in testmodus; je zet ze live in het portaal (API-sleutels, Zet live). De modus hangt aan de sleutel, niet aan het begin van de tekst. Met een sleutel in testmodus worden brieven nooit verstuurd of aangerekend; ze krijgen status `received`. We tonen een sleutel maar één keer. Bewaar hem als geheim op je server en nooit in browsercode. Een ontbrekende, ongeldige of ingetrokken sleutel geeft `401 unauthorized`.

## Gebeurtenis in wachtrij: `POST /api/v1/flow-events`

Voor een actieve herinneringsflow met een bewaarde ontvanger. Maakt een eenmalige reeks, niet onmiddellijk een brief. De dagelijkse verwerking gebruikt dezelfde prijs- en stopcontrole. Bij onvoldoende tegoed blijft de gebeurtenis in de wachtrij. event_key voorkomt dubbels per flow en sleutelmodus. Dagmaximum en wachttijd per ontvanger gelden ook in testmodus; testreeksen versturen en kosten niets en worden alleen door een beheerder getest.

Inhoud (JSON):

| Veld | Type | Verplicht | Uitleg |
|---|---|---|---|
| `flow_id` | string | ja | Id van actieve flow. |
| `contact_id` | string | ja | Id van eigen ontvanger. |
| `event_key` | string | ja | Unieke gebeurtenisreferentie, 1 tot 120 tekens. |
| `order_cents` | integer | nee | Bedrag voor flowvoorwaarde, standaard 0. |
| `variables` | object | nee | Sjabloonvelden, enkel nodige gegevens. |

cURL:

```bash
curl -X POST https://envelopify.lovable.app/api/v1/flow-events \
  -H "Authorization: Bearer test_JOUW_SLEUTEL" \
  -H "Content-Type: application/json" \
  -d '{
  "flow_id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
  "contact_id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
  "event_key": "herinnering-104",
  "variables": {}
}'
```

JavaScript:

```js
// Node 18 of nieuwer, bewaar als voorbeeld.mjs
const KEY = "test_JOUW_SLEUTEL";

const res = await fetch("https://envelopify.lovable.app/api/v1/flow-events", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "flow_id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
    "contact_id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
    "event_key": "herinnering-104",
    "variables": {}
  }),
});
console.log(res.status, await res.json());
```

Python:

```python
import requests  # pip install requests

KEY = "test_JOUW_SLEUTEL"

res = requests.post(
    "https://envelopify.lovable.app/api/v1/flow-events",
    headers={"Authorization": "Bearer " + KEY},
    json={
      "flow_id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
      "contact_id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
      "event_key": "herinnering-104",
      "variables": {}
    },
)
print(res.status_code, res.json())
```

PHP:

```php
<?php
$key = "test_JOUW_SLEUTEL";

$ch = curl_init("https://envelopify.lovable.app/api/v1/flow-events");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . $key,
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => '{
    "flow_id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
    "contact_id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
    "event_key": "herinnering-104",
    "variables": {}
  }',
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $body, "\n";
```

Antwoord `202`:

```json
{
  "status": "queued",
  "test": true
}
```

## Brief versturen: `POST /api/v1/letters`

Maakt een brief aan. Geef precies één bron: een pdf (`pdf_base64` of `pdf_url`) of een sjabloon (`template_id` of `template_code`) met `variables`. Bij een pdf geldt je accountkeuze: gratis adresblad vooraan of een wit adresvak op de eerste pagina met `options.address_on_first_page`. Klein gaat tot 50 g, middel tot 100 g en groot tot 350 g. Middel en groot zijn alleen beschikbaar met een bevestigde, geactiveerde meerprijs. Het adresblad telt mee voor het gewicht, niet voor de basisprijs. Antwoord `201` met id, status, prijs met opsplitsing (`price_breakdown`), aantal vellen (adresblad inbegrepen), uiterste postdatum volgens de snelheid en `test`. Met een testsleutel is de status `received` en `post_date` leeg. Een geweigerde brief geeft `422` met een foutcode en het `letter_id`.

Inhoud (JSON):

| Veld | Type | Verplicht | Uitleg |
|---|---|---|---|
| `options.address_on_first_page` | boolean | nee | Voor opgeladen pdf: wit vast adresvak op de eerste pagina, zonder extra adresblad. Bedekte inhoud wordt wit. Zonder keuze geldt je accountstandaard (aanvankelijk adresblad). |
| `options.corrections` | object | nee | Pdf-correcties: region {left,top,width,height} in mm op A4, window left/right/both, first_page, white_margins, flatten en a4_portrait. Zonder dit object geldt de bewaarde accountregel. Met region wordt het oude adresvak wit gemaakt. Je origineel blijft bewaard. |
| `recipient.language` | string | nee | Optioneel: nl, fr, en of de. Kies de bewaarde sjabloonvariant voor deze taal; anders de basistekst. Zonder taal kiezen we volgens de Belgische postcode. |
| `recipient.company` | string | nee | Optioneel. Bedrijfsnaam. |
| `recipient.name` | string | ja | Naam van de ontvanger. Naam of bedrijf is verplicht. |
| `recipient.street` | string | ja | Straat. |
| `recipient.number` | string | ja | Huisnummer. |
| `recipient.box` | string | nee | Optioneel. Busnummer. |
| `recipient.postal_code` | string | ja | Postcode. In België 4 cijfers. |
| `recipient.city` | string | ja | Gemeente. |
| `recipient.country` | string | nee | Landcode (2 letters), standaard BE. Ook alle andere EU-landen (bv. NL, FR, DE), +€1,50 per brief, levertermijn 5 tot 10 werkdagen na verzending, geen express. |
| `pdf_base64` | string | nee | Pdf als base64, A4, maximaal 20 MB. |
| `pdf_url` | string | nee | Rechtstreekse openbare https-link naar je pdf, maximaal 20 MB. Geen doorverwijzing, login, privaat netwerk of afwijkende poort. |
| `template_id` | string | nee | Id van een sjabloon, in plaats van een pdf. |
| `template_code` | string | nee | Of de code van een sjabloon, bv. herinnering. |
| `variables` | object | nee | Waarden voor de velden in je sjabloon, bv. { "voornaam": "An" }. |
| `options.flat` | boolean | nee | Ongevouwen in grote envelop, handmatig verwerkt. Alleen beschikbaar na activering. Meerprijs via /price. |
| `options.registered` | boolean | nee | Aangetekend papier, alleen BE en standard/express, na activering. Opvolging: registered_tracking, registered_handed_in_at en tijdelijke registered_receipt_url. |
| `options.color` | boolean | nee | Kleur. Standaard zwart-wit. Meerprijs per vel via /price. |
| `options.duplex` | boolean | nee | Dubbelzijdig: twee pagina's per vel. Standaard enkelzijdig. |
| `options.qr_tracking` | boolean | nee | Dynamische QR-code met scanmeting, +€0,05 per brief. |
| `options.speed` | string | nee | Snelheid: `standard` (binnen 3 werkdagen verstuurd), `express` (zelfde werkdag als aangeleverd vóór 12:00, +€0,15 per brief, minimum €15,00 per dag) of `flex` (binnen 5 werkdagen, €-0,05 per brief). Standaard `standard`. |
| `options.qr_target_url` | string | nee | Verplicht bij qr_tracking. Https-link waar de QR-code naartoe gaat. Achteraf aanpasbaar in het portaal. |
| `enclosure_batch_id` | string | nee | Id van ontvangen drukwerk, zichtbaar in het portaal. Eén exemplaar per brief, alleen na activering en ontvangstcontrole. Annulering voor productie geeft voorraad terug. |
| `reference` | string | nee | Optioneel. Jouw eigen referentie, bv. factuurnummer. |
| `post_on` | string | nee | Optioneel: gekozen postdag (YYYY-MM-DD), een werkdag maximaal 90 kalenderdagen vooruit. Geen bezorgdatum. Tegoed wordt bij aanmaak gereserveerd door afboeking en bij annuleren vóór productie teruggestort. |

cURL:

```bash
curl -X POST https://envelopify.lovable.app/api/v1/letters \
  -H "Authorization: Bearer test_JOUW_SLEUTEL" \
  -H "Idempotency-Key: factuur-2026-104" \
  -H "Content-Type: application/json" \
  -d '{
  "recipient": {
    "name": "An Peeters",
    "street": "Meir",
    "number": "12",
    "postal_code": "2000",
    "city": "Antwerpen"
  },
  "pdf_url": "https://envelopify.lovable.app/voorbeeld.pdf",
  "options": {
    "color": false
  },
  "reference": "factuur-2026-104"
}'
```

JavaScript:

```js
// Node 18 of nieuwer, bewaar als voorbeeld.mjs
const KEY = "test_JOUW_SLEUTEL";

const res = await fetch("https://envelopify.lovable.app/api/v1/letters", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + KEY,
    "Idempotency-Key": "factuur-2026-104",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "recipient": {
      "name": "An Peeters",
      "street": "Meir",
      "number": "12",
      "postal_code": "2000",
      "city": "Antwerpen"
    },
    "pdf_url": "https://envelopify.lovable.app/voorbeeld.pdf",
    "options": {
      "color": false
    },
    "reference": "factuur-2026-104"
  }),
});
console.log(res.status, await res.json());
```

Python:

```python
import requests  # pip install requests

KEY = "test_JOUW_SLEUTEL"

res = requests.post(
    "https://envelopify.lovable.app/api/v1/letters",
    headers={"Authorization": "Bearer " + KEY, "Idempotency-Key": "factuur-2026-104"},
    json={
      "recipient": {
        "name": "An Peeters",
        "street": "Meir",
        "number": "12",
        "postal_code": "2000",
        "city": "Antwerpen"
      },
      "pdf_url": "https://envelopify.lovable.app/voorbeeld.pdf",
      "options": {
        "color": False
      },
      "reference": "factuur-2026-104"
    },
)
print(res.status_code, res.json())
```

PHP:

```php
<?php
$key = "test_JOUW_SLEUTEL";

$ch = curl_init("https://envelopify.lovable.app/api/v1/letters");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . $key,
    "Idempotency-Key: factuur-2026-104",
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => '{
    "recipient": {
      "name": "An Peeters",
      "street": "Meir",
      "number": "12",
      "postal_code": "2000",
      "city": "Antwerpen"
    },
    "pdf_url": "https://envelopify.lovable.app/voorbeeld.pdf",
    "options": {
      "color": false
    },
    "reference": "factuur-2026-104"
  }',
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $body, "\n";
```

Antwoord `201`:

```json
{
  "id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
  "status": "scheduled",
  "price_cents": 129,
  "price_breakdown": {
    "unit_price_cents": 129,
    "tier": 1,
    "tier_fixed": false,
    "volume_30d": 120,
    "letters_to_next_tier": 379,
    "pages": 1,
    "sheets": 1,
    "address_sheet": true,
    "total_sheets": 2,
    "color": false,
    "duplex": false,
    "speed": "standard",
    "destination": "BE",
    "options": [],
    "total_cents": 129,
    "on_request": false,
    "currency": "EUR",
    "vat_included": false
  },
  "sheet_count": 2,
  "post_date": "2026-11-13",
  "free_trial": false,
  "test": false
}
```

## Status opvragen: `GET /api/v1/letters/{id}`

Geeft alle velden van de brief, de tijdstippen, de prijsopsplitsing, het aantal QR-scans (`scan_count`) en zodra hij gepost is een tijdelijke link naar de foto van de batch (`batch_photo_url`, 1 uur geldig).

cURL:

```bash
curl -X GET https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029 \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
```

JavaScript:

```js
// Node 18 of nieuwer, bewaar als voorbeeld.mjs
const KEY = "test_JOUW_SLEUTEL";

const res = await fetch("https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029", {
  method: "GET",
  headers: {
    Authorization: "Bearer " + KEY,
  },
});
console.log(res.status, await res.json());
```

Python:

```python
import requests  # pip install requests

KEY = "test_JOUW_SLEUTEL"

res = requests.get(
    "https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
    headers={"Authorization": "Bearer " + KEY},
)
print(res.status_code, res.json())
```

PHP:

```php
<?php
$key = "test_JOUW_SLEUTEL";

$ch = curl_init("https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . $key,
  ],
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $body, "\n";
```

Antwoord `200`:

```json
{
  "id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
  "tracking_code": "B1D1A7F7",
  "reference": "factuur-2026-104",
  "status": "posted",
  "test": false,
  "reject_reason": null,
  "return_reason": null,
  "recipient": {
    "company": null,
    "name": "An Peeters",
    "street": "Meir",
    "number": "12",
    "box": null,
    "postal_code": "2000",
    "city": "Antwerpen",
    "country": "BE"
  },
  "source": "pdf",
  "color": false,
  "duplex": false,
  "qr_tracking": false,
  "sheet_count": 2,
  "price_cents": 129,
  "price_breakdown": {
    "unit_price_cents": 129,
    "tier": 1,
    "tier_fixed": false,
    "volume_30d": 120,
    "letters_to_next_tier": 379,
    "pages": 1,
    "sheets": 1,
    "address_sheet": true,
    "total_sheets": 2,
    "color": false,
    "duplex": false,
    "speed": "standard",
    "destination": "BE",
    "options": [],
    "total_cents": 129,
    "on_request": false,
    "currency": "EUR",
    "vat_included": false
  },
  "speed": "standard",
  "post_deadline": "2026-11-13",
  "created_at": "2026-11-12T10:02:11Z",
  "scheduled_at": "2026-11-12T10:02:11Z",
  "printed_at": "2026-11-13T09:40:00Z",
  "posted_at": "2026-11-13T16:02:11Z",
  "returned_at": null,
  "post_date": "2026-11-13",
  "scan_count": 0,
  "batch_photo_url": null
}
```

## Pdf bekijken: `GET /api/v1/letters/{id}/pdf`

Geeft de definitieve pdf zoals wij hem printen (`application/pdf`), met adresblad. Enkel voor je eigen brieven. Een geweigerde brief heeft geen pdf en geeft `404`.

cURL:

```bash
curl -X GET https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029/pdf \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
```

JavaScript:

```js
// Node 18 of nieuwer, bewaar als voorbeeld.mjs
const KEY = "test_JOUW_SLEUTEL";

const res = await fetch("https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029/pdf", {
  method: "GET",
  headers: {
    Authorization: "Bearer " + KEY,
  },
});
console.log(res.status, await res.json());
```

Python:

```python
import requests  # pip install requests

KEY = "test_JOUW_SLEUTEL"

res = requests.get(
    "https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029/pdf",
    headers={"Authorization": "Bearer " + KEY},
)
print(res.status_code, res.json())
```

PHP:

```php
<?php
$key = "test_JOUW_SLEUTEL";

$ch = curl_init("https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029/pdf");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . $key,
  ],
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $body, "\n";
```

Antwoord `200`: `application/pdf`

## Lijst: `GET /api/v1/letters`

Geeft je recentste brieven, nieuwste eerst, in `data`. Is er meer, dan krijg je een `next_cursor` die je meegeeft als `cursor`.

Filters (query):

| Veld | Type | Verplicht | Uitleg |
|---|---|---|---|
| `status` | string | nee | Filter op status, bv. posted. |
| `reference` | string | nee | Filter op je referentie. |
| `from` | string | nee | Vanaf datum, bv. 2026-11-01. |
| `to` | string | nee | Tot en met datum. |
| `limit` | integer | nee | Standaard 25, maximaal 100. |
| `cursor` | string | nee | Waarde van next_cursor. |

cURL:

```bash
curl -X GET https://envelopify.lovable.app/api/v1/letters?status=posted&limit=50 \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
```

JavaScript:

```js
// Node 18 of nieuwer, bewaar als voorbeeld.mjs
const KEY = "test_JOUW_SLEUTEL";

const res = await fetch("https://envelopify.lovable.app/api/v1/letters?status=posted&limit=50", {
  method: "GET",
  headers: {
    Authorization: "Bearer " + KEY,
  },
});
console.log(res.status, await res.json());
```

Python:

```python
import requests  # pip install requests

KEY = "test_JOUW_SLEUTEL"

res = requests.get(
    "https://envelopify.lovable.app/api/v1/letters?status=posted&limit=50",
    headers={"Authorization": "Bearer " + KEY},
)
print(res.status_code, res.json())
```

PHP:

```php
<?php
$key = "test_JOUW_SLEUTEL";

$ch = curl_init("https://envelopify.lovable.app/api/v1/letters?status=posted&limit=50");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . $key,
  ],
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $body, "\n";
```

Antwoord `200`:

```json
{
  "data": [
    {
      "id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
      "status": "posted",
      "reference": "factuur-2026-104"
    }
  ],
  "next_cursor": "MjAyNi0xMS0xMlQxMDo..."
}
```

## Annuleren: `DELETE /api/v1/letters/{id}`

Annuleert een brief zolang de batch van die dag nog open is. Je tegoed wordt teruggestort en je krijgt de brief terug met status `rejected`. Is de batch al afgesloten, dan krijg je `409 cutoff_passed`.

cURL:

```bash
curl -X DELETE https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029 \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
```

JavaScript:

```js
// Node 18 of nieuwer, bewaar als voorbeeld.mjs
const KEY = "test_JOUW_SLEUTEL";

const res = await fetch("https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029", {
  method: "DELETE",
  headers: {
    Authorization: "Bearer " + KEY,
  },
});
console.log(res.status, await res.json());
```

Python:

```python
import requests  # pip install requests

KEY = "test_JOUW_SLEUTEL"

res = requests.delete(
    "https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
    headers={"Authorization": "Bearer " + KEY},
)
print(res.status_code, res.json())
```

PHP:

```php
<?php
$key = "test_JOUW_SLEUTEL";

$ch = curl_init("https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . $key,
  ],
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $body, "\n";
```

## Prijs berekenen: `POST /api/v1/price`

Berekent de prijs van een brief met je huidige schijf, zonder iets aan te maken of af te rekenen. Zelfde opties als een brief, maar met het aantal bedrukte pagina's in plaats van een pdf. Met `address_sheet` (standaard true) reken je het gratis adresblad mee voor het maximum aantal vellen, zoals bij een opgeladen pdf; zet het op false voor een sjabloonbrief. Niet-geactiveerde gewichtsklassen geven `422 weight_class_unavailable`. Met `options.address_on_first_page` bereken je zonder extra adresblad.

Inhoud (JSON):

| Veld | Type | Verplicht | Uitleg |
|---|---|---|---|
| `options.address_on_first_page` | boolean | nee | Voor opgeladen pdf: wit vast adresvak op de eerste pagina, zonder extra adresblad. Bedekte inhoud wordt wit. Zonder keuze geldt je accountstandaard (aanvankelijk adresblad). |
| `options.flat` | boolean | nee | Ongevouwen in grote envelop, handmatig verwerkt. Alleen beschikbaar na activering. Meerprijs via /price. |
| `options.registered` | boolean | nee | Aangetekend papier, alleen BE en standard/express, na activering. Opvolging: registered_tracking, registered_handed_in_at en tijdelijke registered_receipt_url. |
| `enclosure_batch_id` | string | nee | Id van ontvangen drukwerk, zichtbaar in het portaal. Eén exemplaar per brief, alleen na activering en ontvangstcontrole. Annulering voor productie geeft voorraad terug. |
| `pages` | integer | ja | Aantal bedrukte pagina's (het adresblad niet meegerekend). |
| `address_sheet` | boolean | nee | Gratis adresblad meerekenen voor het maximum aantal vellen. Standaard true. |
| `options.country` | string | nee | Bestemmingsland, standaard BE. Een ander EU-land voegt de toeslag EU toe. |
| `options.color` | boolean | nee | Kleur, meerprijs per vel via /price. |
| `options.duplex` | boolean | nee | Dubbelzijdig. |
| `options.qr_tracking` | boolean | nee | QR-code met scanmeting. |
| `options.speed` | string | nee | standard, express of flex. |

cURL:

```bash
curl -X POST https://envelopify.lovable.app/api/v1/price \
  -H "Authorization: Bearer test_JOUW_SLEUTEL" \
  -H "Content-Type: application/json" \
  -d '{
  "pages": 3,
  "address_sheet": true,
  "options": {
    "color": false,
    "duplex": false,
    "qr_tracking": false,
    "speed": "standard"
  }
}'
```

JavaScript:

```js
// Node 18 of nieuwer, bewaar als voorbeeld.mjs
const KEY = "test_JOUW_SLEUTEL";

const res = await fetch("https://envelopify.lovable.app/api/v1/price", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "pages": 3,
    "address_sheet": true,
    "options": {
      "color": false,
      "duplex": false,
      "qr_tracking": false,
      "speed": "standard"
    }
  }),
});
console.log(res.status, await res.json());
```

Python:

```python
import requests  # pip install requests

KEY = "test_JOUW_SLEUTEL"

res = requests.post(
    "https://envelopify.lovable.app/api/v1/price",
    headers={"Authorization": "Bearer " + KEY},
    json={
      "pages": 3,
      "address_sheet": True,
      "options": {
        "color": False,
        "duplex": False,
        "qr_tracking": False,
        "speed": "standard"
      }
    },
)
print(res.status_code, res.json())
```

PHP:

```php
<?php
$key = "test_JOUW_SLEUTEL";

$ch = curl_init("https://envelopify.lovable.app/api/v1/price");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . $key,
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => '{
    "pages": 3,
    "address_sheet": true,
    "options": {
      "color": false,
      "duplex": false,
      "qr_tracking": false,
      "speed": "standard"
    }
  }',
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $body, "\n";
```

Antwoord `200`:

```json
{
  "price_breakdown": {
    "unit_price_cents": 129,
    "tier": 1,
    "tier_fixed": false,
    "volume_30d": 120,
    "letters_to_next_tier": 379,
    "pages": 3,
    "sheets": 3,
    "address_sheet": true,
    "total_sheets": 4,
    "color": false,
    "duplex": false,
    "speed": "standard",
    "destination": "BE",
    "options": [
      {
        "code": "extra_sheets",
        "label": "Extra vellen (2)",
        "amount_cents": 20
      }
    ],
    "total_cents": 149,
    "on_request": false,
    "currency": "EUR",
    "vat_included": false
  }
}
```

## Sjablonen opvragen: `GET /api/v1/templates`

Geeft je sjablonen in `data`.

cURL:

```bash
curl -X GET https://envelopify.lovable.app/api/v1/templates \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
```

JavaScript:

```js
// Node 18 of nieuwer, bewaar als voorbeeld.mjs
const KEY = "test_JOUW_SLEUTEL";

const res = await fetch("https://envelopify.lovable.app/api/v1/templates", {
  method: "GET",
  headers: {
    Authorization: "Bearer " + KEY,
  },
});
console.log(res.status, await res.json());
```

Python:

```python
import requests  # pip install requests

KEY = "test_JOUW_SLEUTEL"

res = requests.get(
    "https://envelopify.lovable.app/api/v1/templates",
    headers={"Authorization": "Bearer " + KEY},
)
print(res.status_code, res.json())
```

PHP:

```php
<?php
$key = "test_JOUW_SLEUTEL";

$ch = curl_init("https://envelopify.lovable.app/api/v1/templates");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . $key,
  ],
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $body, "\n";
```

Antwoord `200`:

```json
{
  "data": [
    {
      "id": "6c1f...",
      "name": "Herinnering",
      "code": "herinnering",
      "body": "Beste {{voornaam}}, ...",
      "created_at": "2026-11-12T10:00:00Z"
    }
  ]
}
```

## Sjabloon maken: `POST /api/v1/templates`

Maakt een sjabloon. Velden staan tussen dubbele accolades, bv. `{{voornaam}}`. Een code die al bestaat geeft `409 conflict`.

Inhoud (JSON):

| Veld | Type | Verplicht | Uitleg |
|---|---|---|---|
| `name` | string | ja | Naam. |
| `code` | string | ja | Unieke code: letters, cijfers, - en _. |
| `body` | string | ja | Tekst met velden. |

cURL:

```bash
curl -X POST https://envelopify.lovable.app/api/v1/templates \
  -H "Authorization: Bearer test_JOUW_SLEUTEL" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Herinnering",
  "code": "herinnering",
  "body": "Beste {{voornaam}},\n\nWe ontvingen nog geen betaling van {{bedrag}}."
}'
```

JavaScript:

```js
// Node 18 of nieuwer, bewaar als voorbeeld.mjs
const KEY = "test_JOUW_SLEUTEL";

const res = await fetch("https://envelopify.lovable.app/api/v1/templates", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Herinnering",
    "code": "herinnering",
    "body": "Beste {{voornaam}},\n\nWe ontvingen nog geen betaling van {{bedrag}}."
  }),
});
console.log(res.status, await res.json());
```

Python:

```python
import requests  # pip install requests

KEY = "test_JOUW_SLEUTEL"

res = requests.post(
    "https://envelopify.lovable.app/api/v1/templates",
    headers={"Authorization": "Bearer " + KEY},
    json={
      "name": "Herinnering",
      "code": "herinnering",
      "body": "Beste {{voornaam}},\n\nWe ontvingen nog geen betaling van {{bedrag}}."
    },
)
print(res.status_code, res.json())
```

PHP:

```php
<?php
$key = "test_JOUW_SLEUTEL";

$ch = curl_init("https://envelopify.lovable.app/api/v1/templates");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . $key,
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => '{
    "name": "Herinnering",
    "code": "herinnering",
    "body": "Beste {{voornaam}},\\n\\nWe ontvingen nog geen betaling van {{bedrag}}."
  }',
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $body, "\n";
```

## Tegoed en prijzen: `GET /api/v1/balance`

Geeft je huidig tegoed, je schijf, het aantal echte brieven in de voorbije 30 dagen, hoeveel brieven je nog verwijderd bent van de volgende korting en de prijslijst in cent, excl. btw: prijs per brief per schijf, meerprijzen voor opties en snelheden, en de regels voor express. `free_letters_remaining` is het aantal gratis proefbrieven dat je nog hebt.

cURL:

```bash
curl -X GET https://envelopify.lovable.app/api/v1/balance \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
```

JavaScript:

```js
// Node 18 of nieuwer, bewaar als voorbeeld.mjs
const KEY = "test_JOUW_SLEUTEL";

const res = await fetch("https://envelopify.lovable.app/api/v1/balance", {
  method: "GET",
  headers: {
    Authorization: "Bearer " + KEY,
  },
});
console.log(res.status, await res.json());
```

Python:

```python
import requests  # pip install requests

KEY = "test_JOUW_SLEUTEL"

res = requests.get(
    "https://envelopify.lovable.app/api/v1/balance",
    headers={"Authorization": "Bearer " + KEY},
)
print(res.status_code, res.json())
```

PHP:

```php
<?php
$key = "test_JOUW_SLEUTEL";

$ch = curl_init("https://envelopify.lovable.app/api/v1/balance");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . $key,
  ],
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $body, "\n";
```

Antwoord `200`:

```json
{
  "credit_cents": 4950,
  "free_letters_remaining": 3,
  "company_verified": true,
  "tier": 1,
  "tier_fixed": false,
  "volume_30d": 120,
  "letters_to_next_tier": 380,
  "unit_price_cents": 129,
  "tiers": [
    {
      "tier": 1,
      "from": 0,
      "unit_price_cents": 129
    },
    {
      "tier": 2,
      "from": 500,
      "unit_price_cents": 109
    },
    {
      "tier": 3,
      "from": 5000,
      "unit_price_cents": 99
    }
  ],
  "options": {
    "extra_sheet_cents": 10,
    "qr_tracking_cents": 5
  },
  "speeds": [
    {
      "speed": "standard",
      "name": "Standaard",
      "amount_cents": 0,
      "working_days": 3
    },
    {
      "speed": "express",
      "name": "Express",
      "amount_cents": 15,
      "working_days": 0
    },
    {
      "speed": "flex",
      "name": "Flex",
      "amount_cents": -5,
      "working_days": 5
    }
  ],
  "express": {
    "minimum_cents": 1500,
    "max_letters_per_day": 1000,
    "before_hour": 12
  },
  "heavy_letter": {
    "from_grams": 50,
    "amount_cents": 60
  },
  "eu": {
    "surcharge_cents": 150,
    "countries": [
      "AT",
      "DE",
      "FR",
      "NL",
      "..."
    ],
    "delivery": "5 tot 10 werkdagen na verzending",
    "express": false
  },
  "daily_letter_max": 1000,
  "max_sheets": 8,
  "currency": "EUR",
  "vat_included": false
}
```

## Je account en bedrijfscontrole: `GET /api/v1/account`

Geeft de gegevens van je eigen account. `company_verified` is `true` als je btw-nummer gecontroleerd is bij de Europese btw-controle (VIES) of door ons manueel. `verification_status` is `verified`, `none` (niet geverifieerd), `pending` (controle volgt later) of `invalid`. Bij een geverifieerd bedrijf staan ook `official_name` en `official_address` uit het officiële register. `GET /balance` bevat ook `company_verified`. De status kan je niet via de API wijzigen: dat gebeurt enkel na een echte controle in het portaal.

cURL:

```bash
curl -X GET https://envelopify.lovable.app/api/v1/account \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
```

JavaScript:

```js
// Node 18 of nieuwer, bewaar als voorbeeld.mjs
const KEY = "test_JOUW_SLEUTEL";

const res = await fetch("https://envelopify.lovable.app/api/v1/account", {
  method: "GET",
  headers: {
    Authorization: "Bearer " + KEY,
  },
});
console.log(res.status, await res.json());
```

Python:

```python
import requests  # pip install requests

KEY = "test_JOUW_SLEUTEL"

res = requests.get(
    "https://envelopify.lovable.app/api/v1/account",
    headers={"Authorization": "Bearer " + KEY},
)
print(res.status_code, res.json())
```

PHP:

```php
<?php
$key = "test_JOUW_SLEUTEL";

$ch = curl_init("https://envelopify.lovable.app/api/v1/account");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . $key,
  ],
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $body, "\n";
```

Antwoord `200`:

```json
{
  "customer_id": "4f6c2a1e-...",
  "company": "Jouw Bedrijf",
  "email": "post@jouwbedrijf.be",
  "company_verified": true,
  "verification_status": "verified",
  "vat_number": "BE0123456749",
  "official_name": "Jouw Bedrijf BV",
  "official_address": "Kerkstraat 1\n2000 Antwerpen",
  "checked_at": "2026-11-12T10:00:00Z",
  "free_letters_remaining": 3
}
```

## Statusmeldingen opvragen: `GET /api/v1/webhooks`

Toont je geregistreerde links en notification_groups. null betekent alle bestaande gebeurtenissen; een lijst beperkt meldingen tot gekozen groepen.

cURL:

```bash
curl -X GET https://envelopify.lovable.app/api/v1/webhooks \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
```

JavaScript:

```js
// Node 18 of nieuwer, bewaar als voorbeeld.mjs
const KEY = "test_JOUW_SLEUTEL";

const res = await fetch("https://envelopify.lovable.app/api/v1/webhooks", {
  method: "GET",
  headers: {
    Authorization: "Bearer " + KEY,
  },
});
console.log(res.status, await res.json());
```

Python:

```python
import requests  # pip install requests

KEY = "test_JOUW_SLEUTEL"

res = requests.get(
    "https://envelopify.lovable.app/api/v1/webhooks",
    headers={"Authorization": "Bearer " + KEY},
)
print(res.status_code, res.json())
```

PHP:

```php
<?php
$key = "test_JOUW_SLEUTEL";

$ch = curl_init("https://envelopify.lovable.app/api/v1/webhooks");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . $key,
  ],
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $body, "\n";
```

Antwoord `200`:

```json
{
  "data": [
    {
      "id": "a3c2...",
      "url": "https://jouwsite.be/hooks/envelopify",
      "active": true,
      "created_at": "2026-11-12T10:00:00Z"
    }
  ]
}
```

## Statusmelding registreren: `POST /api/v1/webhooks`

Registreert een https-link. Het antwoord bevat eenmalig een `secret` waarmee je de handtekening controleert.

Inhoud (JSON):

| Veld | Type | Verplicht | Uitleg |
|---|---|---|---|
| `url` | string | ja | Https-link die onze POST ontvangt. |
| `notification_groups` | array | nee | Optioneel: problems (review/rejected), sent (posted/registered_handed_in), undeliverable (returned), delivered (binnenkort, nog geen gebeurtenissen). Weglaten bewaart alle bestaande gebeurtenissen, [] ontvangt niets. Instelbaar per adres in het portaal. |

cURL:

```bash
curl -X POST https://envelopify.lovable.app/api/v1/webhooks \
  -H "Authorization: Bearer test_JOUW_SLEUTEL" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://jouwsite.be/hooks/envelopify"
}'
```

JavaScript:

```js
// Node 18 of nieuwer, bewaar als voorbeeld.mjs
const KEY = "test_JOUW_SLEUTEL";

const res = await fetch("https://envelopify.lovable.app/api/v1/webhooks", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "url": "https://jouwsite.be/hooks/envelopify"
  }),
});
console.log(res.status, await res.json());
```

Python:

```python
import requests  # pip install requests

KEY = "test_JOUW_SLEUTEL"

res = requests.post(
    "https://envelopify.lovable.app/api/v1/webhooks",
    headers={"Authorization": "Bearer " + KEY},
    json={
      "url": "https://jouwsite.be/hooks/envelopify"
    },
)
print(res.status_code, res.json())
```

PHP:

```php
<?php
$key = "test_JOUW_SLEUTEL";

$ch = curl_init("https://envelopify.lovable.app/api/v1/webhooks");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . $key,
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => '{
    "url": "https://jouwsite.be/hooks/envelopify"
  }',
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $body, "\n";
```

Antwoord `201`:

```json
{
  "id": "a3c2...",
  "url": "https://jouwsite.be/hooks/envelopify",
  "active": true,
  "created_at": "2026-11-12T10:00:00Z",
  "secret": "whsec_..."
}
```

## Statusmelding verwijderen: `DELETE /api/v1/webhooks/{id}`

Verwijdert een link.

cURL:

```bash
curl -X DELETE https://envelopify.lovable.app/api/v1/webhooks/a3c2... \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
```

JavaScript:

```js
// Node 18 of nieuwer, bewaar als voorbeeld.mjs
const KEY = "test_JOUW_SLEUTEL";

const res = await fetch("https://envelopify.lovable.app/api/v1/webhooks/a3c2...", {
  method: "DELETE",
  headers: {
    Authorization: "Bearer " + KEY,
  },
});
console.log(res.status, await res.json());
```

Python:

```python
import requests  # pip install requests

KEY = "test_JOUW_SLEUTEL"

res = requests.delete(
    "https://envelopify.lovable.app/api/v1/webhooks/a3c2...",
    headers={"Authorization": "Bearer " + KEY},
)
print(res.status_code, res.json())
```

PHP:

```php
<?php
$key = "test_JOUW_SLEUTEL";

$ch = curl_init("https://envelopify.lovable.app/api/v1/webhooks/a3c2...");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . $key,
  ],
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $body, "\n";
```

Antwoord `200`:

```json
{
  "id": "a3c2...",
  "deleted": true
}
```

## Statusmeldingen (webhooks)

Bij elke gebeurtenis sturen we een POST naar al je actieve links met `{ event, letter_id, status, occurred_at, letter }`. Elke melding draagt de header `x-envelopify-signature`: de HMAC-SHA256 (hex) van de ruwe inhoud met je geheim. Controleer die voor je de melding vertrouwt en antwoord met een 2xx-status. Lukt een melding niet (geen 2xx of geen antwoord binnen 8 seconden), dan proberen we ze tot 3 keer opnieuw (bij je volgende gebeurtenis en bij elke productieronde), dus 4 pogingen in totaal. Testbrieven blijven op `received` en geven geen meldingen.

| Gebeurtenis | Wanneer |
|---|---|
| `letter.scheduled` | Ingepland voor een postdag, tegoed afgeboekt |
| `letter.review` | Adres te controleren: wij kijken het na voor we printen |
| `letter.checked` | Adres goedgekeurd na controle, ingepland |
| `letter.rejected` | Afgekeurd na controle of geannuleerd vóór productie, teruggezet op je Envelopify-tegoed, niet uitbetaald |
| `letter.printed` | Geprint en gefrankeerd |
| `letter.posted` | Afgegeven bij bpost |
| `letter.returned` | Teruggekomen als onbestelbaar |
| `letter.registered_handed_in` | Aangetekend afgegeven, tracking en bewijs beschikbaar |
| `letter.scanned` | QR-code gescand |

```json
{
  "event": "letter.posted",
  "letter_id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
  "status": "posted",
  "occurred_at": "2026-11-13T16:02:12Z",
  "letter": {
    "id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
    "tracking_code": "B1D1A7F7",
    "reference": "factuur-2026-104",
    "status": "posted",
    "posted_at": "2026-11-13T16:02:11Z"
  },
  "created_at": "2026-11-13T16:02:12Z"
}
```

Handtekening controleren:

```js
// Node 18+ with Express: verify a webhook signature
import crypto from "node:crypto";
import express from "express";

const SECRET = "whsec_JOUW_GEHEIM"; // returned once when you create the webhook
const app = express();

app.post("/hooks/envelopify", express.raw({ type: "application/json" }), (req, res) => {
  const expected = crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
  const given = req.get("x-envelopify-signature") ?? "";
  const ok = given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
  if (!ok) return res.status(401).end();
  const { event, letter } = JSON.parse(req.body.toString("utf8"));
  console.log(event, letter.id, letter.status);
  res.status(200).end();
});

app.listen(3000);
```

## Dubbels vermijden

Stuur een header `Idempotency-Key` mee, bv. je factuurnummer. Stuur je dezelfde sleutel opnieuw, binnen 24 uur, dan krijg je de bestaande brief terug (status 200) in plaats van een nieuwe, en wordt er niets extra aangerekend. Na 24 uur geldt de sleutel als nieuw.

## Statussen

| Status | Betekenis |
|---|---|
| `received` | Ontvangen. Testbrieven blijven in deze status. |
| `scheduled` | Ingepland voor een postdag, tegoed afgeboekt. |
| `review` | Te controleren: de postcode bestaat niet of past niet bij de gemeente. Wij keuren het adres goed of af (tegoed terug). Zie address_warning. |
| `printed` | Geprint en gefrankeerd. |
| `posted` | Afgegeven bij bpost. |
| `returned` | Teruggekomen als onbestelbaar. |
| `rejected` | Geweigerd of geannuleerd. De reden staat in reject_reason. |

## Foutcodes

Fouten hebben altijd de vorm `{ "error": { "code": "...", "message": "..." } }`. De `code` is taalonafhankelijk en verandert nooit: gebruik die in je logica. De `message` is Nederlands en mag je zo tonen.

| Code | HTTP | Betekenis | Voorbeeldmelding |
|---|---|---|---|
| `sender_not_verified` | 422 | Echte verzending, ook gratis proefpost, vereist een bevestigd e-mailadres en nagekeken bedrijf. Testmodus blijft beschikbaar. | Bevestig je e-mailadres en laat je bedrijf controleren vóór echte verzending. |
| `weight_class_unavailable` | 422 | Gewichtsklasse niet geactiveerd of boven 350 gram. | Voor deze gewichtsklasse is nog geen prijs bevestigd |
| `service_unavailable` | 422 | De gekozen dienst of ontvangen foldervoorraad is nog niet beschikbaar. | Deze dienst is nog niet beschikbaar. |
| `invalid_post_date` | 422 | De gekozen postdag is geen geldige werkdag binnen 90 dagen. | Kies een geldige werkdag vanaf de eerst mogelijke postdag, maximaal 90 dagen vooruit. |
| `address_incomplete` | 422 | Er ontbreekt een adresveld of de postcode klopt niet. | Een Belgische postcode bestaat uit 4 cijfers |
| `insufficient_balance` | 422 | Je tegoed volstaat niet. | Onvoldoende tegoed |
| `pdf_invalid` | 422 | De pdf kan niet geopend worden, is geen pdf of is groter dan 20 MB. | We kunnen deze pdf niet openen. Is hij beveiligd of beschadigd? |
| `too_many_sheets` | 422 | Meer dan 8 vellen, adresblad inbegrepen. | Te veel vellen: 9 (adresblad inbegrepen), een standaardbrief gaat tot 8 vellen. Grotere brieven kunnen op aanvraag: stuur ons een WhatsApp op 0473 39 95 89. |
| `express_limit` | 422 | Meer dan 1.000 expressbrieven op één dag. | Maximum 1.000 expressbrieven per dag bereikt. Kies Standaard of vraag het via WhatsApp 0473 39 95 89. |
| `country_not_supported` | 422 | Land buiten België en de EU. | Land US wordt niet ondersteund: we versturen naar België en de andere EU-landen |
| `express_not_available` | 422 | Express kan niet naar andere EU-landen. | Express is niet beschikbaar voor brieven naar andere EU-landen. Kies Standaard of Flex. |
| `daily_limit` | 422 | Dagelijks maximum brieven bereikt (standaard 1.000, aanpasbaar op aanvraag). | Dagelijks maximum van 1.000 brieven bereikt. Vraag een hoger maximum via WhatsApp 0473 39 95 89. |
| `template_invalid` | 422 | Sjabloon niet gevonden of er ontbreken velden. | Ontbrekende velden voor het sjabloon: voornaam |
| `invalid_request` | 400 | De aanvraag is niet geldig, bv. een ontbrekende bron of foute QR-link. | Geef precies één bron op: pdf_base64, pdf_url, of template_id / template_code. |
| `unauthorized` | 401 | Sleutel ontbreekt, is ongeldig of ingetrokken. | Deze API-sleutel is ongeldig of ingetrokken. |
| `not_found` | 404 | Brief, webhook of eindpunt bestaat niet. Ook zonder sleutel. | Dit eindpunt bestaat niet. Bekijk de documentatie op /developers. |
| `cutoff_passed` | 409 | Annuleren kan niet meer, de brief is in productie. | Deze brief is al in productie en kan niet meer geannuleerd worden. |
| `not_cancellable` | 409 | Annuleren kan niet, bv. omdat de brief al geweigerd of geannuleerd is. | Deze brief kan niet meer geannuleerd worden. |
| `conflict` | 409 | Er bestaat al een sjabloon met deze code. | Er bestaat al een sjabloon met deze code. |
| `rate_limited` | 429 | Meer dan 60 aanvragen per minuut. Kijk naar de header Retry-After. | Je hebt meer dan 60 aanvragen per minuut gedaan. Probeer het binnen een minuut opnieuw. |
| `server_error` | 500 | Er liep iets mis aan onze kant. | Er liep iets mis aan onze kant. Probeer opnieuw. |

## Limieten

Maximaal 60 aanvragen per minuut per sleutel. Daarboven krijg je `429 rate_limited` en een header `Retry-After`. Pdf's mogen maximaal 20 MB zijn, een brief maximaal 350 g, middel en groot alleen na activering, maximaal 1.000 expressbrieven per dag en standaard maximaal 1.000 brieven per dag per account. De `error.code` is taalonafhankelijk en verandert niet; de `message` is Nederlands. CORS staat open, maar gebruik je sleutel nooit in browsercode.

## Prijzen

Eén prijs per brief, excl. btw, alles inbegrepen: 1 vel (enkel- of dubbelzijdig, zwart-wit), envelop, echte postzegels en posten. Volgens je echte brieven in de glijdende voorbije 30 dagen, de nieuwe brief meegeteld, zonder maandreset: €1,29 onder 500, €1,09 vanaf 500, €0,99 vanaf 5.000. Kleur: +€0,08 per vel. Dubbelzijdig zonder meerprijs. Extra vel: +€0,10 per vel bovenop het eerste (dubbelzijdig zijn het twee pagina's per vel). QR-code met scanmeting: +€0,05. Het adresblad is gratis en telt niet mee voor de prijs. Een standaardbrief gaat tot 8 vellen in totaal, adresblad inbegrepen; grotere brieven kunnen op aanvraag via WhatsApp. Snelheden: Standaard (binnen 3 werkdagen verstuurd), Express (+€0,15, zelfde werkdag als aangeleverd vóór 12:00, anders de volgende werkdag) en Flex (€-0,05, binnen 5 werkdagen). Verstuurd betekent: door ons afgegeven aan bpost. bpost bezorgt binnen 2 tot 3 werkdagen na afgifte. Minimum express via de API: zijn je expressbrieven van één dag om 12:00 samen minder dan €15,00, dan houden we het verschil af met de regel "Aanvulling minimum express" in je bewegingen. Je tegoed moet dat bedrag bij het aanmaken al dekken. Maximum 1.000 expressbrieven per dag. Elk antwoord met een prijs bevat `price_breakdown` met `unit_price_cents`, `tier`, `volume_30d`, `letters_to_next_tier`, `sheets`, `total_sheets`, `speed`, `options` (lijst met `code`, `label`, `amount_cents`: extra_sheets, qr_tracking, express, flex) en `total_cents`. Gratis proefbrieven: elk nieuw account krijgt 3 echte brieven gratis, ook via de API met je sleutel in livemodus. Voorwaarden: snelheid `standard`, maximaal 3 vellen inhoud, bevestigd e-mailadres en maximaal 3 proefbrieven per ontvangstadres. Een brief die niet voldoet, wordt gewoon van je tegoed betaald. Een proefbrief heeft `free_trial: true` en `price_cents: 0`; annuleren voor productie geeft hem terug. `GET /balance` toont `free_letters_remaining`. Zware brief: boven 50 g komt er +€0,60 per brief bij (optie `heavy_letter`). Andere EU-landen: +€1,50 per brief (optie `eu`), levertermijn 5 tot 10 werkdagen na verzending, geen express. Adrescontrole: bij een Belgisch adres kijken we of de postcode bestaat en bij de gemeente past. Twijfel je antwoord niet weg: de brief krijgt status `review` met `address_warning` en wij keuren hem goed of af (tegoed terug).
