sid that keys your recording and analytics.
Everything below runs on the Voice SDK. The
control plane is request/response, so the dialer is just an originate +
outcome-poll loop wrapped in two limiters. The conversation itself is driven
server-side by the agent — your process never touches audio.
Architecture at a glance
max_concurrent means simultaneous live calls, exactly
like a predictive dialer. Pacing and the AI leg are independent: the agent, its
providers, and its transfer tool are configured server-side (see
Runtime overview); the dialer only decides
who to call and how fast.
Step 1 — the qualifying agent
The dialer attaches an agent by id; the agent’s brain (speech-to-speech or cascaded ASR→LLM→TTS), its system prompt, and its tools are configured once in the console or via the control plane — not inline inoriginate. Give the agent
a telephony transfer tool so it can route a qualified lead to your human
closer queue itself (the recommended “hot lead” path — see
Step 4).
Agents are attached by id —
originate({ agent: "outbound-sales" }) and
agents.attach(sid, "outbound-sales") both take a string. Provider keys,
prompt, turn-detection, and the transfer tool live in the agent’s server-side
config. Build the agent first: BYO speech-to-speech,
tool calling.Step 2 — the paced dialer
The dialer combines two limiters over the plaincalls.originate call:
- a calls-per-second gate so you never originate faster than the trunk / carrier allows, and
- a max-concurrency semaphore so no more than N calls are live at once.
calls.get
until it reaches a terminal state; the slot is released only then.
- TypeScript
- Python
Step 3 — no-answer, busy, and voicemail
Every attempt lands in exactly one terminalCallStatus, which is how the dialer
classifies the outcome:
status | Meaning | Typical disposition |
|---|---|---|
in_progress | Callee answered; the agent is talking | live — keep the slot |
completed | Call answered then hung up normally | answered → CRM outcome |
no_answer | Rang out / ring-timeout / declined | retry later |
failed | Busy, rejected, unallocated, carrier error | inspect, maybe suppress |
calls.get as the dialer does above, or — if you’d rather not poll —
consume call-lifecycle events
(initiated → ring → established/cleared, each with a Q.850 cause) and drive the
same state machine off the stream.
Step 4 — transfer hot leads to a human
When the agent qualifies a lead, hand the live call to a human closer. Thesid is preserved across the transfer, so the recording, transcript, and
analytics stay on one timeline.
Recommended: let the agent route to a closer queue
The cleanest path is a warm handoff driven by the conversation: configure the agent with a telephony transfer tool so it routes the qualified call into a human skill queue on the ACD the moment it decides the lead is hot. Your dialer process stays out of the audio path entirely.Manual: transfer from your own logic
If your process decides when to escalate (for example off a scoring webhook), usecall.transfer directly. Pass a PSTN number to blind-transfer the audio to a
closer’s phone (a SIP REFER under the hood), or an agent id to re-attach the
call to a different AI agent in place.
- TypeScript
- Python
transfer needs exactly one of to (PSTN, via REFER) or agent
(re-attach). A blind PSTN transfer forwards the audio but carries no lead
context — for a true warm handoff with a screen-pop, prefer the
route-to-skill path above.Scaling to very large lists
The dialer above paces in your process, which is ideal when each lead needs custom logic (CRM lookups, per-lead scripts, retry policy). For fire-and-forget lists in the tens of thousands, the platform also ships a server-side campaign dialer: you hand the control plane the whole number list plus a template (trunk, agent, caller-id, CPS, max-concurrent) and the engine owns the pacing — no long-running loop on your side. Reach it from the console’s campaign screen or the control-plane API. Use the SDK pacer here when you need programmatic control; use the built-in campaign dialer when you just need throughput.Next steps
Outbound calls
The
originate reference — every param, agent attach, and outcome polling.AI → human handoff
How the warm transfer to a human closer works end to end.
Tool calling
Give the agent a route-to-skill tool so it escalates hot leads itself.
Turn detection
Idle watchdog and barge-in — your voicemail and dead-air backstop.
Call lifecycle
Consume events instead of polling to drive the outcome state machine.
Support handoff recipe
The sibling recipe: routing a qualified call into a human queue.