RaabtaHQ
Create and send a template broadcast

Create and send a template broadcast

POST/api/v1/broadcasts

scope broadcasts:sendIdempotentCosts 5 rate-limit units

Creates a WhatsApp template campaign, materialises its recipients, and starts sending — immediately, or at schedule_at if that is in the future. Always asynchronous: the response is 202 with the campaign, and GET /broadcasts/{id} reports progress. Per-recipient values come from variables (a placeholder → static text, a contact field, or a custom field). Counts toward the account’s monthly broadcast limit; a phone-number audience that creates contacts counts toward the contact limit.

Body

application/json
NameTypeDescription
namerequired
stringCampaign name shown in the dashboard
templaterequired
object
template.namerequired
stringAn APPROVED template on this account
template.languageoptional
stringDefaults to en_US
audiencerequired
one ofWho receives it. Opted-out and phone-less contacts are always skipped.
audience (option 1)optional
object
audience.typerequired
stringphones
audience.phonesrequired
string[]Numbers to reach; unknown ones become contacts
audience (option 2)optional
object
audience.typerequired
stringcontacts
audience.contact_idsrequired
string (uuid)[]
audience (option 3)optional
object
audience.typerequired
stringtags
audience.tag_idsrequired
string (uuid)[]Contacts carrying ANY of these tags
audience.exclude_tag_idsoptional
string (uuid)[]
audience (option 4)optional
object
audience.typerequired
stringall
audience.exclude_tag_idsoptional
string (uuid)[]
audience (option 5)optional
object
audience.typerequired
stringsegment
audience.segment_idrequired
string (uuid)UUID
audience.exclude_tag_idsoptional
string (uuid)[]
variablesoptional
map<string, object>Placeholder key ("1", "2", …) → how to fill it
header_media_urloptional
string (url)Overrides the template’s header media
schedule_atoptional
string (date-time)Start later; omit to start now
Request
curl -X POST "https://your-crm.example.com/api/v1/broadcasts" \
  -H "Authorization: Bearer $RAABTA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2a9e-3b4d-4c5e-8f7a-9b0c1d2e3f4a" \
  -d '{
    "name": "September reminders",
    "template": {
      "name": "appointment_reminder",
      "language": "en_US"
    },
    "audience": {
      "type": "tags",
      "tag_ids": [
        "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
      ]
    },
    "variables": {
      "1": {
        "type": "field",
        "value": "name"
      },
      "2": {
        "type": "static",
        "value": "Saturday 6 September"
      },
      "3": {
        "type": "static",
        "value": "11:00"
      }
    }
  }'
Response · 202 Accepted
{
  "data": {
    "id": "a7b8c9d0-e1f2-4a3b-8c4d-5e6f7a8b9c0d",
    "name": "September reminders",
    "kind": "template",
    "channel": "whatsapp",
    "template": {
      "name": "appointment_reminder",
      "language": "en_US"
    },
    "status": "scheduled",
    "scheduled_at": "2026-09-02T10:00:00.000Z",
    "counts": {
      "total": 240,
      "sent": 0,
      "delivered": 0,
      "read": 0,
      "replied": 0,
      "failed": 0
    },
    "created_at": "2026-09-02T10:00:00.000Z",
    "updated_at": "2026-09-02T10:02:00.000Z"
  }
}

Response

Wrapped in { data: … }
NameTypeDescription
idrequired
string (uuid)UUID
namerequired
stringCampaign name, as shown in the dashboard
kindrequired
string (enum)template = an approved WhatsApp template; free_text = a personal-WhatsApp messagetemplatefree_text
channelrequired
stringwhatsapp | personal_whatsapp
templaterequired
object | nullThe template a template broadcast sends
template.namerequired
string
template.languagerequired
string
statusrequired
string (enum)draft → scheduled → sending → sent | faileddraftscheduledsendingsentfailed
scheduled_atrequired
string (date-time) | nullWhen it was (or will be) started
countsrequired
objectLive delivery counters — poll this endpoint for progress
counts.totalrequired
integerRecipients materialised
counts.sentrequired
integerHanded to the channel
counts.deliveredrequired
integerConfirmed delivered to the handset
counts.readrequired
integerConfirmed read
counts.repliedrequired
integerRecipients who replied
counts.failedrequired
integerRejected by the channel
created_atrequired
string (date-time)ISO 8601 timestamp
updated_atrequired
string (date-time)ISO 8601 timestamp

Errors

StatusCodeWhen
400bad_requestThe request could not be parsed: malformed JSON, an invalid cursor, or a query parameter of the wrong shape.
400validation_errorThe body or query failed validation. `details` lists each failing field with a `path` and a `message`.
401unauthorizedNo usable API key: the Authorization header is missing or malformed, or the key is unknown, revoked or expired. The three are deliberately indistinguishable.
402plan_limit_reachedThe account has reached a plan limit for this resource. `details[0].limit` is the cap; ask the account owner to raise it.
403forbiddenThe key is valid but lacks the scope this endpoint requires, or the request came from an address outside the key’s IP allowlist. The message says which.
403account_suspendedThe account this key belongs to is suspended. Rotating the key will not help; contact support.
409conflictThe request conflicts with current state: a duplicate phone number on create, an invalid state transition, or an idempotent request that is still in flight.
422unprocessableThe request was well-formed but cannot be carried out. `reason` is a stable string saying why (for example `outside_window` or `stage_not_in_pipeline`).
422channel_not_connectedThe channel this request needs is not connected on the account. Connect it in Settings and retry.
429rate_limitedThe per-key budget, or the per-IP budget for failed authentication, is exhausted. Honour `Retry-After` before retrying.
500internalSomething failed on our side. Safe to retry with the same Idempotency-Key; quote `request_id` if it persists.