> ## Documentation Index
> Fetch the complete documentation index at: https://docs.keebai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# POST /v1/messages/bulk

> Send an approved template to multiple recipients in a single request. Up to 5000 recipients per call. Returns a broadcast id for tracking.

Send the same template to multiple recipients, with potentially different variables per recipient. Creates a broadcast in the database that you can track via [`GET /v1/messages/bulk/:broadcastId`](/dev/endpoints/messages-bulk-status).

## Endpoint

```
POST https://api.keebai.com/v1/messages/bulk
```

## Required scope

`messages:bulk`

<Warning>
  The `messages:bulk` scope is separate from `messages:send` precisely because bulk sending carries higher risk (Meta cost, sender number reputation, potential abuse). Grant it only to tokens controlled by the marketing tool or the job that needs to trigger campaigns.
</Warning>

## Headers

| Header          | Required | Value                    |
| --------------- | -------- | ------------------------ |
| `Authorization` | Yes      | `Bearer kbai_pk_<token>` |
| `Content-Type`  | Yes      | `application/json`       |

## Body

| Field             | Type     | Required | Description                                                                                                                                                                        |
| ----------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `template_name`   | `string` | Yes      | Name of the approved template.                                                                                                                                                     |
| `language`        | `string` | Yes      | Template language code.                                                                                                                                                            |
| `channel_id`      | `string` | No       | Keebai channel id of the WhatsApp channel. Only needed when the project has more than one active WhatsApp channel.                                                                 |
| `phone_number_id` | `string` | No       | Alias for `channel_id`: the numeric id Meta issues for the sending number, not a Keebai `ObjectId`. List yours with [`GET /v1/whatsapp/numbers`](/dev/endpoints/whatsapp-numbers). |
| `campaign_name`   | `string` | No       | Human-readable name to identify the campaign in the portal. Auto-generated if omitted.                                                                                             |
| `recipients`      | `array`  | Yes      | Between 1 and 5000 recipients. Each item: `{ to, variables?, meta_data? }`.                                                                                                        |

### Structure of each `recipients[i]`

| Field       | Type                     | Required | Description                                             |
| ----------- | ------------------------ | -------- | ------------------------------------------------------- |
| `to`        | `string`                 | Yes      | Phone number in E.164 format.                           |
| `variables` | `object<string, string>` | No       | Template variables for this specific recipient.         |
| `meta_data` | `object`                 | No       | Free-form metadata persisted with this individual send. |

