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

# Campaigns

> Plan, schedule, launch, and monitor bulk messaging campaigns in Briq — primarily through the dashboard, with an optional developer API for advanced integrations.

Campaigns are how Briq organizes bulk messaging into reusable, schedulable units — each one bundles an **audience**, **content**, and one or more **runs** that decide when the message actually goes out. The primary surface for managing campaigns is the [Briq dashboard](https://briq.tz/login); the API documented here exists for integrators who need to automate or embed the same workflow into their own systems.

## What a campaign is made of

A campaign is a parent record with three logical children. None of them are useful in isolation — the campaign only sends when all three are present and validated.

* **Audiences** — one or more contact groups attached to the campaign. These are the recipients. A campaign with no audiences cannot be launched.
* **Content** — the message body or template for the campaign, **one row per channel**. SMS, voice, and WhatsApp each have their own content row. You cannot attach two content rows for the same channel to the same campaign.
* **Runs** — the scheduled executions of the campaign. A run can be **single-shot** (fire once at a specific time) or **recurring** (a cron-style schedule). A campaign with no run will never send anything, no matter how complete its audience and content are.

## Dashboard vs API — when to use which

<Note>
  The dashboard is the default surface for campaigns. Reach for the API only when you genuinely need automation — most teams ship every campaign they will ever run from the dashboard alone.
</Note>

| Surface                                                | Use it when                                                                                                                                                                        |
| ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Dashboard** ([briq.tz/login](https://briq.tz/login)) | Day-to-day campaigns, marketing sends, one-off announcements, manual scheduling, reviewing analytics, and pausing or cancelling live runs.                                         |
| **API** (`https://karibu.briq.tz`)                     | CI/CD-driven sends, programmatic recurring drops, CRM-triggered campaigns, server-driven retry or cancel orchestration, and embedding campaign management inside your own product. |

If you are not sure which one you need, use the dashboard. Everything described on the API subpages is available there with no code.

## Lifecycle at a glance

Every campaign moves through a fixed set of states. The dashboard surfaces them in the campaign header; the API returns them on `GET /v1/campaign/{id}/status`.

```
draft → configured → ready → scheduled → running → completed
                                                 → failed
                                                 → cancelled
                                                 → paused (reversible)
```

| Status       | Meaning                                                                     |
| ------------ | --------------------------------------------------------------------------- |
| `draft`      | Campaign record exists but has no audience or content yet. Not launchable.  |
| `configured` | Audience and content are attached. Has not been validated yet.              |
| `ready`      | Validation passed. Eligible to be launched.                                 |
| `scheduled`  | Launched with a future run time; waiting for the scheduler to pick it up.   |
| `running`    | The engine has claimed the run and is actively dispatching messages.        |
| `paused`     | A running campaign was paused. Reversible — resume returns it to `running`. |
| `completed`  | All scheduled work finished. Terminal.                                      |
| `failed`     | The run ended in an error state. Terminal.                                  |
| `cancelled`  | Operator-cancelled. Terminal and sticky — see below.                        |

Two rules are worth memorizing:

1. **Editing a `ready` or `scheduled` campaign drops it back to `configured`.** Any change to audiences, content, or runs invalidates the pre-flight check. You must call `/validate` again (dashboard does this automatically when you re-open the launch panel) before you can launch.
2. **`cancelled` is sticky.** There is no un-cancel. Cancelling refunds unsent message reservations and closes the campaign permanently. If you need to send again, clone the campaign.

## Run status

Each individual run inside a campaign has its own status, separate from the campaign-level status. This is what powers per-run analytics and retry decisions.

| Run status  | Meaning                                                 |
| ----------- | ------------------------------------------------------- |
| `pending`   | Run is created and waiting for its scheduled time.      |
| `running`   | Engine has claimed the run and is dispatching messages. |
| `completed` | Run finished and all messages were dispatched.          |
| `failed`    | Run terminated in error before completing.              |
| `cancelled` | Run was cancelled before or during execution.           |

<Note>
  Internally the engine uses a `claimed` state when a worker picks up a run. The API never exposes this — `claimed` is always translated to `running` in responses. Treat `running` as the only "in-flight" state you need to handle.
</Note>

## The dashboard walkthrough (high level)

If you are using the dashboard — which is the recommended path — the flow is five steps:

1. **Open your workspace** at [briq.tz/login](https://briq.tz/login) and switch to the workspace that owns the campaign.
2. **Click "New Campaign"** and give it a name and description.
3. **Add audiences** by selecting one or more existing contact groups.
4. **Add content** for each channel you intend to use (SMS, voice, WhatsApp). The dashboard enforces the one-row-per-channel rule.
5. **Schedule a run and click Launch.** The dashboard runs validation behind the Launch button — if anything is missing or inconsistent it tells you exactly which field to fix — and then moves the campaign through the same lifecycle states described above.

You never need to touch the API for any of this.

## The API walkthrough (high level)

If you do need to automate, the seven calls below are the canonical sequence. Each subpage walks through the payloads, error cases, and idempotency rules in detail.

```
1. POST /v1/campaign                              — create
2. POST /v1/campaign/{id}/audiences               — attach contact groups
3. POST /v1/campaign/{id}/content                 — attach message/template
4. POST /v1/campaign/{id}/runs                    — schedule a run
5. POST /v1/campaign/{id}/validate                — pre-flight check
6. POST /v1/campaign/{id}/launch                  — go live
7. GET  /v1/campaign/{id}/status                  — poll for progress
```

The three campaign subpages cover these in order:

* [Building a campaign](/guides/campaigns-building) — creating the campaign record and attaching audiences, content, and runs.
* [Launch control](/guides/campaigns-launch-control) — validating, launching, pausing, resuming, cancelling, and retrying.
* [Observability](/guides/campaigns-observability) — status polling, analytics, and run-level detail.

## Authentication snapshot

Every campaign API call is authenticated the same way:

* **`X-API-Key`** (required) — your developer API key. The key must be in `ACTIVE` state; revoked or expired keys return `401 UNAUTHORIZED`.
* **`X-App-ID`** (optional but recommended) — the UUID of the developer app to attribute the call to. Send this when your API key is linked to multiple developer apps so Briq can disambiguate which one is calling.

When the API key is **workspace-scoped** (through its linked developer app), every campaign call is sandboxed to that workspace — you cannot list, read, or modify campaigns in any other workspace using that key. Attempting to do so returns `403 FORBIDDEN`.

The developer host is **`https://karibu.briq.tz`**. See [Developer Apps](/guides/developer-apps) for how keys and app IDs are issued, and [Workspaces](/guides/workspaces) for how scoping works.

## Response envelope

Every campaign endpoint returns the same JSON envelope:

```json theme={null}
{
  "success": true,
  "data": { },
  "errors": null,
  "request_id": "<uuid>"
}
```

Three rules to internalize:

* **`success` mirrors HTTP status.** `true` on 2xx, `false` on 4xx and 5xx. Branch on `success` rather than re-parsing the status code.
* **On error, `data` is `null` and `errors` is an array** of `{ code, message, field }` objects. `field` is populated for validation errors so you can map the failure back to the specific input.
* **Always log `request_id`.** It is how Briq support correlates your call to internal traces. Include it in any support ticket.

## Error codes at a glance

| Code                | HTTP      | Meaning                                                                                                                 |
| ------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------- |
| `VALIDATION_FAILED` | 400 / 422 | Body, query, or current campaign state failed validation.                                                               |
| `UNAUTHORIZED`      | 401       | API key missing, invalid, expired, or revoked.                                                                          |
| `FORBIDDEN`         | 403       | Authenticated but not allowed — workspace ownership or app scope mismatch.                                              |
| `NOT_FOUND`         | 404       | Resource does not exist or is soft-deleted.                                                                             |
| `CONFLICT`          | 409       | State precondition not met — duplicate content for a channel, missing validation on launch, or a run already in flight. |
| `INTERNAL_ERROR`    | 500       | Unexpected server error. Retry with backoff and report `request_id` if it persists.                                     |
| `DEPRECATED`        | 410       | The endpoint has been removed. Migrate to the replacement listed in the response message.                               |

## Best practices

* **Use the dashboard for one-off sends.** The API is for automation — every dashboard action has an API equivalent, but not every API workflow is worth the engineering cost when a human will only do it once.
* **Always validate before launching.** The dashboard does this automatically; via the API, call `/validate` before `/launch` so failures surface as actionable field errors instead of a launch-time `CONFLICT`.
* **Treat `cancelled` as permanent.** It refunds unsent message reservations and cannot be undone. Clone the campaign if you need to send again.
* **Log every `request_id`.** Support cannot trace a call without it. Persist it alongside whichever business object you correlate to the campaign.
* **Poll `/status` no faster than every 5–15 seconds**, and back off entirely once the campaign reaches a terminal state (`completed`, `failed`, `cancelled`). For real-time progress, prefer [Webhooks](/guides/webhooks) over polling.
