Campaigns overview
How campaigns dispatch bulk outbound calls, manage concurrency, isolate phone-number pools per telephony config, and surface structured event logs.
A campaign is a managed bulk-outbound batch: a CSV of contacts + a target agent + a telephony configuration + per-campaign concurrency, retry, scheduling, and circuit-breaker rules. zoxaAI dispatches calls, respects all the limits, and records every important state transition as a structured logs[] entry on the campaign — you call start, then poll progress / runs (or read logs[]) for visibility.
Lifecycle
created → syncing → running → (paused | completed | failed)
↑ ↓
└── resume ─────┘pause works only while running. resume works from either paused or failed — both are recoverable, and resume revalidates wallet + telephony first. Transient trouble while dialing (repeated batch errors, a drained phone-number pool) pauses the campaign, so failed is reserved for unrecoverable setup errors (e.g. a CSV sync failure). Completion is exact: a campaign moves to completed the moment its last call's outcome lands.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /campaign/create | Create a campaign tied to an agent + a CSV source + (optionally) a telephony config. |
GET | /campaign | List campaigns in your org (paginated; state / archived filters). |
GET | /campaign/{id} | Single campaign detail + stats + logs[]. |
PATCH | /campaign/{id} | Update name, concurrency, retry/schedule/circuit-breaker, and telephony config (before running). |
POST | /campaign/{id}/start | Begin dispatching. |
POST | /campaign/{id}/pause | Pause dispatch (in-flight calls finish). |
POST | /campaign/{id}/resume | Resume a paused or failed campaign. |
POST | /campaign/{id}/archive | Hide a campaign (reversible, never deletes). |
POST | /campaign/{id}/unarchive | Restore an archived campaign. |
GET | /campaign/{id}/progress | Real-time progress counters. |
GET | /campaign/{id}/contacts | Per-contact ledger with retry chains. |
GET | /campaign/{id}/insights | Funnel, outcomes, retry effectiveness, cost/duration, timeline. |
GET | /campaign/{id}/runs | Paginated per-call results. |
WS | /campaign/{id}/events | Live event stream for a campaign. |
GET | /campaign/{id}/source-download-url | Signed URL to download the original CSV. |
Target agent
Every campaign targets one agent. Pass the agent's public uuid as agent_uuid when you create the campaign; each contact is called by that agent's pipeline. The campaign response reports the resolved agent as agent_id (internal integer id) and agent_name. The target can't be changed after creation.
CSV source
Campaigns are CSV-only. Contacts come from a previously-uploaded CSV file (via Files or the campaign create form). Required: a phone_number column. Optional: any number of template-variable columns — referenced from your agent prompt as {{column_name}}.
Telephony configuration
Each campaign is bound to a specific telephony configuration — the provider account and outbound phone-number pool it dials from. Pass telephony_configuration_id on create to pick one explicitly, or omit it to inherit the org's default.
From-number pool isolation
The from-number pool is keyed per (organization, telephony_configuration_id). Two campaigns running on different telephony configs draw from independent caller-ID pools — they never share or steal numbers. This isolation also drives the bounded phone-pool exhaustion retry (see Logs below).
Concurrency, retry, schedule, circuit breaker
All four are optional structured configs on create / update:
| Block | Controls |
|---|---|
max_concurrency | Hard ceiling on simultaneously-active calls for this campaign. |
retry_config | When + how often to retry busy / no-answer / voicemail contacts. |
schedule_config | Time windows (e.g. business hours, timezone). |
circuit_breaker | Auto-pause if failure rate spikes within a rolling window. |
`max_concurrency` is capped
The effective ceiling is the lower of the org concurrency limit and the available caller-number count (from the org's default telephony config). A 400 is returned if you try to exceed it.
Structured event logs
Every campaign carries an append-only logs[] array in its API response. The orchestrator, circuit breaker, source-sync, and phone-pool-exhaustion handler all write structured entries to it — so operators can see why a campaign moved to paused or failed without trawling server logs.
Each entry has:
| Field | Type | Notes |
|---|---|---|
timestamp | ISO 8601 string | When the event happened. |
level | string | info / warning / error. |
event | string | Event identifier (see below). |
message | string | Human-friendly summary. |
details | object | null | Event-specific structured context. |
Common event identifiers:
| Event | Meaning | Notable details |
|---|---|---|
circuit_breaker_tripped | Failure rate crossed the configured threshold | Window stats + recent_failures[] (last 20 failures — call id, reason, timestamp) |
phone_number_pool_exhausted_retry | No free from-number available; retry attempt N of 3 | attempt, max_attempts |
phone_number_pool_exhausted | All 3 retries exhausted; campaign paused (recoverable) | attempt, max_attempts |
batch_failure_retry | A batch errored; bounded retry attempt N of 3 | error |
batch_failures_paused | 3 consecutive batch failures; campaign paused (recoverable) | error |
stuck_rows_swept | The self-healing sweep recovered contacts a crashed worker stranded | reverted, advanced |
stale_call_resolved | A call whose terminal status callback never arrived was resolved | queued_run_ids |
campaign_archived / campaign_unarchived | Campaign hidden / restored | — |
| Source-sync failures | CSV parse / schema issues | Underlying error |
Pool exhaustion and batch errors pause, not fail
Repeated batch failures and a drained phone-number pool pause the campaign (recoverable) after 3 consecutive misses, with a structured log explaining why. A paused campaign can be resumed once the underlying issue clears.
Related
- Calls overview
- Agent config — defines what each campaign call runs