Core concepts
Pagination, idempotency, test mode and versioning: the four rules that apply to every endpoint.
The four rules
These apply to every endpoint, so they are worth reading once rather than discovering one 4xx at a time.
List endpoints are cursor-paginated. Pass ?limit= to size a page (limits vary by endpoint; see each endpoint's reference) and page forward with ?cursor= using the previous response's next_cursor. When has_more is false you have reached the end. Treat cursors as opaque tokens: pass back exactly what a response returns; do not synthesise them.
Send an Idempotency-Key header (a UUID) on the POST routes that honour it: messages, campaigns, contacts, templates, flows, events/send, webhooks, integrations, groups, studio flows, flow publish, delivery replay, replay failed, table rows and template submit. On those, a replay within 24 hours returns the original response. Every other POST IGNORES the header, so retrying one after a timeout can create a second record; check the endpoint before you rely on it. Reusing a key with a different body returns 409 idempotency_conflict; a concurrent duplicate returns 409 idempotency_in_progress with retry-after.
The API is versioned in the path (/api/v1). Additive changes (new endpoints, new optional fields, new response fields) ship without a version bump, so parse responses leniently and ignore fields you do not recognise. Existing endpoints and fields are not removed or renamed. On the rare occasion a change could fail an existing integration, it is in the changelog marked breaking, in the same deploy, with migration guidance.
Blueticked has two independent test tools. Test API keys (blu_test_…) simulate every send end to end: a message row, flagged test_mode, is created and simulated. On a test key a message moves queued → sent → delivered, about one step every 5 seconds, and on to read for WhatsApp; SMS and email stop at delivered. The webhooks message.sent, message.delivered and message.failed fire as it moves; the simulated read fires none. No provider is called, no credits are spent, and nothing touches your WhatsApp quality rating. Each webhook carries "test": true, and +27800000003 also fires a simulated message.received reply; use these to build and run a full integration. test_mode: true (a request field) is a dry run that validates your request and returns status: "validated" without creating or sending anything, and works with any key including a live one. You can combine them, but a test key already sandboxes everything. To rehearse a failure, send the X-Simulate-Error header with a test key and name the code: the response is the real status, code and next_steps (the Errors page lists the codes you can ask for). A test key sandboxes sends, not data: a test key's writes are real, so a contact it creates lives in your workspace beside your customers. A live key refuses to send to the sandbox numbers (+27800000000 to +27800000099) from the send endpoint, but a campaign or a flow does not check the number, so delete test contacts before you go live.
| +27800000001 | Always delivers |
| +27800000002 | Fails at send with recipient_unreachable |
| +27800000003 | Delivers, then fires a simulated message.received reply |