Overview
A run is one agent dialling a list of contacts. It is the batch primitive: instead of creating a conversation per person and tracking hundreds of ids, you launch a run and read one aggregate. A run does not replace the conversation, it batches it. Launching mints one conversation per contact, and the call attempts hang off that conversation exactly as they do for a conversation you created on its own. So everything the Conversations guide says about transcripts, analysis, analytics and webhooks holds for a conversation a run produced. What the run adds is a lifecycle and a roll-up.Building and running one
Three requests, and none of them can call anybody twice:pause, resume and cancel drive it, and GET /runs/{id} reports where it has got to.
Create takes no contacts, on purpose. If it did, a request that timed out after the server
committed would leave you retrying into a second draft holding the same list — dialling
nothing, but sitting there launchable. A retried create leaves an empty draft instead, which
holds nothing anyone can launch by mistake. It is also why this API has no
Idempotency-Key
header: the one endpoint that would have needed one does not, once create is empty.Resource Structure
scheduledAt is when a SCHEDULED run starts dialling, null on one that launched immediately. scheduleError is why a scheduled run did not start at its time — see Starting later.
The run carries no caller ID, retry policy or call window. Those are the agent’s, and the agent is the single source of them.
Required Scopes
The run scopes are new and have no legacy aliases — runs had no public surface before, so there is nothing to stay compatible with. Keys that already existed were granted all three, and new keys are issued with all three, so there is nothing to add.
delete:runs is separate from write:runs deliberately: a key that may build and drive a run need not also be able to remove it.
The conversations drill-down needs read:conversations as well because its rows are conversation resources, with the same analysis and analytics GET /conversations returns. read:interviews satisfies that half, as it does everywhere else.
Progress
Every conversation the run has produced lands in exactly one bucket, so the six counts sum tototal:
Two of them are worth reading carefully.
inProgress is not derived from the conversation’s stored status. Nothing flips that row while a call is being placed and ringing, so a contact whose phone is ringing right now would otherwise report as queued. Such a call is moved out of queued rather than added on top, which is why the buckets still sum to total.
unreachable is its own bucket rather than part of failed because nothing malfunctioned — the contact simply never picked up within the retry budget. Folding the two together would also mean an entirely unreachable run never reached 100%.
completion is the fraction of conversations in a terminal state (completed + failed + cancelled + unreachable), between 0 and 1.
What a launch actually promises
A2xx from launch means the conversations are queued, not dialled. The provider is called later by the dispatch worker, outside your request, so:
- per-contact outcomes never come back in the response. They arrive as
conversation.*webhooks, each carryingrunId - the batch is all-or-nothing. The whole contact list is admitted against your billing before any of it is dispatched; a run you cannot afford is refused whole with a
402, nothing is queued, and the run stays a draft. Partial admission was rejected because it turns one request into an outcome nobody can predict or undo — you cannot un-call the contacts who already went out
Starting later
POST /runs/{id}/launch with a scheduledAt books the run instead of dispatching it: it moves to SCHEDULED, queues nothing, and starts on its own at that time — up to 30 days ahead, the same ceiling a single conversation’s scheduleTime has always had.
It fires through the same code an immediate launch runs, so a run that starts on Monday morning is indistinguishable from one launched by hand on Monday morning. It may begin up to a minute late.
Until it fires it is still an ordinary draft: attach more contacts, or cancel it and the appointment goes with it. Contacts attached in the meantime are held rather than dialled — a run booked for Monday has not started, so a contact added on Saturday waits and goes out with the rest.
Lifecycle
PAUSED. Cancelling a scheduled run clears its appointment as well as setting the status, so it never starts.
CANCELLED is terminal. PAUSED is reversible, and it is a real stop:
- Nothing further is dialled, including calls already queued when you paused. They are withdrawn from the dispatch queue, and anything the queue had already picked up is refused at the call itself.
- The work is held, not discarded. The conversations stay in place and
resumeputs back exactly what pause held — a contact already reached during the running window is not called again, and none of the waiting ones are lost. - A contact attached to a paused run is not called either. The conversation is created, the run stays paused, and they go out with everything else on resume.
CANCELLED differs on the last two: it closes the un-dialled conversations out, and there is no way back. A call already connected when you cancel is left to finish; its retry is refused rather than rescheduled.
Correlating a Conversation Back to Its Run
Every conversation carries a nullablerunId:
null for a conversation created on its own, and the run’s id for one a run produced.
The webhook payloads carry it too, beside the ids they already send, so a conversation.completed handler attributes the call to its batch without reading the conversation back at all:
runId does not depend on the webhook’s vocabulary — it is always present, and null where there is no run.
Monitoring a Run
conversation.* events, and runId in the payload attributes it to the run without a request of your own.
Company Isolation
You can only read runs belonging to your own API key’s company. A run in another company returns404 Not Found, the same as one that does not exist — the API does not confirm that a resource you cannot read exists.
Related Resources
Get Run
Status and aggregate progress
List Run Conversations
The per-contact drill-down
Conversations
What each conversation in a run carries
Agents
The agent a run dials with