Quickstart
From nothing to a delivered message in 6 steps, on a test key until you go live.
An API key keeps working long after you close the tab, so we ask for a second factor before minting one. It takes about a minute, and from then on every sign-in asks for a code from your authenticator app. Set up two-step verification. It opens over this page; close it to come back here.
The path
6 steps, each linking to where it happens.
Everything up to "Going live" runs on a test key, on a brand-new workspace, with nothing connected.
- 1Create a test keyIn Developers → API keys, create a blu_test_ key. Test keys sandbox every send end to end: the message row walks its real lifecycle, no provider is ever called, and nothing bills. A test key works on a brand-new workspace: you do not need to connect WhatsApp, a sender number or a mailbox first.Create a key
- 2Send your first messagePOST /api/v1/messages with a
bodyandto.create: true, which makes the recipient a contact in the same call: no template, no channel setup, no separate contact step, nothing to connect first. On a blu_test_ key the whole lifecycle is 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 and nothing bills. The send carries an Idempotency-Key header: send the same key again within 24 hours and you get the first answer back instead of a second message, which is what happens if you re-run the snippet unchanged. The same key with a changed body answers 409 idempotency_conflict, so use a new key for each new message.Send from the consoleRequest · answers 202 Acceptedcurl -X POST "https://app.blueticked.com/api/v1/messages" \ -H "Authorization: Bearer $BLUETICKED_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: change-me-per-request-0001" \ -d '{ "channel": "whatsapp", "to": { "phone": "+27800000001", "create": true, "first_name": "Sam" }, "body": "Hello from the Blueticked sandbox." }' - 3Test failure and inbound, not just the happy pathThe sandbox has deterministic magic numbers, so you can build your error handling before you ever go live: +27800000001 delivers (and so does any other number), +27800000002 fails at the send step with
recipient_unreachable, and +27800000003 delivers and then fires a simulated inbound reply, the same webhook shape a real customer reply produces. Each is a number like any other: send it withto.create: true, as step 2 does, because a send to a number you have never stored answers 404. A test key's writes are real workspace data: the contact you add is stored like any other, where a live campaign could reach it, so a live key refuses to send to these numbers from the send endpoint (400 validation_failed), but a campaign or a flow does not check. Delete the test contacts when you go live.Watch the events - 4Going live: WhatsApp templatesSandbox sends accept a free-form body on every channel. Live WhatsApp is different, and that is Meta's rule rather than ours: a business-initiated WhatsApp message needs a template Meta has approved, and a free-form body is only valid inside an open 24-hour customer-service window. SMS and email have no such restriction. Approve a template before you switch to a blu_live_ key, or your first live send will be the first thing that fails.Open templates
- 5Subscribe a webhookCreate a webhook in Developers → Webhooks, fire a signed test delivery, and verify the signature against the raw body.Create a webhook
- 6Go live with idempotencySwap in a blu_live_ key and send one real message, keeping the Idempotency-Key header you have sent since step 2, so a retry never double-sends. (test_mode: true stays available on any key as a dry-run that validates without sending.)Create a live key
Your first request
A read that works with just a key and no setup.
GET /api/v1/channels lists your senders: a new workspace sees the SMS entry only, and WhatsApp and email appear once you connect them (WhatsApp at /whatsapp/channel, email at /email/settings).
curl "https://app.blueticked.com/api/v1/channels" \
-H "Authorization: Bearer $BLUETICKED_API_KEY"{
"data": [
{
"id": "1891a644-c71d-4a79-b679-6db1db14e065",
"channel": "sms",
"display_name": "SMS",
"identifier": "Blueticked",
"status": "connected",
"created_at": "2026-10-12T08:00:00.000Z",
"sender_id": "Blueticked",
"uses_default_sender": false,
"provider": "smsportal"
}
],
"request_id": "req_091284c4482187a053a615628d3bf435"
}Working in TypeScript or JavaScript? npm install @blueticked/sdk makes these requests from typed code, the first send included (to.create); see the SDK page.
Every status it can return:
- connected · SMS
- Blueticked's own SMS provider is configured, as it is in production. SMS sends go out. This word reads only the provider: the dashboard's SMS sender page can still say "Not active" while one of the checks Blueticked support finishes before SMS campaigns can go is open ("SMS sending is temporarily unavailable", "Delivery tracking needs attention", "STOP replies are not ready", "Secure delivery tracking is not ready", "Do-Not-Contact screening is not ready"); its banner names which one, and support finishes it.
- unavailable · SMS
- The provider is not configured, as on a local or self-hosted stack; the dashboard's SMS pages say "SMS sending is not active" and point to support. A test key still sends; a live SMS send answers 503 channel_unavailable.
- connected · WhatsApp and email
- Ready to send. Email connects straight away: an email channel is created connected, so email only ever shows this word.
- pending_verification · WhatsApp and email
- WhatsApp only: added but not yet verified, such as a number waiting for the code Meta sends to its phone. Finish it in the dashboard; until then a live send answers 503 channel_unavailable.
- disconnected · WhatsApp and email
- WhatsApp only: disconnected in the dashboard. Reconnect it there; until then a live send answers 503 channel_unavailable.
- error · WhatsApp and email
- WhatsApp only: the connection failed, for example an expired access token. Re-run setup in the dashboard; until then a live send answers 503 channel_unavailable.
The send those steps build up to
It runs on a blu_test_ key, on a brand-new workspace.
No template, no connected channel, and no business id to pass (the status check's answer still names the business the message belongs to). to.create: true makes the recipient a contact in the same call if they are not one yet; without it, a send to a number you have never stored answers 404. This is step 2's call. Going live means a blu_live_ key and, on WhatsApp, an approved template.
curl -X POST "https://app.blueticked.com/api/v1/messages" \
-H "Authorization: Bearer $BLUETICKED_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: change-me-per-request-0001" \
-d '{
"channel": "whatsapp",
"to": {
"phone": "+27800000001",
"create": true,
"first_name": "Sam"
},
"body": "Hello from the Blueticked sandbox."
}'{
"message_id": "763c4dc3-d46b-4c10-8bd4-3f233a933ea0",
"conversation_message_id": "5592edf6-d7ae-4198-962e-3e8ebe83bb49",
"conversation_id": "fa3efd47-67b2-428c-b75f-17d1dd2e9790",
"status": "queued",
"contact_id": "ff506e53-e2f0-4d92-884a-dab77d628bad",
"contact_created": true,
"request_id": "req_b2e3d610e10f1b07468721ee98f0e1d2"
}On a test key opt-in is never checked; on a live key, WhatsApp marketing (a campaign, or a template in the marketing category) needs the contact's recorded opt-in, and this transactional send needs none. Opt-in is not step 4's 24-hour customer-service window: opt-in is the contact's consent to marketing, and the window is Meta's rule for free-form text, so on a live key this free-form body goes only inside an open window, and outside it you send an approved template.
Re-running it unchanged is safe: within 24 hours the send replays its first answer, because its Idempotency-Key is the same, so it creates no second contact and sends no second message; the same key with a changed body answers 409 idempotency_conflict.
What happened to my message
"queued" means accepted, not delivered.
Read the message back with the message_id the send returned: paste it into the request's second line, MESSAGE_ID=, which sets $MESSAGE_ID for the call below it. 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. To be told instead of asking, subscribe a webhook.
# Paste the message_id from your send's answer.
MESSAGE_ID="<message_id from the send>"
curl "https://app.blueticked.com/api/v1/messages/$MESSAGE_ID" \
-H "Authorization: Bearer $BLUETICKED_API_KEY"{
"id": "763c4dc3-d46b-4c10-8bd4-3f233a933ea0",
"business_id": "22222222-2222-4222-9222-222222222222",
"channel": "whatsapp",
"status": "queued",
"external_id": null,
"failure_reason": null,
"cost_zar_cents": 0,
"sent_at": null,
"delivered_at": null,
"read_at": null,
"failed_at": null,
"created_at": "2026-10-06T12:00:00.000Z",
"updated_at": "2026-10-06T12:00:00.000Z",
"request_id": "req_1a25b5e904ab6da082511caed4efb27b"
}Subscribe a webhook for message.sent and message.delivered; a live WhatsApp send adds message.read.