# Create a broadcast

`POST /v1/broadcasts`

Sends one approved template to up to 10,000 customers you list, or to contacts with certain tags, now or at `send_at`. It checks the template and the customers you list before it saves anything.

Needs `broadcasts:write`. Counts against the heavy request limit (10 a minute), and its messages against `broadcast_messages_per_month`.

## Body

- `name` (string, required): 1 to 255 characters.
- `template` (object, required): The template, by `id`, or by `name` and `language`, with its header file if it has an image, video or document header.
  - `id` (string, uuid): The template's ID. Send this, or `name` and `language`.
  - `name` (string): The template's name on the sending number. Send it with `language`, or send `id` instead.
  - `language` (string): The template's language, such as `en`. Needed with `name`.
  - `header` (object): The file to send, for a template with an image, video or document header. A header file must be JPEG or PNG (image), MP4 or 3GPP (video), or PDF (document).
    - `media_id` (string, uuid, required): An upload from `POST /v1/media`. It is kept until the broadcast has sent.
- `number_id` (string, uuid): The number to send from. Left out, the template's number.
- `recipients` (array of objects): The customers to send to. Send this or `audience`, not both. 1 to 10,000 items.
  - `to` (string, required): The phone number, with its country code. Spaces, dashes and brackets are fine.
  - `name` (string): Shown in the broadcast's list of recipients. Up to 255 characters.
  - `body` (object): Keys are the variable names in the template's `variables.body`. A map; each value is a string.
  - `header` (object): The value of the template's header variable.
    - `text` (string, required)
  - `buttons` (object): Keys are button indexes, as in the template's `variables.buttons`. A value is a link's suffix or a coupon code. A map; each value is a string.
