Skip to content
Ledenboek Developers

Concepts

What applies to every endpoint. Read this before you put an integration into production.

Errors

An error always has the same shape, application/problem+json as described in RFC 9457:

403 Forbidden
{
  "type": "https://developers.ledenboek.nl/errors/insufficient_scope",
  "title": "Insufficient permissions",
  "status": 403,
  "detail": "This key lacks the permission leden:schrijven. An administrator can create a key with that permission.",
  "instance": "/v2/members"
}
# -i shows the status line and headers; errors come as application/problem+json
curl -i https://api.ledenboek.nl/v2/members \
  -H "Authorization: Bearer $LEDENBOEK_API_KEY"

All error codes

Code HTTP Title
api_key_in_url 400 API key in URL
invalid_request 400 Invalid request
invalid_api_key 401 Invalid API key
missing_api_key 401 Missing API key
subscription_required 402 Subscription required
insufficient_scope 403 Insufficient permissions
module_disabled 404 Module disabled
not_found 404 Not found
already_in_group 409 Already in group
already_registered 409 Already registered
event_full 409 Not enough spots
event_requires_payment 409 Payment required
group_follows_subscription 409 Group follows subscriptions
idempotency_in_progress 409 Request still in progress
invoice_not_sendable 409 Invoice cannot be sent
membership_already_ended 409 Membership already ended
recipient_without_email 409 No email address
registration_closed 409 Registration closed
idempotency_key_reused 422 Idempotency-Key already used
validation_failed 422 Validation failed
rate_limited 429 Too many requests
internal_error 500 Internal error

Pagination

A list returns an object with data, has_more and next_cursor. You page with two parameters:

Records come in order of id. If records are added while you page, you skip nothing and see nothing twice, which page numbers cannot guarantee. On the last page has_more is false and next_cursor is null.

There is an example in the quickstart.

Syncing with updated_since

If you keep your own copy, do not fetch everything each time. With updated_since you only get records changed at or after that moment. The timestamp is ISO 8601, for example 2026-10-01T00:00:00Z; any other format returns invalid_request. You can combine it with pagination.

Record the moment your sync starts and use that as updated_since next time. That way you miss nothing that changed while you were fetching.

curl "https://api.ledenboek.nl/v2/members?updated_since=2026-10-01T00:00:00Z&limit=100" \
  -H "Authorization: Bearer $LEDENBOEK_API_KEY"

A record that has been deleted entirely does not come back with updated_since. Compare the full list with your copy now and then.

Idempotency

A POST accepts the Idempotency-Key header: a unique value of at most 255 characters, such as a UUID. If you send the same request again with the same key, nothing is created twice.

Keys are kept for 24 hours. Store the key with your own record before you send the request, so a retry uses the same one automatically.

Rate limits

Each key may make 600 requests per minute, a test key 120. The window is a fixed minute. Every response tells you how much room is left:

HeaderMeaning
RateLimit-LimitRequests per minute for this key.
RateLimit-RemainingWhat is left in this minute.
RateLimit-ResetSeconds until the next minute starts.
Retry-AfterOnly with 429: how many seconds to wait.

Above the limit you get rate_limited with status 429. Wait the number of seconds in Retry-After.

# The headers show how much room is left in this minute
curl -s -D - -o /dev/null https://api.ledenboek.nl/v2/organization \
  -H "Authorization: Bearer $LEDENBOEK_API_KEY" | grep -i ratelimit

Versioning

The version is in the path: /v2. This applies: