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

# Campaigns

> Bulk outbound voice campaigns — upload a CSV, dial thousands of contacts with cap-aware concurrency, retry logic, and live SSE metrics. Pause / resume / stop semantics.

A **campaign** dials a list of contacts through one phone number,
using one agent, with cap-aware concurrency.

## Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> draft
    draft --> scheduled: scheduled_at set
    draft --> running: start
    scheduled --> running: scheduled time hits
    running --> paused: pause
    paused --> running: resume
    running --> completed: stop / all done
    running --> failed: terminal error
```

## Create a campaign

The create endpoint is `multipart/form-data` because contacts are
uploaded as a CSV file (max 10 MB ≈ 100k contacts).

```python theme={null}
with open("contacts.csv", "rb") as f:
    campaign = client.campaigns.create(
        name="Q4 follow-ups",
        agent_id="<agent uuid>",
        phone_number_id="<phone number uuid>",
        contacts=f,
        max_concurrent_calls=5,
        retries_per_contact=1,
        retry_delay_seconds=300,
        scheduled_at="2026-05-10T18:00:00Z",  # optional, RFC3339
    )
```

### CSV format

* First row must be headers; **`phone_number` column is required**.
* Other columns become the contact's `custom_fields` (passed to the
  agent as variables for templating).

```csv theme={null}
phone_number,first_name,order_id
+14155550100,Sam,A-12001
+14155550101,Robin,A-12002
```

## Concurrent budget

When you start (or resume) a campaign, the server checks:

> sum of running campaigns' `max_concurrent_calls` + this campaign's
> `max_concurrent_calls` ≤ account `concurrent_max`

Failing this returns `409` with an explanatory message — pause
another campaign or reduce `max_concurrent_calls`.

Without this check, two campaigns at concurrency=5 on a tenant with
cap=10 would starve everything else.

## State transitions

| Operation            | Endpoint                             | SDK                           |
| -------------------- | ------------------------------------ | ----------------------------- |
| Start (or resume)    | `POST /api/v1/campaigns/{id}/start`  | `client.campaigns.start(id)`  |
| Pause                | `POST /api/v1/campaigns/{id}/pause`  | `client.campaigns.pause(id)`  |
| Resume               | `POST /api/v1/campaigns/{id}/resume` | `client.campaigns.resume(id)` |
| Stop                 | `POST /api/v1/campaigns/{id}/stop`   | `client.campaigns.stop(id)`   |
| Delete (drafts only) | `DELETE /api/v1/campaigns/{id}`      | `client.campaigns.delete(id)` |

**Stop** marks the campaign completed and skips pending contacts.
In-flight calls are NOT aborted — they finish naturally. "Stop" means
"no more new dials," not "kill in-flight."

## Live metrics (SSE)

`GET /api/v1/campaigns/{id}/events` is a Server-Sent Events stream:

* 2-second `data: {snapshot}\n\n` ticks while the campaign runs.
* 15-second `: heartbeat` comments to keep proxies from idle-timing out.
* Closes with `event: end` when the campaign reaches a terminal status.

```python theme={null}
for event in client.campaigns.stream_events(campaign_id):
    print(event)
```

## Contact pagination

`GET /api/v1/campaigns/{id}/contacts?status=failed&page=0` —
paginated, 100 per page, optional status filter.