- `audience` (object): Contacts picked by tag. Send this or `recipients`, not both.
  - `tags` (array of strings, required): Contacts with any of these tags are sent to. 1 to 20 items. Each 1 to 50 characters.
  - `exclude_tags` (array of strings): Contacts with any of these tags are left out. Up to 20 items. Each 1 to 50 characters.
  - `body` (object): Keys are the variable names in the template's `variables.body`. Each value says where that variable's value comes from, as `{from, fallback}`.
    - `from` (string, required): `contact.name`, `contact.phone` (as `+` and digits), `attribute:<name>` (one of the contact's `attributes`) or `text:<value>` (the same for everyone).
    - `fallback` (string): Used when the contact has no value, or one WhatsApp wouldn't take, such as a line break. Required for `contact.name` and `attribute:` sources.
  - `header` (object): The source of the template's header variable.
    - `text` (object, required): Where the header variable's value comes from, as `{from, fallback}`, like a `body` value.
  - `buttons` (object): Keys are button indexes, as in the template's `variables.buttons`. Each value says where that value comes from, as `{from, fallback}`.
- `send_at` (string, date-time): When to send, 5 minutes to 30 days from now. Left out, it sends now.

## Headers

- `Idempotency-Key` (string): Send one so a retry doesn't send the broadcast twice. See [Retrying safely](/reference/api/#retrying-safely). 1 to 255 characters.

## How it works

### Who gets it

Numbers that aren't valid, repeats of a number already listed, and, for a `MARKETING` template, contacts who opted out are left out and counted in `skipped`. The two spellings WhatsApp uses for Mexican and Argentine mobiles count as one number.

### Choosing the template

Authentication templates can't be broadcast. Send them with `POST /v1/messages`.

### Sending to an audience

Soosh collects the contacts in the background, a moment after you create the broadcast. It sends to contacts that existed when you made it and have the tags by then, even if you tagged them after. A contact added later isn't sent to. Numbers are cleaned as above.

If nobody is left to send to, the broadcast becomes `failed`, with `paused_reason` saying why.

### The monthly allowance

Soosh checks `broadcast_messages_per_month` when you create the broadcast. With an `audience` it checks again once the contacts are collected, and a broadcast that doesn't fit becomes `failed` the same way. If other messages use up the allowance while it sends, the broadcast pauses, with `paused_reason` saying so.

### Who runs it

The broadcast runs as the key that made it. If that key is revoked or loses `broadcasts:write`, the broadcast pauses with `paused_reason` "API access for this broadcast was removed".

### The status it starts in

- `preparing` when you pass an `audience`.
- `scheduled` when you pass `send_at`.
- `queued`, then `processing`, when it sends now. `queued` is usually a moment, but it can last about 5 minutes if queueing has to be retried.
- `paused`, with `paused_reason`, if its key can no longer run it when it starts.

### Request size

The body can be up to 4 MB. Other endpoints take 1 MB.

## Example: Scheduled, to a list of numbers

```sh
curl -X POST https://app.soosh.io/v1/broadcasts \
  -H "Authorization: Bearer $SOOSH_API_KEY" \
  -H "Idempotency-Key: create-broadcast-scheduled-to-a-list-1" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Diwali offer",
    "template": {
      "name": "diwali_offer",
      "language": "en"
    },
    "recipients": [
      {
        "to": "+919876543210",
        "name": "Priya",
        "body": {
          "1": "Priya"
        },
        "buttons": {
          "0": "PRIYA10"
        }
      },
      {
        "to": "+919812345678",
        "name": "Rahul",
        "body": {
          "1": "Rahul"
        },
        "buttons": {
          "0": "RAHUL10"
        }
      }
    ],
    "send_at": "2026-11-01T04:30:00Z"
  }'
```

Response 201:

```json
{
  "id": "c7a9e1b3-5d2f-4c68-8a0e-3b5d7f9a1c24",
  "name": "Diwali offer",
  "status": "scheduled",
  "template": {
    "id": "8d2f4c1e-7a3b-4e59-b0c6-1f2e3d4c5b6a",
    "name": "diwali_offer",
    "language": "en"
  },
  "number_id": "0f6d3c1e-5b7a-4c52-9a55-3f6a2d5c8b10",
  "send_at": "2026-11-01T04:30:00.000Z",
  "started_at": null,
  "completed_at": null,
  "paused_reason": null,
  "counts": {
    "total": 2,
    "sent": 0,
    "delivered": 0,
    "read": 0,
    "failed": 0,
    "replied": 0,
    "clicked": 0
  },
  "skipped": {
    "opted_out": 0,
    "duplicate": 0,
    "invalid_phone": 0
  },
  "created_by": {
    "type": "api_key",
    "id": "5e1d2c3b-4a59-4f68-8e7d-6c5b4a392817",
    "name": "Order system"
  },
  "created_at": "2026-10-14T10:49:51.327Z"
}
```

## Example: Everyone tagged VIP

```sh
curl -X POST https://app.soosh.io/v1/broadcasts \
  -H "Authorization: Bearer $SOOSH_API_KEY" \
  -H "Idempotency-Key: create-broadcast-everyone-tagged-vip-1" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Diwali offer",
    "template": {
      "name": "diwali_offer",
      "language": "en"
    },
    "audience": {
      "tags": [
        "VIP"
      ],
      "exclude_tags": [
        "churned"
      ],
      "body": {
        "1": {
          "from": "contact.name",
          "fallback": "there"
        }
      },
      "buttons": {
        "0": {
          "from": "text:DIWALI10"
        }
      }
    }
  }'
```

Response 201:

```json
{
  "id": "c7a9e1b3-5d2f-4c68-8a0e-3b5d7f9a1c24",
  "name": "Diwali offer",
  "status": "preparing",
  "template": {
    "id": "8d2f4c1e-7a3b-4e59-b0c6-1f2e3d4c5b6a",
    "name": "diwali_offer",
    "language": "en"
  },
  "number_id": "0f6d3c1e-5b7a-4c52-9a55-3f6a2d5c8b10",
  "send_at": null,
  "started_at": null,
  "completed_at": null,
  "paused_reason": null,
  "counts": {
    "total": 0,
    "sent": 0,
    "delivered": 0,
    "read": 0,
    "failed": 0,
    "replied": 0,
    "clicked": 0
  },
  "skipped": {
    "opted_out": 0,
    "duplicate": 0,
    "invalid_phone": 0
  },
  "created_by": {
    "type": "api_key",
    "id": "5e1d2c3b-4a59-4f68-8e7d-6c5b4a392817",
    "name": "Order system"
  },
  "created_at": "2026-10-14T10:49:51.327Z"
}
```

## Returns

201. The new broadcast. Follow it with `GET /v1/broadcasts/{id}` or the `broadcast.status` webhook.

## Errors

Besides the ones every request can get (https://docs.soosh.io/reference/api/errors/):

- 422 `invalid_field`: A field breaks a rule: for example both `recipients` and `audience`, a `send_at` outside 5 minutes to 30 days from now, a template or number that isn't yours, an authentication template, or nobody left to send to.
- 422 `template_params`: A listed customer's `body`, `header` or `buttons` keys don't match the template's `variables`. `error.details.rows` lists up to 100 bad rows as `{index, missing, unexpected}`.
- 422 `template_not_approved`: The template isn't approved.
- 422 `template_ambiguous`: The template name exists on more than one number and you didn't pass `number_id`.
- 422 `number_unavailable`: The sending number isn't active.
- 422 `limit_reached`: Your sent, queued and pending broadcast messages in the month it sends, plus this broadcast's customers, are over `broadcast_messages_per_month`.
- 409 `idempotency_in_progress`: A request with the same `Idempotency-Key` is still running.
- 422 `idempotency_mismatch`: The `Idempotency-Key` was used before for a different request, or by another of your API keys.