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

# Create and Manage Broadcast Campaigns — Zexa API

> POST /campaigns to create and launch a broadcast, GET /campaigns/{id} to track its status and delivery stats across any Zexa channel.

The Campaigns API lets you broadcast a message to an entire contact list in a single API call. Campaigns are ideal for promotional announcements, service notifications, and event reminders sent to hundreds or thousands of recipients simultaneously. You can send a campaign immediately or schedule it for a future date and time, and track its delivery progress through the campaign status endpoint.

## Create a Campaign

```text theme={null}
POST https://api.zexa.ao/v1/campaigns
```

### Request Parameters

<ParamField body="name" type="string" required>
  A descriptive internal name for the campaign (e.g. `Weekend Sale - June 2026`). This name is visible only in your dashboard and API responses — recipients do not see it.
</ParamField>

<ParamField body="channel" type="string" required>
  The messaging channel to use. Must be one of: `sms`, `whatsapp`, `telegram`, `email`, `slack`.
</ParamField>

<ParamField body="contact_list_id" type="string" required>
  The ID of the contact list to target. All active contacts in the list will receive the campaign message. Retrieve list IDs from the [Contacts API](/api/contacts) or your dashboard.
</ParamField>

<ParamField body="sender_id" type="string" required>
  Your registered Sender ID (for SMS, WhatsApp, Telegram) or verified email address (for email) to use as the message origin. Use `GET /sender-ids` to list your registered Sender IDs.
</ParamField>

<ParamField body="body" type="string">
  The plain-text message body. Required unless `template` is provided.
</ParamField>

<ParamField body="template" type="object">
  A WhatsApp-approved message template. Use instead of `body` when sending WhatsApp campaigns that require a pre-approved template.

  <Expandable title="template fields">
    <ParamField body="template.name" type="string" required>
      The exact name of the approved WhatsApp template.
    </ParamField>

    <ParamField body="template.language" type="string" required>
      The language code for the template (e.g. `pt_BR`, `en_US`).
    </ParamField>

    <ParamField body="template.variables" type="array">
      An ordered array of string values to substitute into the template's variable placeholders.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="scheduled_at" type="string">
  An ISO 8601 UTC datetime string specifying when to start sending the campaign (e.g. `2026-07-01T08:00:00Z`). Set to `null` or omit entirely to begin sending immediately after the campaign is created.
</ParamField>

### Request Example

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.zexa.ao/v1/campaigns \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Weekend Sale",
      "channel": "sms",
      "contact_list_id": "lst_abc123",
      "sender_id": "MYBRAND",
      "body": "Get 20% off this weekend only. Shop now at myapp.ao. Reply STOP to opt out.",
      "scheduled_at": null
    }'
  ```

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

  response = requests.post(
      "https://api.zexa.ao/v1/campaigns",
      headers={
          "Authorization": "Bearer YOUR_API_KEY",
          "Content-Type": "application/json",
      },
      json={
          "name": "Weekend Sale",
          "channel": "sms",
          "contact_list_id": "lst_abc123",
          "sender_id": "MYBRAND",
          "body": "Get 20% off this weekend only. Shop now at myapp.ao. Reply STOP to opt out.",
          "scheduled_at": None,
      },
  )

  print(response.json())
  ```
</CodeGroup>

### Response Fields

<ResponseField name="id" type="string">
  The unique campaign identifier, prefixed with `cmp_`.
</ResponseField>