## Example request

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.keebai.com/v1/messages/bulk \
    -H "Authorization: Bearer kbai_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
    -H "Content-Type: application/json" \
    -d '{
      "template_name": "promo_marzo_2026",
      "language": "es",
      "phone_number_id": "109876543210987",
      "campaign_name": "Promo marzo 2026 — clientes activos",
      "recipients": [
        { "to": "+5491155551111", "variables": { "nombre": "Juan" } },
        { "to": "+5491155552222", "variables": { "nombre": "Camila" } },
        { "to": "+5491155553333", "variables": { "nombre": "Joaquín" } }
      ]
    }'
  ```

  ```js JavaScript theme={null}
  const recipients = customers.map((c) => ({
    to: c.phone,
    variables: { nombre: c.first_name },
  }));

  await fetch("https://api.keebai.com/v1/messages/bulk", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.KEEBAI_API_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      template_name: "promo_marzo_2026",
      language: "es",
      phone_number_id: process.env.KEEBAI_PHONE_NUMBER_ID,
      campaign_name: "Promo marzo 2026",
      recipients,
    }),
  });
  ```

  ```python Python theme={null}
  import os, requests

  recipients = [
      {"to": c["phone"], "variables": {"nombre": c["first_name"]}}
      for c in customers
  ]

  resp = requests.post(
      "https://api.keebai.com/v1/messages/bulk",
      headers={"Authorization": f"Bearer {os.environ['KEEBAI_API_TOKEN']}"},
      json={
          "template_name": "promo_marzo_2026",
          "language": "es",
          "phone_number_id": os.environ["KEEBAI_PHONE_NUMBER_ID"],
          "campaign_name": "Promo marzo 2026",
          "recipients": recipients,
      },
      timeout=30,
  )
  resp.raise_for_status()
  broadcast_id = resp.json()["broadcast_id"]
  ```
</CodeGroup>

## Response

### 202 Accepted

```json theme={null}
{
  "broadcast_id": "65f4b5c6d7e8f9a0b1c2d3e4",
  "status": "PENDING",
  "total_recipients": 3,
  "scheduled_at": null
}
```

| Field              | Type             | Description                                                                  |
| ------------------ | ---------------- | ---------------------------------------------------------------------------- |
| `broadcast_id`     | `string`         | Broadcast id. Store it to check status later.                                |
| `status`           | `string`         | Initial status: `PENDING`. Switches to `IN_PROGRESS` once processing starts. |
| `total_recipients` | `number`         | Number of accepted recipients.                                               |
| `scheduled_at`     | `string \| null` | Schedule date if applicable.                                                 |

### 400 Bad Request

Invalid body: empty array, more than 5000 recipients, malformed phone number on any recipient.

### 401 Unauthorized

Missing, invalid, revoked, or expired token.

### 403 Forbidden

The token does not have the `messages:bulk` scope.

## Best practices

* **Split very large batches**. If your campaign has 50000 recipients, send 10 requests of 5000 each instead of fighting with timeouts. Use different `campaign_name` values to keep them distinct.
* **Deduplicate before sending**. The API does not deduplicate by phone number — if you send the same recipient twice, the message goes out twice and Meta charges twice.
* **Keep variables consistent with the template**. If the template expects `{{nombre}}` and `{{monto}}` and a recipient is missing `monto`, that individual send will fail. Validate the variable set up front.
* **Monitor status** via [`GET /v1/messages/bulk/:broadcastId`](/dev/endpoints/messages-bulk-status) to detect campaigns with a high error rate.


## OpenAPI

````yaml POST /v1/messages/bulk
openapi: 3.0.0
info:
  title: Keebai Public API
  description: Superficie pública REST autenticada con Personal Access Tokens (PAT).
  version: 1.0.0
  contact: {}
servers:
  - url: https://api.keebai.com
    description: Production
security: []
tags: []
paths:
  /v1/messages/bulk:
    post:
      tags:
        - messages
      summary: Schedule a bulk template broadcast to many recipients
      description: WhatsApp Cloud API only. Runs as an asynchronous job.
      operationId: PublicMessagesController_sendBulk
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendBulkDto'
      responses:
        '202':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkDispatchResponse'
      security:
        - PAT: []
components:
  schemas:
    SendBulkDto:
      type: object
      properties:
        channel_id:
          type: string
          example: 665f1a2b3c4d5e6f70819234
          description: >-
            Keebai channel id. Optional when the tenant has exactly one active
            channel of the given type; required to disambiguate otherwise.
        phone_number_id:
          type: string
          example: '100000000000001'
          description: >-
            WhatsApp-only alias for channel_id: the Meta phone_number_id of the
            sending number. Ignored on other channels.
        template_name:
          type: string
          example: welcome_v2
        language:
          type: string
          example: es
        campaign_name:
          type: string
          example: Onboarding marzo 2026
        recipients:
          minItems: 1
          maxItems: 5000
          type: array
          items:
            $ref: '#/components/schemas/BulkRecipientDto'
      required:
        - template_name
        - language
        - recipients
    BulkDispatchResponse:
      type: object
      properties:
        broadcast_id:
          type: string
        status:
          type: string
          example: scheduled
        total_recipients:
          type: number
        scheduled_at:
          type: string
          nullable: true
      required:
        - broadcast_id
        - status
        - total_recipients
    BulkRecipientDto:
      type: object
      properties:
        to:
          type: string
          example: '+5491155555555'
        variables:
          type: object
          example:
            nombre: Juan
        meta_data:
          type: object
          description: >-
            Free-form metadata persisted with the message. Returned on webhooks
            for correlation.
      required:
        - to
  securitySchemes:
    PAT:
      scheme: bearer
      bearerFormat: kbai_pk_<hex>
      type: http
      description: >-
        Personal Access Token con prefijo `kbai_pk_`. Generar desde el portal
        con permiso `developer.manage_tokens`.

````