Bulk & idempotency
Bulk endpoints apply up to 100 operations in one request. Operations are applied independently: a failing item never rolls back the others — you get a per-item result instead.
Members: mixed batch
curl -X POST https://app.club-tool.nl/api/v1/members/bulk \
-H "Authorization: Bearer ct_live_..." \
-H "Idempotency-Key: import-2026-07-02-a" \
-H "Content-Type: application/json" \
-d '{"operations": [
{"op": "create", "data": {"name": "Sam de Vries", "group_ids": [3]}},
{"op": "update", "id": 91, "data": {"status": "archived"}},
{"op": "delete", "id": 12}
]}'
Per-item results
{
"results": [
{ "status": 201, "id": 137 },
{ "status": 200, "id": 91 },
{ "status": 404, "id": 12, "error": "Niet gevonden" }
],
"succeeded": 2,
"failed": 1
}
The HTTP status of the batch itself is 200 as long as the batch was
processed; inspect results[i].status per item (2xx = applied).
Idempotency keys
Bulk requests require an Idempotency-Key header
(any unique string, max 120 chars). That makes network retries safe:
- Same key + same body within 24 hours → the stored response is replayed and the
response carries
Idempotency-Replayed: true. Nothing runs twice. - Same key + a different body →
409 idempotency_key_reuse.
Pick keys per logical batch, e.g.
import-<date>-<chunk>. Reuse the exact same key when retrying a
failed connection; mint a new key for a new batch.
Attendance: batch upsert
curl -X POST https://app.club-tool.nl/api/v1/attendance/bulk \
-H "Authorization: Bearer ct_live_..." \
-H "Idempotency-Key: checkin-2026-07-02-19u" \
-H "Content-Type: application/json" \
-d '{"records": [
{"member_id": 41, "group_id": 3, "date": "2026-07-02", "present": true},
{"member_id": 42, "group_id": 3, "date": "2026-07-02", "present": false}
]}'
Setting "present": null deletes the record (same semantics as clearing
it in the app).
Ordering & consistency
- Operations run in array order, top to bottom.
- Each item is committed on its own — after the batch, succeeded items are durable even if later items failed.
- There is deliberately no all-or-nothing mode: model your import as retryable, per-item work and use the results array to reconcile.