Skip to content

REST API · v1

API documentation

Let your own software send brochures. Your system places an order; we print and fold the brochure and send it by post. Everything runs as JSON over HTTPS.

Quick start

  1. Log in and go to “Integraties” → “API-sleutels” (Integrations → API keys). You need the “Integraties” (Integrations) team permission for this.
  2. Create a key with a name, the scopes your integration needs and a validity period. Copy the key straight away: you will only see it once.
  3. Put the key in an environment variable and retrieve your brochures:
curl https://slimfolderen.nl/api/v1/brochures?status=active \
  -H "Authorization: Bearer $SLIMFOLDEREN_API_KEY" \
  -H "Accept: application/json"

Base URL: https://slimfolderen.nl/api/v1. Send Accept: application/json with every request, so that errors are returned as JSON as well.

Authentication and scopes

Send the key as a bearer token: Authorization: Bearer 12|Xh3k…. A key acts on behalf of the colleague who created it. Orders are placed in their name and follow their permissions. A key created by an administrator sees all of the company’s orders; keys of other roles see only their own orders.

A key can only call the endpoints it has a scope for:

ScopeGives access to
orders:create“Orders plaatsen” (place orders). Place orders (individually or in a batch) and calculate the cost in advance. Uses credit.
orders:read“Orders inzien” (view orders). Retrieve orders and their shipping status.
brochures:read“Brochures inzien” (view brochures). Retrieve brochures, for example to pick the right brochure_id.
campaigns:read“Campagnes inzien” (view campaigns). Retrieve webhook campaigns. The webhook URL and token stay hidden.

Your credit balance, team, settings, other API keys and payments can never be reached with an API key, not even with all scopes. Choose a validity period for each key, from 30 days to one year, or let it never expire. Revoke a key as soon as you stop using it; the portal shows when each key was last used.

Endpoints

  • GET/brochures

    Brochures, newest first. Filters: status, format, search.

    Scope: brochures:read

  • GET/brochures/{id}

    A single brochure.

    Scope: brochures:read

  • POST/orders/estimate

    Calculate the cost without sending anything.

    Scope: orders:create

  • POST/orders

    Place one order (or a bulk order to one address).

    Scope: orders:create

  • POST/orders/batch

    The same brochure to up to 100 recipients.

    Scope: orders:create

  • GET/orders

    Orders, newest first. Filters: status, source, brochure_id, external_reference, date_from, date_to.

    Scope: orders:read

  • GET/orders/{id}

    A single order with its status history.

    Scope: orders:read

  • GET/campaigns

    Webhook campaigns. Filter: active.

    Scope: campaigns:read

  • GET/campaigns/{id}

    A single campaign.

    Scope: campaigns:read

Lists are paginated: the items are in data, with current_page, last_page and total alongside. Use page and per_page (up to 100).

Placing and tracking orders

An order costs credits from your wallet: printing costs per page plus shipping costs per address. Calculate the cost first if you want to show or check it:

Calculate the cost
curl -X POST https://slimfolderen.nl/api/v1/orders/estimate \
  -H "Authorization: Bearer $SLIMFOLDEREN_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"brochure_id": "9d3c4a52-7f1e-4b8a-9c61-2f0e8b7a1d34", "recipient_country": "NL"}'
Response
{
  "print_credits": 10,
  "shipping_credits": 12,
  "total_credits": 22,
  "paper": { "key": "normal", "label": "Standaard", "cover_gsm": 120, "inner_gsm": 100, "credits_per_page": 5 },
  "estimated_euro": 4.4
}

Then place the order. Only brochures with status active can be sent. The country defaults to NL.

Place an order
curl -X POST https://slimfolderen.nl/api/v1/orders \
  -H "Authorization: Bearer $SLIMFOLDEREN_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2b8e-1d4a-4f0e-9b3a-2c7d5e8f9a10" \
  -d '{
    "brochure_id": "9d3c4a52-7f1e-4b8a-9c61-2f0e8b7a1d34",
    "recipient_name": "Jan Jansen",
    "recipient_company": "Jansen Installatietechniek B.V.",
    "recipient_address": "Keizersgracht 1",
    "recipient_postal_code": "1015 CJ",
    "recipient_city": "Amsterdam",
    "recipient_country": "NL",
    "external_reference": "crm-4711"
  }'
Response
HTTP/1.1 201 Created

{
  "message": "Brochure succesvol ingepland voor verzending!",
  "order": {
    "id": "0b8f3f9e-2c4d-4e1a-8f57-6a9d2e1c7b40",
    "status": "queued",
    "quantity": 1,
    "total_credits": 22,
    "recipient_name": "Jan Jansen",
    "external_reference": "crm-4711",
    "brochure": "Productbrochure 2026",
    "created_at": "2026-10-27T09:15:02.000000Z"
  },
  "credit_balance": 978
}

