Skip to content
MeridFlow AiFlow v8.x • self-hosted

Campaigns

Batch, paced outbound calling: upload a contact list once, and AiFlow dials through it at a configured pace, instead of the strictly one-call-per-matched-event flow that Triggers drive. Each contact becomes exactly one Call once dialed, and from there that Call's own status tracks the actual call.

Endpoint family

GET  /api/v1/agents/{agent_id}/campaigns
POST /api/v1/agents/{agent_id}/campaigns
GET  /api/v1/agents/{agent_id}/campaigns/{campaign_id}
POST /api/v1/agents/{agent_id}/campaigns/{campaign_id}/contacts
GET  /api/v1/agents/{agent_id}/campaigns/{campaign_id}/progress
POST /api/v1/agents/{agent_id}/campaigns/{campaign_id}/start
POST /api/v1/agents/{agent_id}/campaigns/{campaign_id}/pause
POST /api/v1/agents/{agent_id}/campaigns/{campaign_id}/cancel

Authentication and roles

Every endpoint takes an admin session token (see Authentication). Reading (GET) is available to every role, including Viewer. Every mutating action requires Owner, Admin, or Editor.

Lifecycle

stateDiagram-v2
    [*] --> draft
    draft --> running: start
    running --> paused: pause
    paused --> running: start
    running --> completed: every contact dispatched
    draft --> cancelled: cancel
    running --> cancelled: cancel
    paused --> cancelled: cancel
    completed --> [*]
    cancelled --> [*]

A campaign starts as draft: create it, then upload one or more contact CSVs (uploading again just appends more pending contacts, even onto a running campaign). start begins dispatching.

One shared background tick paces every running campaign, rather than a worker per campaign. It creates one call at a time, spaced by pacing_per_minute, and hands each to the same durable queue used by manual calls and Trigger-placed calls. Per-agent concurrency limits and retry behaviour are therefore unchanged.

If Twilio Lookup screening is on, each number is checked just before dialling. A number that comes back invalid, or on a blocked line type, is marked failed and never dialled.

pause stops new dispatches and leaves calls already placed alone. cancel stops dispatching and marks every still-pending contact cancelled. A campaign that runs out of pending contacts becomes completed on its own.

Create a campaign

POST /api/v1/agents/{agent_id}/campaigns
Field Type Required Description
name string Yes 1-120 characters.
pacing_per_minute integer No Contacts dispatched per minute, 1-60. Defaults to 10.
curl -X POST https://api.your-domain.com/api/v1/agents/1/campaigns \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{"name": "Q1 lapsed customers", "pacing_per_minute": 5}'
{
  "id": 7,
  "agent_id": 1,
  "name": "Q1 lapsed customers",
  "status": "draft",
  "pacing_per_minute": 5,
  "created_at": "2026-01-15T10:00:00Z"
}

Upload contacts

POST /api/v1/agents/{agent_id}/campaigns/{campaign_id}/contacts

A multipart file upload, multipart/form-data with a file field: a CSV with a required phone_number column and an optional context column, the per-contact equivalent of a manual call's context, used verbatim in the agent's dynamic context with no template rendering. Blank phone_number rows are skipped. Rejected with 422 if the campaign is already completed or cancelled, or the file has no phone_number column.

phone_number,context
+15557654321,"Ordered item #4821, still in cart"
+15557654322,
curl -X POST https://api.your-domain.com/api/v1/agents/1/campaigns/7/contacts \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -F "file=@contacts.csv"
{ "contacts_added": 2 }

Check progress

GET /api/v1/agents/{agent_id}/campaigns/{campaign_id}/progress
{
  "total": 200,
  "pending": 140,
  "dispatched": 55,
  "cancelled": 5,
  "calls_completed": 40,
  "calls_failed": 10,
  "calls_in_flight": 5
}

dispatched counts every contact a Call row has been created for, regardless of how that call turned out. calls_completed, calls_failed, and calls_in_flight break that number down further by the linked Call's own current status.

Start, pause, cancel

POST /api/v1/agents/{agent_id}/campaigns/{campaign_id}/start
POST /api/v1/agents/{agent_id}/campaigns/{campaign_id}/pause
POST /api/v1/agents/{agent_id}/campaigns/{campaign_id}/cancel

Each returns the updated Campaign. start returns 422 if the agent has no twilio_phone_number configured, there are no pending contacts, or the campaign isn't currently draft or paused. pause returns 422 unless the campaign is running. cancel returns 422 if the campaign is already completed or cancelled.

curl -X POST https://api.your-domain.com/api/v1/agents/1/campaigns/7/start \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Next

  • Calls: what each dispatched contact becomes.
  • Triggers: the event-driven alternative to a campaign's upload-a-list-and-go flow.