Campaigns
Run outbound calling campaigns: upload contacts, set calling windows, and track outcomes.
A campaign dials a list of contacts with one of your agents, from
one of your outbound-capable numbers, inside a daily calling window in
the timezone you pick. The runner respects a per-campaign concurrency
cap and retries contacts whose calls ended in outcomes you selected
(no_answer, voicemail, failed, …) with a configurable backoff,
up to max_attempts per contact.
Lifecycle: draft → (scheduled →) running → completed, with
paused and cancelled reachable via actions.
Contacts are only editable while the campaign is a draft.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v1/campaigns | List campaigns |
POST | /v1/campaigns | Create a campaign (starts in draft) |
GET | /v1/campaigns/{campaign_id} | Retrieve a campaign incl. contacts |
PATCH | /v1/campaigns/{campaign_id} | Update a draft/scheduled/paused campaign |
DELETE | /v1/campaigns/{campaign_id} | Delete a campaign |
POST | /v1/campaigns/{campaign_id}/contacts | Add contacts (CSV upload or JSON) |
GET | /v1/campaigns/{campaign_id}/stats | Progress counters + recent calls |
POST | /v1/campaigns/{campaign_id}/{action} | start, pause, or cancel |
{campaign_id} is the campaign's UUID (public_id).
Campaign object
{
"public_id": "3f6b2c9e-…",
"name": "May win-back",
"agent": 12,
"agent_name": "Customer Support Agent",
"from_number": "+15551234567",
"status": "draft",
"timezone": "America/Los_Angeles",
"daily_window_start": "09:00:00",
"daily_window_end": "17:00:00",
"start_at": null,
"end_at": null,
"max_concurrent_calls": 3,
"max_attempts": 2,
"retry_backoff_minutes": 60,
"retry_on": ["no_answer", "voicemail", "failed"],
"total_count": 0,
"completed_count": 0,
"failed_count": 0,
"in_progress_count": 0,
"created_at": "2026-04-20T18:24:10.113Z",
"updated_at": "2026-04-20T18:24:10.113Z"
}| Field | Type | Description |
|---|---|---|
public_id | UUID | Campaign id (used in every path) |
name | string | 1–255 chars |
agent | integer | The Agent that speaks on every call (writable) |
agent_name | string | Read-only convenience |
from_number | string | An outbound-capable number in your org. Demo numbers are rejected |
status | string | draft, scheduled, running, paused, completed, cancelled (read-only — change via actions) |
timezone | string | IANA timezone the daily window is evaluated in. Default UTC |
daily_window_start, daily_window_end | time | Local calling window, e.g. "09:00:00"–"17:00:00" |
start_at, end_at | timestamp | null | Optional overall schedule bounds. end_at must be after start_at |
max_concurrent_calls | integer | 1–10, default 3 |
max_attempts | integer | ≥ 1, default 2 — total tries per contact |
retry_backoff_minutes | integer | Delay before a retryable contact is attempted again. Default 60 |
retry_on | array of string | Call outcomes that trigger a retry. Default ["no_answer", "voicemail", "failed"] |
total_count, completed_count, failed_count, in_progress_count | integer | Progress counters (read-only) |
Create a campaign
curl -X POST https://api.thunderphone.com/v1/campaigns \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "May win-back",
"agent": 12,
"from_number": "+15551234567",
"timezone": "America/Los_Angeles",
"daily_window_start": "09:00",
"daily_window_end": "17:00",
"max_concurrent_calls": 3,
"max_attempts": 2,
"retry_on": ["no_answer", "voicemail"]
}'Required: name, agent, from_number, daily_window_start,
daily_window_end. Returns 201 Created with the campaign in
status="draft". Validation errors (400) cover unknown agents,
numbers not registered to your org, demo numbers, invalid IANA
timezones, and end_at <= start_at.
Retrieve / update / delete
GET /v1/campaigns/{id} returns the campaign plus a contacts
array (first 500 contact objects; filter with
?contact_status=pending|scheduled|calling|completed|failed|exhausted).
PATCH accepts any subset of the writable fields, but only while
status is draft, scheduled, or paused — otherwise
409 Conflict.
DELETE returns 204 No Content and removes the campaign and its
contacts (already-made call logs are kept).
Add contacts
Only allowed while the campaign is a draft (409 otherwise).
Two formats:
curl -X POST https://api.thunderphone.com/v1/campaigns/3f6b2c9e-…/contacts \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-F 'file=@contacts.csv'curl -X POST https://api.thunderphone.com/v1/campaigns/3f6b2c9e-…/contacts \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{ "phone_number": "+14155550199", "first_name": "Jamie", "account_id": "A-1042" }
]
}'- CSV: UTF-8, must have a
phoneorphone_numbercolumn. Every other column becomes a per-contact variable. - JSON: a top-level list, or
{"contacts": [...]}— each object needsphone_number(orphone); all other keys become variables. - Max 5,000 rows per request. Numbers are normalized to E.164 (8–15 digits); invalid rows are rejected individually, not the whole batch.
{
"accepted": 148,
"rejected": 2,
"errors": [
{ "row": 17, "error": "Enter a valid E.164 phone number (8-15 digits)." }
]
}Contact object
{id, phone_number, variables, status, attempts, next_attempt_at, last_outcome, call_id, created_at, updated_at}
— status is pending, scheduled, calling, completed,
failed, or exhausted (retries used up); call_id links the most
recent call.
Campaign actions
POST /v1/campaigns/{id}/{action} where action is:
| Action | Body | Effect |
|---|---|---|
start | {"consent_to_charge": true} (required) | draft/paused/scheduled/cancelled → running (or scheduled when start_at is in the future). Requires at least one contact and the org's outbound TCPA confirmation |
pause | — | running/scheduled → paused |
cancel | — | Any non-terminal status → cancelled |
Returns 200 OK with the updated campaign. Invalid transitions return
409; unknown actions return 404.
Stats
curl https://api.thunderphone.com/v1/campaigns/3f6b2c9e-…/stats \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"{
"total": 150,
"completed": 41,
"failed": 3,
"in_progress": 2,
"by_status": { "pending": 96, "scheduled": 8, "calling": 2, "completed": 41, "failed": 3 },
"recent_calls": [
{
"contact_id": 9911,
"phone_number": "+14155550199",
"call_id": 987654321,
"status": "completed",
"outcome": "completed",
"started_at": "2026-04-20T18:24:10.113Z"
}
]
}recent_calls holds the 20 most recently updated contacts that have a
call attached.