Campaigns
A journey run over a list of contacts, paced against your own concurrency, pausable while it runs, and reportable while it is still going. This is the answer to "I have four thousand numbers".
The shape of it
create ──▶ upload entries ──▶ start ──┬──▶ pause ──▶ resume
│
├──▶ cancel
│
└──▶ report (readable throughout)1 · Create it
/v1/journey_campaignscurl -s -X POST https://voice.sphoro.com/v1/journey_campaigns \
-H "Authorization: Bearer $SPHORO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"journey_id": "'"$JOURNEY_ID"'",
"name": "September collections"
}'{
"id": "cmp_5d81a3f70b2e94c6",
"journey_id": "jny_2c4e…",
"name": "September collections",
"status": "draft",
"entry_count": 0
}A campaign is pinned to the journey's published version when it starts. Publishing a new version mid-campaign does not change what the running one does — which is what you want, and is why publishing during a campaign is safe.
2 · Load the contacts
/v1/journey_campaigns/{id}/entriescurl -s -X POST https://voice.sphoro.com/v1/journey_campaigns/$CAMPAIGN_ID/entries \
-H "Authorization: Bearer $SPHORO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entries": [
{"contact": {"phone": "+919876543210", "email": "priya@example.com"},
"variables": {"first_name": "Priya", "amount_due": 8400}},
{"contact": {"phone": "+919812345678"},
"variables": {"first_name": "Arun", "amount_due": 2200}}
]
}'Every variable the journey's agents need must be here. A contact missing one is refused at upload rather than failing when it is dialled three days later — see variables.
3 · Start, and control it while it runs
/v1/journey_campaigns/{id}/start/v1/journey_campaigns/{id}/pause/v1/journey_campaigns/{id}/resume/v1/journey_campaigns/{id}/cancelcurl -s -X POST https://voice.sphoro.com/v1/journey_campaigns/$CAMPAIGN_ID/start \
-H "Authorization: Bearer $SPHORO_API_KEY"| Action | Does |
|---|---|
| Pause | Stops starting new contacts, and holds every contact already walking the graph on the node they are standing on. Calls already connected finish normally. |
| Resume | Starts new contacts again, and releases the held ones within about thirty seconds, from where each left off. |
| Cancel | Stops for good. Executions in flight are ended. |
4 · Read the report while it runs
/v1/journey_campaigns/{id}/reportcurl -s https://voice.sphoro.com/v1/journey_campaigns/$CAMPAIGN_ID/report \
-H "Authorization: Bearer $SPHORO_API_KEY"{
"campaign_id": "cmp_5d81a3f70b2e94c6",
"status": "running",
"entries": { "total": 4000, "started": 1240, "running": 86, "finished": 1154 },
"outcomes": { "converted": 402, "unreachable": 611, "opted_out": 12, "stuck": 3, "expired": 0 },
"nodes": {
"remind": { "entered": 1240, "left": 1154 },
"hold": { "entered": 700, "left": 700 },
"sms": { "entered": 611, "left": 611 },
"arrange": { "entered": 402, "left": 402 }
}
}The nodes block is the funnel: how many contacts entered each node and how many
left. It is where a badly-drawn graph shows up — a node with far fewer leaving than entering has
contacts standing on it, and stuck in the outcomes says some of them ran out of
cases.
Pacing
A campaign paces its own dialling against your account's concurrency ceiling. It does not fire every contact at once, which is the mistake a hand-rolled loop makes — see concurrency for what that costs.
Two things still gate every individual call, and they are the reason a campaign's numbers never quite match its entry count:
- Compliance. Suppressed numbers, missing consent and out-of-hours calls are refused before dialling. See compliance.
- Calling windows. A
waitnode with a window holds the contact until the window opens where they are.
Listing them
/v1/journey_campaigns/v1/journey_campaigns/{id}curl -s https://voice.sphoro.com/v1/journey_campaigns \
-H "Authorization: Bearer $SPHORO_API_KEY"Before your first real campaign
- Run the journey on yourself. One execution,
POST /v1/journey_executions, your own number. You will find something. - Run it on ten colleagues. A campaign of ten, started and watched to completion.
- Check the suppression list is loaded, and that consent is where it needs to be. See compliance.
- Then load four thousand — and keep the pause endpoint to hand.