Campaigns
A campaign dials a list of contacts with one agent. You add contacts, start it, and Vocobase works through the list at the concurrency you set — retrying the ones that do not connect and reporting progress as it goes.How it works
1
Create the campaign
Pick the agent and the caller IDs to dial from. The campaign starts in
draft and nothing dials yet.2
Add contacts
Up to 10,000 per request. Numbers are canonicalised, so the same number written two ways
becomes one contact.
3
Start it
Vocobase dials up to
concurrency_limit contacts at once — clamped by your account’s own
concurrency limit — and retries failures on your schedule, inside the dialling window.4
Watch it
Poll
GET /campaigns/{id}/stats, or let the ordinary session.completed webhook deliver each
call’s transcript and extraction as it finishes.Create a campaign
draft, with a campaign_id you use for everything below.
Choosing caller IDs
There are two ways to say which number the campaign dials from, and they are mutually exclusive:
Sending both fails unless they agree. Each number must be active, owned by you, and voice-capable —
this is re-checked when the campaign starts, so a campaign whose numbers were deactivated while it
sat in draft refuses to start rather than dispatching doomed calls.
tata_smartflo is rejected on campaigns. Smartflo does not support streaming voice bots on
Broadcast/Dialer campaigns yet — only 1:1 click-to-call and inbound DIDs.Retries
retry_count is how many times a contact is re-dialled after the first attempt. The wait between
attempts comes from one of two fields:
cooldown_minutes— the same wait every time.retry_delays_minutes— a per-attempt list, e.g.[30, 240]waits 30 minutes before the second attempt and 4 hours before the third. It is consulted per attempt: once the list runs out, later attempts fall back tocooldown_minutes. Max 20 entries, max 10080 minutes each.
retry_count accepts up to 20. retry_concurrency reserves slots for retries rather than
capping them — it stops a backlog of retries from starving fresh contacts, and retries may still
borrow the whole pool once fresh contacts are exhausted. It is inert at its default of 0.
Add contacts
metadatareaches the bot, but not as a template variable. It arrives undercampaign_context.contact_metadata, not as a pre-call variable — writing{{plan}}in the prompt renders an empty string. Prompt variables come from the campaign’s column-to-variable map, which the v2 API does not expose, sovariablesis empty on API-created campaigns andmetadatadoes not appear on thesession.completedpayload.- Contacts are reused, not duplicated. A number already in your contact library is linked
rather than re-created — that is the split between
contacts_createdandcontacts_existing. duplicatesmeans already on this campaign. Those rows are skipped, not re-queued.- Adding to a finished campaign re-opens it to
paused, notrunning. Acompletedcampaign moves back topausedandcampaign_reopenedistrue. Dialling has not resumed — you must callPOST /campaigns/{id}/resumeyourself, which is what re-validates caller IDs and provider access before any new call goes out. This is how you top up a campaign instead of creating a new one. - A stopped campaign can never be topped up.
/stopis terminal and is keyed on the stop itself, not the status, so a stopped campaign that later readscompletedstill returns400 INVALID_STATE.
Run it
Each returns the updated campaign. Calling one from the wrong status returns
400 INVALID_STATE.
Pausing lets in-flight calls finish and dispatches no new ones. Stopping is terminal — every
still-queued contact is marked skipped and the campaign cannot be resumed.
Both start and resume also accept an optional body to shift concurrency mid-campaign:
concurrency_limit is clamped to 1–10; retry_concurrency must be between 0 and the resulting
concurrency_limit. Omit the body to keep the campaign’s current values.
The dialling window
Campaigns only dial between 09:00 and 20:59 in your account’s timezone. The window is fixed — there is no per-campaign override and no exemption, including for promised callbacks. A campaign started outside the window reachesrunning and simply dials nothing until 09:00.
Retries are rolled forward into the window when they are booked, and held again at dispatch time,
so cooldown_minutes and retry_delays_minutes are a minimum wait, never an exact one — a retry
due at 22:00 goes out the next morning.
Track progress
total_contacts. dispositions
does not: it breaks down only the calls that reached an outcome.
screener_encountered cuts across dispositions rather than partitioning them. A call can be both
completed and screener-encountered, so do not add it to the disposition counts.One contact’s history
GET /campaigns/{id}/contacts/{contactId}/journey returns every touch for a single contact,
oldest first — each outbound attempt with its disposition and cost, plus any inbound call they made
back. Use it to answer “what actually happened to this lead” without stitching sessions together
yourself.
Callbacks
By default, calls ask the lead for a better time if they cannot talk now, and the campaign re-queues them for it.callbacks_pending in the stats is how many are waiting.
Set callback_enabled: false at creation to turn this off. It is an opt-out, not an opt-in.
Campaign-wide extraction
Attach an extraction set to the campaign and its keys apply to every call the campaign makes, on top of whatever the agent already extracts:409 EXTRACTION_KEY_CLASH if a key in the set collides with one the agent already
produces — two sources writing the same field would silently shadow each other.
Next steps
Pre-call Variables
Use contact
metadata inside the prompt.Webhook Payloads
Receive each call’s transcript and result.
Credits & Billing
What a campaign costs to run.
API Reference
Every campaign field and error code.