Campaigns API

Create and run outbound campaigns — define lists (stages), enrol contacts, and manage the approval queue. Reads need campaigns.read, writes campaigns.write, and deciding an approval needs campaigns.approve.

List & manage campaigns

GET    /api/v1/rest/campaigns          ?status=
POST   /api/v1/rest/campaigns
GET    /api/v1/rest/campaigns/:id
PATCH  /api/v1/rest/campaigns/:id
DELETE /api/v1/rest/campaigns/:id

Set status to active to launch a campaign or paused to halt it. max_call_duration_seconds caps how long any outbound call may run. Campaigns run in one of two modes: "mode": "rules" (your stages drive everything) or "mode": "agentic" — give the agent an objectiveand it decides each contact's next touch, bounded by agentic_max_touches and (optionally) agentic_require_approval. Send-window and daily-cap fields (timezone, send_window_start/end, daily_cap_phone/sms/email) are settable on create and PATCH. service_booking_window_days sets a minimum lead time, in calendar days, for service appointments booked on the campaign — with 3, the agent cannot offer or write an appointment sooner than three days out, and slots inside the gap are removed from the availability it sees. service_booking_weekdays (PATCH) limits those appointments to certain weekdays, 0 = Sunday to 6 = Saturday — [1,2,3,4,5] books Monday to Friday only, even where the shop opens on Saturday; null allows every day. Trigger an immediate pass with POST /api/v1/rest/campaigns/:id/run, and inspect the schedule with GET /api/v1/rest/campaigns/:id/next-run.

Lists

GET    /api/v1/rest/campaigns/:id/lists
POST   /api/v1/rest/campaigns/:id/lists

A list is a stage in the campaign. Wire it to an agent and a channel (phone, sms, email); with trigger_on_enter set, entering the list enqueues outbound automatically.

Contacts

GET    /api/v1/rest/campaigns/:id/contacts       ?list_id= &status=
POST   /api/v1/rest/campaigns/:id/contacts       enrol an EXISTING contact
POST   /api/v1/rest/campaigns/:id/contacts/add   create + enrol in one call
POST   /api/v1/rest/campaigns/:id/contacts/bulk  up to 1000 rows

/contacts enrols a contact you already have — create it first via the Contacts API. Most integrations want /contacts/add instead, which takes a phone or email and creates the underlying contact if the workspace has no match, so pushing a new lead is one request./contacts/bulk takes the same shape as rows for an import.

Both /contacts/add and /contacts/bulk accept two fields worth knowing about:

{
  "phone": "+16135550142",
  "first_name": "Dale",
  "facts": { "trade_in_vehicle": "2019 F-150", "source": "autoverify" },
  "do_not_call": false
}

facts is a free-form bag of per-contact attributes. The agent reads them on the call, and email and SMS templates merge them as {{facts.<key>}} — with a fallback as {{facts.trade_in_vehicle|your vehicle}}. This is how a campaign is personalised; a contact added without facts is one the agent knows nothing about.

do_not_call marks the contact suppressed, and it is the real suppression flag the call queue checks — not a fact. A fact named do_not_call suppresses nothing. It is set-only: true suppresses, and false never clears an existing suppression, so re-sending a stale list cannot un-suppress someone who opted out.

Approvals

GET    /api/v1/rest/campaigns/:id/approvals  ?status=pending
POST   /api/v1/rest/campaigns/:id/approvals

For lists that require sign-off, outbound messages wait in the approval queue. POST { approval_id, decision } with decision of approved or rejected — the decision is final.

Why a campaign did (or did not) do something

GET    /api/v1/rest/campaigns/:id/events      audit trail
GET    /api/v1/rest/campaigns/:id/decisions   agentic reasoning log
GET    /api/v1/rest/campaigns/:id/next-run    due now, next run, cap usage
POST   /api/v1/rest/campaigns/:id/lists/:listId/trigger

/events records every action a campaign took and every evaluation that decided to do nothing, which is what answers “why has this contact not advanced”. For agentic campaigns, /decisionsadds the planner’s own reasoning per tick — what it concluded about the lead and whether it chose to act, wait, or finish.

When nothing is going out, /next-run tells you whether the campaign is simply capped: it reports caps.phone/sms/email with cap, sent, remaining and capped for today, on the campaign’s own date. A cap of null means unlimited, not spent.

/lists/:listId/triggerforces a single stage’s schedule conditions to evaluate now and dispatches whatever they moved — narrower than /run, which evaluates the whole campaign.

Live outbound calls are covered by the Campaign calls API. The wider campaign surface — bulk contact import, CRM contact sources with auto-sync, write-back rules, webhook trigger tokens, templates, and AI campaign generation — is fully available too; every endpoint is in the OpenAPI reference.