Broadcasts
10 endpoints in this family.
List broadcasts
GET/api/v1/broadcasts
The 50 latest broadcasts of the client you act on, and the templates approved there that you can write a new one with. With scope=account (a key that sees every client), the 50 latest broadcasts of all your clients, each with its client.
Query parameters
scopestringaccountfor every client of your account.account
Response
The latest broadcasts, and the approved templates.
data.itemsobject[]The broadcasts, newest first.
data.templatesobject[]The templates approved at this client (not with
scope=account).
curl https://nodium.io/api/v1/broadcasts \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "items": [ {} ], "templates": [ {} ] }}Send a broadcast
POST/api/v1/broadcasts
Sends an approved WhatsApp template to a group: a list of contacts, or every contact who opted in. Everything is checked first; recipients then leave in batches, and counts.sent grows as they do — follow it with GET /broadcasts/{id}. Consent is honoured: a contact without opt-in, opted out or blocked receives nothing, and is counted. Try the target first with POST /broadcasts/preview. Recipients are taken in turn across the broadcasts being sent, so a large one does not hold the others back. The number's messaging tier is honoured: a broadcast that reaches more recipients than the tier allows per 24 hours is refused, and a smaller one pauses by itself when the day's allowance is used, then continues. If sends are refused over and over for an account reason (payment, quality, rate limit, token, plan allowance), the broadcast is paused: status is paused with its reason, and POST /broadcasts/{id}/resume continues it.
Later, or step by step. With scheduledAt the broadcast waits (status: scheduled) and leaves at that time. With draft: true nothing leaves (status: draft): add recipients with POST /broadcasts/{id}/recipients, then start it with POST /broadcasts/{id}/send. The target is resolved when recipients are written: a contact who opts in afterwards is not added.
Body
namestringrequiredA name for you, up to 120 characters.
whatsappTemplateIdstringrequiredAn approved template id.
contactIdsstring[]The contacts to reach, up to 10,000. Or use
allOptIn.allOptInbooleantrueto reach every contact who opted in.channelIdstringThe number to send from. Optional when the client has a single active number.
variablesobjectThe values of the placeholders, the same for everyone:
{ "1": "value" }.scheduledAtstringWhen to send, ISO 8601, at least a minute ahead. Leave it out to send now.
draftbooleantruewrites the broadcast without sending it. The target is optional then.
Response
The broadcast id and its starting counts.
data.idstringThe broadcast id.
data.statusstringsending,scheduledordraft;finishedwhen no one can receive it.sendingscheduleddraftfinisheddata.countsobjectmatched,recipients,withoutConsent,withoutNumber,sent,failed.
Refusals
- 422
scheduled_at_invalid`scheduledAt` is not a valid ISO 8601 date, or is less than a minute ahead. - 409
template_not_approvedThe template is not approved by Meta at this client yet. - 422
broadcast_exceeds_messaging_limitThe broadcast reaches more recipients than the number's messaging tier allows per 24 hours; `data.limit`, `data.recipients` and `data.tier` say how many. Send it in smaller broadcasts, or wait for a higher tier.
curl -X POST https://nodium.io/api/v1/broadcasts \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "…", "whatsappTemplateId": "…", "scheduledAt": "2026-11-02T09:00:00Z" }'{ "data": { "id": "…", "status": "sending", "counts": {} }}Get a broadcast
GET/api/v1/broadcasts/{id}
One broadcast and its counts. draft waits for POST /broadcasts/{id}/send; scheduled leaves at sendAt. Recipients leave in batches, so poll this route to follow counts.sent and counts.failed until status is finished (or cancelled). A recipient counts as sent once WhatsApp has accepted its message, and as failed once the message is refused for good: a message waiting for a new attempt is in neither count yet. paused means sends were refused over and over for an account reason: pauseReason says why (pauseInfo explains a Meta refusal), and POST /broadcasts/{id}/resume continues it.
Path parameters
idstringThe broadcast id.
Response
The broadcast and its counts.
data.broadcastobjectThe broadcast.
Show fields
idstringThe broadcast id.
namestringIts name.
statusstringWhere it stands.
draftscheduledsendingpausedfinishedcancelledtemplateIdstringThe template sent.
templateNamestringIts name.
channelIdstringThe number it leaves from.
countsobjectmatched,recipients,withoutConsent,withoutNumber,sent,failed, andcancelledafter a cancellation.pausedAtstringWhen the broadcast was paused, or
nullwhen it is not paused.pauseReasonstringThe refusal that paused it, or
null.pauseInfoobjectWhen the reason carries a Meta code:
code,title,actionandactorexplaining it; otherwisenull.sendAtstringWhen it starts or started;
nullfor a draft.sentAtstringWhen the last recipient was handled, or
nullwhile sending.createdAtstringWhen it was written.
requestobjectThe body of
POST /broadcaststhat would send it again: same template, number, values and target. A target listing more than 200 contacts is left out (contactIdsabsent).
curl https://nodium.io/api/v1/broadcasts/<id> \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "broadcast": { "id": "…", "name": "…", "status": "draft", "templateId": "…", "templateName": "…", "channelId": "…", "counts": {}, "pausedAt": "…", "pauseReason": "…", "pauseInfo": {}, "sendAt": "…", "sentAt": "…", "createdAt": "…", "request": {} } }}Cancel a broadcast
POST/api/v1/broadcasts/{id}/cancel
Stops a broadcast that is still sending: recipients still waiting receive nothing. Messages already handed to WhatsApp cannot be recalled — a few recipients of the batch in flight may still receive it. The withdrawn recipients are counted in counts.cancelled.
Path parameters
idstringThe broadcast id.
Response
How many recipients were withdrawn, and the broadcast.
data.withdrawnintegerHow many recipients will not receive it.
data.broadcastobjectThe broadcast, as
GET /broadcasts/{id}returns it.
Refusals
- 409
broadcast_finishedThe broadcast has already finished.
curl -X POST https://nodium.io/api/v1/broadcasts/<id>/cancel \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "withdrawn": 0, "broadcast": {} }}Clear the recipients of a broadcast
DEL/api/v1/broadcasts/{id}/recipients
Removes every recipient of a broadcast that has not started (draft or scheduled), and resets its counts. A scheduled broadcast goes back to draft: add recipients, then start it again with POST /broadcasts/{id}/send.
Path parameters
idstringThe broadcast id.
Response
How many recipients were removed.
data.removedintegerThe recipients removed.
Refusals
- 409
broadcast_startedThe broadcast has already started. - 409
broadcast_finishedThe broadcast has finished or was cancelled.
curl -X DELETE https://nodium.io/api/v1/broadcasts/<id>/recipients \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "removed": 0 }}List the recipients of a broadcast
GET/api/v1/broadcasts/{id}/recipients
The recipients of a broadcast, with where each one stands: pending (waiting), sending, sent, failed (with error and errorCode) or cancelled. Paginated with limit and cursor.
Path parameters
idstringThe broadcast id.
Query parameters
limitintegerHow many to return, 50 by default, 200 at most.
cursorstringThe
nextCursorof the previous page.
Response
A page of recipients.
data.itemsobject[]The recipients.
data.nextCursorstringPass it as
cursorfor the next page;nullat the end.data.hasMorebooleanWhether another page follows.
curl https://nodium.io/api/v1/broadcasts/<id>/recipients \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "items": [ {} ], "nextCursor": "…", "hasMore": true }}Add recipients to a broadcast
POST/api/v1/broadcasts/{id}/recipients
Adds contacts to a broadcast that has not started (draft or scheduled). A contact without opt-in, opted out, blocked or without a number is left out and counted. A contact already in the broadcast is not added twice. Call it as often as needed.
Path parameters
idstringThe broadcast id.
Body
contactIdsstring[]The contacts to add, up to 10,000 per call. Or use
allOptIn.allOptInbooleantrueto add every contact who opted in.
Response
The counts of the broadcast.
data.countsobjectmatched,recipients,withoutConsent,withoutNumber— summed over every addition.
Refusals
- 409
broadcast_startedThe broadcast has already started. - 409
broadcast_finishedThe broadcast has finished or was cancelled.
curl -X POST https://nodium.io/api/v1/broadcasts/<id>/recipients \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "contactIds": [ "…" ], "allOptIn": true }'{ "data": { "counts": {} }}Resume a broadcast
POST/api/v1/broadcasts/{id}/resume
Continues a paused broadcast: recipients still waiting leave in the next minute. A broadcast is paused when several sends in a row were refused for an account reason (missing payment method, restricted account, rate limit, rejected token, plan allowance) — fix the cause first, pauseReason on GET /broadcasts/{id} says what it is. Recipients refused before the pause stay failed and are not sent again. If the cause is still there, the broadcast pauses again after a few more refusals.
Path parameters
idstringThe broadcast id.
Response
The broadcast.
data.broadcastobjectThe broadcast, as
GET /broadcasts/{id}returns it.
Refusals
- 409
broadcast_not_pausedThe broadcast is not paused: it is sending, finished or cancelled.
curl -X POST https://nodium.io/api/v1/broadcasts/<id>/resume \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "broadcast": {} }}Send or schedule a broadcast
POST/api/v1/broadcasts/{id}/send
Starts a draft now, or at scheduledAt. On a scheduled broadcast that has not started, it moves the start time (or starts it now without scheduledAt). The number's messaging tier is checked here, on the recipients written.
Path parameters
idstringThe broadcast id.
Body
scheduledAtstringWhen to send, ISO 8601, at least a minute ahead. Leave it out to send now.
Response
The broadcast, started or scheduled.
data.broadcastobjectThe broadcast, as
GET /broadcasts/{id}returns it:statusissendingorscheduled.
Refusals
- 409
broadcast_startedThe broadcast has already started. - 409
broadcast_finishedThe broadcast has finished or was cancelled. - 422
broadcast_emptyThe broadcast has no recipient. - 422
broadcast_exceeds_messaging_limitMore recipients than the number's messaging tier allows per 24 hours. - 422
scheduled_at_invalid`scheduledAt` is not a valid ISO 8601 date, or is less than a minute ahead.
curl -X POST https://nodium.io/api/v1/broadcasts/<id>/send \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "scheduledAt": "2026-11-02T09:00:00Z" }'{ "data": { "broadcast": {} }}Preview who a broadcast would reach
POST/api/v1/broadcasts/preview
Counts who would receive a broadcast with this target, without sending anything: contacts matched, recipients kept, and those set aside for lack of consent or of a phone number.
Body
contactIdsstring[]The contacts to reach. Or use
allOptIn.allOptInbooleantrueto reach every contact who opted in.channelIdstringThe number you would send from. When given, the answer also tells its messaging tier and daily limit.
Response
Who would receive it, and who would be set aside.
data.matchedintegerContacts matched by the target.
data.recipientsintegerThose who would receive it.
data.withoutConsentintegerSet aside: no opt-in, opted out, or blocked.
data.withoutNumberintegerSet aside: no phone number.
data.tierstringWith
channelId: the number's messaging tier (for exampleTIER_1K), ornullwhen unknown.data.limitintegerWith
channelId: how many contacts the number may reach per 24 hours, ornullwhen there is no limit.
curl -X POST https://nodium.io/api/v1/broadcasts/preview \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "contactIds": [ "…" ], "allOptIn": true }'{ "data": { "matched": 0, "recipients": 0, "withoutConsent": 0, "withoutNumber": 0, "tier": "…", "limit": 0 }}