<ResponseField name="status" type="string">
  The initial campaign status after creation. See [Campaign Statuses](#campaign-statuses).
</ResponseField>

<ResponseField name="name" type="string">
  The campaign name as provided in the request.
</ResponseField>

<ResponseField name="channel" type="string">
  The messaging channel used for this campaign.
</ResponseField>

<ResponseField name="total_recipients" type="integer">
  The total number of contacts targeted by this campaign.
</ResponseField>

<ResponseField name="created_at" type="string">
  ISO 8601 UTC timestamp of when the campaign was created.
</ResponseField>

**Example response (`201 Created`):**

```json theme={null}
{
  "id": "cmp_xyz789",
  "status": "processing",
  "name": "Weekend Sale",
  "channel": "sms",
  "total_recipients": 850,
  "created_at": "2026-06-24T10:00:00Z"
}
```

### Error Scenarios

| Status | Error                  | Description                                                             |
| ------ | ---------------------- | ----------------------------------------------------------------------- |
| `400`  | `invalid_request`      | Missing required fields or malformed JSON body                          |
| `401`  | `unauthorized`         | API key is missing or invalid                                           |
| `404`  | `not_found`            | The specified `contact_list_id` does not exist                          |
| `422`  | `validation_error`     | `sender_id` is not registered, or `body` and `template` are both absent |
| `422`  | `insufficient_credits` | Not enough credits to dispatch the campaign                             |
| `429`  | `rate_limit_exceeded`  | Too many requests — retry after the time indicated in `Retry-After`     |

***

## Get Campaign Status

Retrieve the current status and delivery statistics for a specific campaign:

```text theme={null}
GET https://api.zexa.ao/v1/campaigns/{id}
```

<CodeGroup>
  ```bash curl theme={null}
  curl https://api.zexa.ao/v1/campaigns/cmp_xyz789 \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

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

  response = requests.get(
      "https://api.zexa.ao/v1/campaigns/cmp_xyz789",
      headers={"Authorization": "Bearer YOUR_API_KEY"},
  )

  print(response.json())
  ```
</CodeGroup>

### Response Fields

<ResponseField name="id" type="string">
  The unique campaign identifier, prefixed with `cmp_`.
</ResponseField>

<ResponseField name="name" type="string">
  The campaign name as provided when created.
</ResponseField>

<ResponseField name="status" type="string">
  The current campaign status. See the [Campaign Statuses](#campaign-statuses) table below.
</ResponseField>

<ResponseField name="channel" type="string">
  The messaging channel used for this campaign.
</ResponseField>

<ResponseField name="total_recipients" type="integer">
  The total number of contacts targeted by this campaign.
</ResponseField>

<ResponseField name="delivered" type="integer">
  The number of messages confirmed as delivered so far.
</ResponseField>

<ResponseField name="failed" type="integer">
  The number of messages that failed or were undeliverable.
</ResponseField>

<ResponseField name="created_at" type="string">
  ISO 8601 UTC timestamp of when the campaign was created.
</ResponseField>

<ResponseField name="sent_at" type="string">
  ISO 8601 UTC timestamp of when sending began. Null if the campaign has not started yet.
</ResponseField>

<ResponseField name="completed_at" type="string">
  ISO 8601 UTC timestamp of when all messages reached a final delivery status. Null if the campaign has not yet completed.
</ResponseField>

**Example response:**

```json theme={null}
{
  "id": "cmp_xyz789",
  "name": "Weekend Sale",
  "status": "completed",
  "channel": "sms",
  "total_recipients": 850,
  "delivered": 812,
  "failed": 38,
  "created_at": "2026-06-24T10:00:00Z",
  "sent_at": "2026-06-24T10:00:12Z",
  "completed_at": "2026-06-24T10:05:47Z"
}
```

## Campaign Statuses

| Status       | Description                                                                                 |
| ------------ | ------------------------------------------------------------------------------------------- |
| `draft`      | Campaign has been created but sending has not started                                       |
| `scheduled`  | Campaign is queued and will begin sending at the specified `scheduled_at` time              |
| `processing` | Messages are actively being dispatched to recipients                                        |
| `sent`       | All messages have been dispatched to carriers; delivery confirmations may still be arriving |
| `completed`  | All messages have reached a final delivery status — the campaign is fully resolved          |
| `failed`     | The campaign could not be processed; contact support with the campaign ID                   |

<Warning>
  A campaign in `processing` status cannot be cancelled. Verify your contact list, message body, and schedule before submitting a campaign request.
</Warning>

***

## List Campaigns

Retrieve a paginated list of all campaigns on your account:

```text theme={null}
GET https://api.zexa.ao/v1/campaigns
```

<ParamField query="page" type="integer">
  Page number (default: `1`).
</ParamField>

<ParamField query="per_page" type="integer">
  Results per page (default: `50`, max: `100`).
</ParamField>

<CodeGroup>
  ```bash curl theme={null}
  curl "https://api.zexa.ao/v1/campaigns?page=1&per_page=20" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

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

  response = requests.get(
      "https://api.zexa.ao/v1/campaigns",
      params={"page": 1, "per_page": 20},
      headers={"Authorization": "Bearer YOUR_API_KEY"},
  )

  print(response.json())
  ```
</CodeGroup>

**Example response:**

```json theme={null}
{
  "data": [
    {
      "id": "cmp_xyz789",
      "name": "Weekend Sale",
      "status": "completed",
      "channel": "sms",
      "total_recipients": 850,
      "created_at": "2026-06-24T10:00:00Z"
    }
  ],
  "meta": {
    "total": 14,
    "page": 1,
    "per_page": 20
  }
}
```