With external_reference (up to 100 characters) you give the order your own reference, such as a CRM or order number. It comes back in every response and you can use it to look up orders. The Idempotency-Key makes it safe to resend the request after a time-out; see Retrying safely.

For multiple recipients, use POST /orders/batch with a list of recipients, each with its own external_reference if you like. Recipients at the same address go together in one envelope. The response is 201 if everything succeeded and 207 if some of it failed; those recipients are listed in failed, with their index and external_reference.

Retrieve the status
curl https://slimfolderen.nl/api/v1/orders/0b8f3f9e-2c4d-4e1a-8f57-6a9d2e1c7b40 \
  -H "Authorization: Bearer $SLIMFOLDEREN_API_KEY" \
  -H "Accept: application/json"
StatusMeaning
queuedScheduled, waiting for the printer.
printingBeing printed.
printedPrinted and folded.
ready_to_shipReady for shipping.
shippedShipped.
deliveredDelivered.
failedFailed. Please contact us.
cancelledCancelled.
The same in JavaScript (Node 18+)
import { randomUUID } from 'node:crypto';

// One idempotency key per order. Store it and reuse the same key on every retry.
const idempotencyKey = randomUUID();

const response = await fetch('https://slimfolderen.nl/api/v1/orders', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SLIMFOLDEREN_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
    'Idempotency-Key': idempotencyKey,
  },
  body: JSON.stringify({
    brochure_id: '9d3c4a52-7f1e-4b8a-9c61-2f0e8b7a1d34',
    recipient_name: 'Jan Jansen',
    recipient_address: 'Keizersgracht 1',
    recipient_postal_code: '1015 CJ',
    recipient_city: 'Amsterdam',
    external_reference: 'crm-4711',
  }),
});

const data = await response.json();
if (!response.ok) {
  // 422: data.errors per field, e.g. data.errors.credits when there is not enough credit
  throw new Error(data.message);
}
console.log(data.order.id, data.order.status);

Retrying safely

A placed order is printed and costs credit. If you get no response, for example because of a time-out, you cannot tell whether the order has already been created. So send an Idempotency-Key with POST /orders and POST /orders/batch: a unique value of your own for each order or batch, such as a UUID. You can then safely retry with the same idempotency key and the same body.

  • The same idempotency key with the same body returns the first response for 24 hours: the same status and body, with the header Idempotent-Replayed: true. No second order is created and nothing extra is charged. So for a batch that returned a 207, the recipients that succeeded are not sent again.
  • Idempotency keys apply per API key. The same idempotency key with a different body or on a different endpoint returns a 422.
  • If the first request is still running, you get a 409 with Retry-After. Wait that number of seconds and try again.
  • A 4xx is not stored, because nothing was created. After topping up your credit or correcting a field, for example, you reuse the same idempotency key.
  • A 5xx is stored, because a batch may already have been partly sent. A 409 without Retry-After means that the first request was aborted. In both cases, use external_reference to check what has been created, and send the rest with a new idempotency key.
Retried with the same idempotency key
HTTP/1.1 201 Created
Idempotent-Replayed: true

{ ...the same response as the first time... }
Look up an order by your own reference
curl "https://slimfolderen.nl/api/v1/orders?external_reference=crm-4711" \
  -H "Authorization: Bearer $SLIMFOLDEREN_API_KEY" \
  -H "Accept: application/json"

Errors

Errors are returned as JSON with a message. With a 422, errors shows what is wrong with each field. The text of message and of the field errors is in Dutch; rely on the status code and field names in your code.

  • 401No key, or an invalid, expired or revoked key.
  • 403The key lacks the scope (see required_scopes), the endpoint is not available to API keys, or the owner of the key is not authorised to use it.
  • 404Does not exist or does not belong to your company.
  • 409A request with the same Idempotency-Key is still being processed or was aborted.
  • 422Invalid input, a brochure that cannot be sent, insufficient credit (errors.credits) or an Idempotency-Key that has already been used for a different request (errors.idempotency_key).
  • 429Rate limit reached. Wait the number of seconds given in the Retry-After header.
Missing scope
HTTP/1.1 403 Forbidden

{
  "message": "Deze API-sleutel mist de benodigde scope.",
  "required_scopes": ["orders:create"]
}

Rate limits

Each key can make 60 requests per minute. In addition, a single user can place up to 60 orders and 20 batches per minute. Every response includes X-RateLimit-Limit and X-RateLimit-Remaining. With a 429, Retry-After tells you after how many seconds you can try again.

OpenAPI specification

All endpoints, fields and responses are described in the OpenAPI 3.1 specification. Import it into Postman, Insomnia or a code generator.

Prefer not to write code? With webhooks you can connect your CRM, Zapier or Make without any programming.