Campaigns
Create outbound campaigns and run them at scale.
A campaign dials a list of contacts with a published agent, on a schedule and within your compliance window. The typical flow: create → set contacts → publish → run, then poll the run for progress.
Campaign object:
{
"id": "cmp_…",
"name": "August outreach",
"channel": "voice",
"workflowType": "collections",
"dltTemplateRef": null,
"agentId": "agt_…",
"status": "draft",
"settings": { "fromNumber": "+91…", "timezone": "Asia/Kolkata", "startAt": null, "endAt": null,
"dailyFrom": "10:00", "dailyTo": "19:00", "retryCount": 2, "cooldownMinutes": 30,
"emailNotifications": [] },
"callingList": null
}
Run object:
{
"id": "run_…", "campaignId": "cmp_…", "status": "running",
"attempted": 120, "placed": 96, "rejected": 24,
"skippedReason": null, "error": null,
"createdAt": "2026-08-20T10:00:00Z", "finishedAt": null
}
List campaigns
GET /v1/campaigns · scope campaigns:read → { "data": campaign[] } (all in the workspace).
Get a campaign
GET /v1/campaigns/:id · scope campaigns:read → { "data": campaign }. Unknown → 404 campaign not found.
Create a campaign
POST /v1/campaigns · scope campaigns:write
Body
| Field | Type | Default / constraints |
|---|---|---|
name |
string | max 120, default "" |
channel |
enum | voice, sms, whatsapp, email, ivr - default voice |
workflowType |
enum | collections, onboarding - default collections |
agentId |
string | null | default null |
dltTemplateRef |
string | null | default null |
settings |
object | see below |
settings: fromNumber (string|null), timezone (valid IANA tz|null), startAt/endAt/dailyFrom/dailyTo (string|null), retryCount (int 0–10, default 0), cooldownMinutes (int ≥0, default 0), emailNotifications (string[] of emails, default []).
Validation
- If
agentIdis set, the agent must exist and be published - else422 agent must be published before it can run a campaign. - If
settings.fromNumberis set, it must be an active number you own - else400 from number is not an active number you own.
Response 201: { "data": campaign } (status: "draft", callingList: null).
Update a campaign
PUT /v1/campaigns/:id · scope campaigns:write
PATCH semantics - send only what changes: name, agentId, dltTemplateRef, settings (each sub-field optional, merged over current). The agent-published and owned-number checks run only for the fields you send. Unknown → 404.
Set the calling list
POST /v1/campaigns/:id/contacts · scope campaigns:write · rate limited
Replaces the entire calling list.
{
"contacts": [
{ "phone": "+919812345678", "name": "Nalin", "variables": { "amount": "4999" } },
{ "phone": "+919812345679", "name": "Asha" }
]
}
contacts- 1 to 50,000 rows. Each row:phone(8–20, required),name(optional),variables(optional object).- Each phone is validated; duplicates keep the first occurrence. Invalid and duplicate rows are stored flagged and skipped at dial time.
Response 200:
{ "data": { "total": 2, "valid": 2, "invalid": 0 } }
Publish / pause
POST /v1/campaigns/:id/status · scope campaigns:write
{ "status": "active" } // "draft" | "active" | "paused"
Publishing (active) requires the attached agent to be published (422 otherwise). Response 200: { "data": campaign }.
Start a run
POST /v1/campaigns/:id/run · scope campaigns:write · rate limited · no body
Kicks off background dialing.
Response 202:
{ "data": { "runId": "run_…", "status": "queued" } }
403 workspace suspended404 campaign not found409 campaign must be published before it can run(status isn’tactive)
Runs
GET /v1/campaigns/:id/runs· scopecampaigns:read→{ "data": run[] }. Unknown campaign →404.GET /v1/campaign-runs/:runId· scopecampaigns:read→{ "data": run }. Unknown →404 run not found.
Poll a run’s status, attempted/placed/rejected counts, and skippedReason (e.g. outside_schedule, kyb_unverified, wallet) to track progress.
Delete a campaign
DELETE /v1/campaigns/:id · scope campaigns:write → { "data": { "id": "cmp_…", "deleted": true } }.
409 pause the campaign before deleting it(active campaigns can’t be deleted)404 campaign not found
Next: Phone numbers →