Skip to content
Get an API key
Reference

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

  • scopestring

    account for 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"

Response

200 OK
{ "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

  • namestringrequired

    A name for you, up to 120 characters.

  • whatsappTemplateIdstringrequired

    An approved template id.

  • contactIdsstring[]

    The contacts to reach, up to 10,000. Or use allOptIn.

  • allOptInboolean

    true to reach every contact who opted in.

  • channelIdstring

    The number to send from. Optional when the client has a single active number.

  • variablesobject

    The values of the placeholders, the same for everyone: { "1": "value" }.

  • scheduledAtstring

    When to send, ISO 8601, at least a minute ahead. Leave it out to send now.

  • draftboolean

    true writes the broadcast without sending it. The target is optional then.

Response

The broadcast id and its starting counts.

  • data.idstring

    The broadcast id.

  • data.statusstring

    sending, scheduled or draft; finished when no one can receive it.

    sendingscheduleddraftfinished

  • data.countsobject

    matched, recipients, withoutConsent, withoutNumber, sent, failed.

Refusals

  • 422scheduled_at_invalid`scheduledAt` is not a valid ISO 8601 date, or is less than a minute ahead.
  • 409template_not_approvedThe template is not approved by Meta at this client yet.
  • 422broadcast_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" }'

Response

200 OK
{ "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

  • idstring

    The broadcast id.

Response

The broadcast and its counts.

  • data.broadcastobject

    The broadcast.

    Show fields
    • idstring

      The broadcast id.

    • namestring

      Its name.

    • statusstring

      Where it stands.

      draftscheduledsendingpausedfinishedcancelled

    • templateIdstring

      The template sent.

    • templateNamestring

      Its name.

    • channelIdstring

      The number it leaves from.

    • countsobject

      matched, recipients, withoutConsent, withoutNumber, sent, failed, and cancelled after a cancellation.

    • pausedAtstring

      When the broadcast was paused, or null when it is not paused.

    • pauseReasonstring

      The refusal that paused it, or null.

    • pauseInfoobject

      When the reason carries a Meta code: code, title, action and actor explaining it; otherwise null.

    • sendAtstring

      When it starts or started; null for a draft.

    • sentAtstring

      When the last recipient was handled, or null while sending.

    • createdAtstring

      When it was written.

    • requestobject

      The body of POST /broadcasts that would send it again: same template, number, values and target. A target listing more than 200 contacts is left out (contactIds absent).

curl https://nodium.io/api/v1/broadcasts/<id> \ -H "Authorization: Bearer $NODIUM_KEY"

Response

200 OK
{ "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

  • idstring

    The broadcast id.

Response

How many recipients were withdrawn, and the broadcast.

  • data.withdrawninteger

    How many recipients will not receive it.

  • data.broadcastobject

    The broadcast, as GET /broadcasts/{id} returns it.

Refusals

  • 409broadcast_finishedThe broadcast has already finished.
curl -X POST https://nodium.io/api/v1/broadcasts/<id>/cancel \ -H "Authorization: Bearer $NODIUM_KEY"

Response

200 OK
{ "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

  • idstring

    The broadcast id.

Response

How many recipients were removed.

  • data.removedinteger

    The recipients removed.

Refusals

  • 409broadcast_startedThe broadcast has already started.
  • 409broadcast_finishedThe broadcast has finished or was cancelled.
curl -X DELETE https://nodium.io/api/v1/broadcasts/<id>/recipients \ -H "Authorization: Bearer $NODIUM_KEY"

Response

200 OK
{ "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

  • idstring

    The broadcast id.

Query parameters

  • limitinteger

    How many to return, 50 by default, 200 at most.

  • cursorstring

    The nextCursor of the previous page.

Response

A page of recipients.

  • data.itemsobject[]

    The recipients.

  • data.nextCursorstring

    Pass it as cursor for the next page; null at the end.

  • data.hasMoreboolean

    Whether another page follows.

curl https://nodium.io/api/v1/broadcasts/<id>/recipients \ -H "Authorization: Bearer $NODIUM_KEY"

Response

200 OK
{ "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

  • idstring

    The broadcast id.

Body

  • contactIdsstring[]

    The contacts to add, up to 10,000 per call. Or use allOptIn.

  • allOptInboolean

    true to add every contact who opted in.

Response

The counts of the broadcast.

  • data.countsobject

    matched, recipients, withoutConsent, withoutNumber — summed over every addition.

Refusals

  • 409broadcast_startedThe broadcast has already started.
  • 409broadcast_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 }'

Response

200 OK
{ "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

  • idstring

    The broadcast id.

Response

The broadcast.

  • data.broadcastobject

    The broadcast, as GET /broadcasts/{id} returns it.

Refusals

  • 409broadcast_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"

Response

200 OK
{ "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

  • idstring

    The broadcast id.

Body

  • scheduledAtstring

    When to send, ISO 8601, at least a minute ahead. Leave it out to send now.

Response

The broadcast, started or scheduled.

  • data.broadcastobject

    The broadcast, as GET /broadcasts/{id} returns it: status is sending or scheduled.

Refusals

  • 409broadcast_startedThe broadcast has already started.
  • 409broadcast_finishedThe broadcast has finished or was cancelled.
  • 422broadcast_emptyThe broadcast has no recipient.
  • 422broadcast_exceeds_messaging_limitMore recipients than the number's messaging tier allows per 24 hours.
  • 422scheduled_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" }'

Response

200 OK
{ "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.

  • allOptInboolean

    true to reach every contact who opted in.

  • channelIdstring

    The 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.matchedinteger

    Contacts matched by the target.

  • data.recipientsinteger

    Those who would receive it.

  • data.withoutConsentinteger

    Set aside: no opt-in, opted out, or blocked.

  • data.withoutNumberinteger

    Set aside: no phone number.

  • data.tierstring

    With channelId: the number's messaging tier (for example TIER_1K), or null when unknown.

  • data.limitinteger

    With channelId: how many contacts the number may reach per 24 hours, or null when 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 }'

Response

200 OK
{ "data": { "matched": 0, "recipients": 0, "withoutConsent": 0, "withoutNumber": 0, "tier": "…", "limit": 0 }}