Skip to main content

Dialer API

The Dialer API runs outbound calling campaigns: preview, progressive, and predictive modes for human agents, plus agentless voice broadcast and AI-voice-agent dispatch. You create a campaign, attach contact lists, then either let the pacing engine dial (progressive / predictive) or pull the next contact yourself (preview). Live statistics, local-presence caller-ID coverage, and per-attempt dispositions round out the surface. Base path: /api/v1/dialer Authentication: API key (X-API-Key: dv_live_sk_…) or a Clerk session (Authorization: Bearer <token>). Scopes: read endpoints require dialer:read; create / update / delete endpoints require dialer:write. Response envelope: every endpoint returns { "data": …, "meta": { "request_id", "timestamp" } }. Errors return { "error": { "code", "message" } } (validation failures also carry error.issues). Request samples: the first ten operations below ship the six-language sample group (cURL, Node.js, Python, Go, Ruby, PHP — every operation up to POST /campaigns/{id}/dispositions); every later operation, and every read endpoint that only returns JSON, ships the cURL + TypeScript pair the sample-writing policy guarantees. Hero flows past the cut keep the full group inside Worked sequences below.
Outbound calls placed by the dialer egress only through Orbit’s wholesale voice path. The caller ID you set on a campaign must be a number your account owns (or one you’ve verified) — a foreign number is rejected with 422 UNVERIFIED_CALLER_ID on both create and update.

Using the SDKs

The Node SDK is the only client with typed dialer helpers today (the SDK index documents every other client as core-scope). Pick the preview claim with orbit.dialer.getNextCall() and dial it with orbit.dialer.dial() — worked call sequences for Python and Go (documented client.request / client.Request escape hatch) come later on this page under SDK call flows.
Returns the typed ApiResponse envelope. See the SDK index at SDK quickstart.

Campaign modes

dispatch_mode is a second axis. The default human fronts the call with a live operator. voice_agent connects each answered leg to the AI voice agent named by voice_agent_id; those campaigns are paced by POST /campaigns/:id/batch-dispatch instead of the human-agent scheduler.

Create a campaign

POST /api/v1/dialer/campaigns
Scope: dialer:write. Created in draft status — activate it later with PATCH /campaigns/:id.
string
required
Human-readable campaign name. 1–200 characters.
string
required
One of preview, progressive, predictive, agentless.
string
required
Caller ID presented on each dial, in E.164 format. Must be a number your account owns.
number
default:"1.2"
Predictive over-dial ratio. 0.8–3.0.
number
default:"0.03"
Rolling abandon-rate ceiling. Greater than 0 and at most 0.03 (the FCC 3% cap). See FCC abandon-rate ceiling.
number
default:"20"
Predictive pacing target — seconds to answer. 5–120.
string
default:"human"
human or voice_agent. voice_agent requires voice_agent_id.
string
AI voice agent that answers each leg when dispatch_mode='voice_agent'. Must reference an existing, non-archived voice agent in your workspace.
number
default:"10"
Maximum simultaneous in-flight legs a single batch-dispatch invocation may originate. 1–200.
array
default:"[]"
Skills an agent must hold to take this campaign’s calls. Accepts a string array (["billing","french"]) or per-skill objects ([{ "skill": "billing", "min_level": 3, "weight": 2 }]).
array
default:"[]"
Rotation pool of E.164 numbers for local-presence dialing. Every entry must be a number your account owns. Required (non-empty) when caller_id_strategy is not single.
string
default:"single"
single (always caller_id_e164), npa_match (present the pool number matching the lead’s area code), or round_robin.
string
HTTPS audio URL played on answer in agentless mode. Required for agentless; rejected for every other mode.
string
HTTPS audio URL dropped when answering-machine detection fires on an agent-driven dial. Not valid for agentless mode.
number
Per-campaign preview countdown the agent has to accept a contact. Only valid for preview mode; omit to use the platform default.
boolean
default:"false"
Record calls on this campaign. Requires recording_consent_acknowledged: true in the same request.
Affirms you have authority to record and the two-party-consent disclaimer will play. Required when recording_enabled is true. Internal API-key callers cannot acknowledge on behalf of an operator (401).
boolean
default:"false"
Request carrier answering-machine detection on each dial.
string
Voice application (UUID) bound to answered legs.
object
default:"{}"
Per-outcome retry backoff keyed by outcome bucket (no_answer, busy, machine, machine_silence, failed). Each entry sets retry delays (60–86400s) and max_attempts (0–10). Empty {} keeps the platform’s uniform retry schedule.
object
Human-agent guided-dialog script (ordered sections plus per-disposition branches). Omit to store the canonical empty script { "version": 1, "sections": [], "branches": {} }.
object
default:"{}"
Arbitrary key-value pairs stored on the campaign.
max_abandon_rate is capped at 0.03 (3%) per the FCC Telemarketing Sales Rule — a create that sends a higher value fails validation with 422 VALIDATION_ERROR. See FCC abandon-rate ceiling.
201 Created
Errors

List campaigns

GET /api/v1/dialer/campaigns
Scope: dialer:read. Cursor-paginated, newest first. Soft-deleted campaigns are excluded. Query parameters Each row carries a bounded contacts_total / contacts_connected rollup. When a campaign’s contact count exceeds the scan cap, contacts_count_capped is true and the totals are a floor; the envelope’s contacts_count_capped_at reports where the count stops being exact.
On a workspace that has never created a campaign — or one provisioned before the dialer shipped — this endpoint returns an empty list (items: []) rather than an error, so an empty-state UI renders cleanly.

Campaign stats summary

GET /api/v1/dialer/campaigns/stats
Scope: dialer:read. Totals across every non-deleted campaign — the source of truth for a dashboard KPI strip, independent of pagination. Query parameters
avg_abandon_rate is null when no active campaign carries a 30-day rate.

Get a campaign

GET /api/v1/dialer/campaigns/{id}
Scope: dialer:read. Returns the full campaign row; campaign_script is always the canonical shape.
Errors

Update a campaign

PATCH /api/v1/dialer/campaigns/{id}
Scope: dialer:write. Partial update — only the fields you send change. Accepts the same fields as create, plus status.
string
active, paused, completed, or aborted. aborted is the operator-initiated terminal state — once aborted, a campaign cannot be re-activated. Transitioning to active is blocked while your workspace’s compliance emergency-stop is engaged.
Nullable PATCH fields: voice_agent_id, voicemail_message_url, preview_decision_seconds, and call_script_id accept null to clear them. Sending caller_id_e164 or any caller_id_pool entry re-runs the ownership check. Sending retry_policy or campaign_script replaces the whole object.
Errors

Soft-delete a campaign

DELETE /api/v1/dialer/campaigns/{id}
Scope: dialer:write. Soft-deletes the campaign and flips its pending contacts to archived. Terminal contacts (connected / failed / dnc / exhausted / wrong_number) are preserved untouched as a compliance audit trail. Campaign, lists, and contacts commit in one transaction.
Errors

List contact lists

GET /api/v1/dialer/campaigns/{id}/lists
Scope: dialer:read. Cursor-paginated. Each list carries total_contacts and a live contacts_dialing badge. Query parameters

Upload a contact list

POST /api/v1/dialer/campaigns/{id}/lists
Scope: dialer:write. Attaches a new contact list to the campaign and enqueues its contacts.
string
required
List name. 1–200 characters.
array
required
Contacts to dial. At least one. Each entry: phone_e164 (required, E.164), display_name (optional, ≤200 chars), contact_id (optional, links to a CRM contact). Duplicate phone_e164 values within the upload are de-duplicated (first occurrence wins) and reported back.
number
default:"5"
Maximum dial attempts before a contact is exhausted. 1–20.
Errors

Soft-delete a contact list

DELETE /api/v1/dialer/campaigns/{id}/lists/{listId}
Scope: dialer:write. Soft-deletes one list within the campaign. Pending contacts in the list flip to archived; terminal contacts are preserved as audit trail.
Errors

Campaign live stats

GET /api/v1/dialer/campaigns/{id}/stats
Scope: dialer:read. Wallboard detail for one campaign: per-status contact breakdown, a 24-hour call-outcome rollup with average handle time, and a rolling 30-minute abandonment rate that mirrors what the pacing engine throttles against.
avg_handle_time_seconds is null until a connected call has terminated. contacts_count_capped is true when the contact set exceeds the scan cap (the totals are then a floor). Errors

Caller-ID coverage

GET /api/v1/dialer/campaigns/{id}/caller-id-coverage
Scope: dialer:read. Local-presence analytics for one campaign: which destination area codes (NPAs) your caller-ID pool can present locally, the rolling answer rate per destination NPA, and the highest-volume under-covered NPAs to add a local number for. Read-only; originates no calls. North American Numbering Plan (+1) numbers only.
Errors

Voice-agent campaign rollup

GET /api/v1/dialer/voice-agents/{agentId}/campaigns
Scope: dialer:read. Lists the dispatch_mode='voice_agent' campaigns bound to one AI voice agent, with a per-campaign contact and call-outcome rollup over a rolling window. Cursor-paginated (limit / cursor, next_cursor / has_more). Human-mode campaigns never appear here. The envelope discloses contacts_count_capped_at and rollup_window_days. Returns a zeroed rollup (campaign_count: 0) for an unprovisioned workspace rather than an error.


Submit a disposition

POST /api/v1/dialer/campaigns/{id}/dispositions
Scope: dialer:write. An agent submits the outcome of a dial attempt; the platform routes the contact’s lifecycle status from the disposition (for example do_not_call adds the number to your do-not-call list, callback_later reschedules the contact).
string
required
The attempt_id returned when the dial leg was placed (from POST /dial or the batch-dispatch legs payload).
string
required
Agent-selected disposition string (1–100 chars). The routing matrix matches it case-insensitively — see Dialer Dispositions.
string
ISO-8601 timestamp. Required when the disposition reschedules a callback (callback_later / callback). Must be in the future and at most 90 days ahead.
string
Free-form agent notes, up to 2000 characters.
The full disposition-to-status matrix and side effects (do-not-call writes, callback scheduling, connect-rate accounting) are documented on the Dialer Dispositions page.
200 OK
Errors

Get the next contact (preview mode)

GET /api/v1/dialer/next-call
Scope: dialer:read. For preview-mode campaigns: atomically claims the next eligible contact so two agents polling at once never receive the same row. Progressive / predictive campaigns return 204 No Content — the pacing engine dials those instead. A preview contact stuck dialing for more than five minutes (the agent abandoned the tab) becomes re-claimable. The claim + dial round trip is worked end to end in every supported language under SDK call flows below.
cURL
200 OK
204 No Content — the campaign is progressive or predictive (the pacing engine handles dialing). Errors

Place a preview dial

POST /api/v1/dialer/dial
Scope: dialer:write. Originates the dial leg for a contact the agent claimed via GET /next-call. Only valid for active preview campaigns; progressive / predictive campaigns dial through the pacing engine. Requires an authenticated operator — an API-key caller cannot dial on behalf of an agent (401 AGENT_REQUIRED). The claim + dial round trip is worked end to end in every supported language under SDK call flows below.
string
required
The preview campaign to dial within.
string
required
The contact (from GET /next-call) to dial.
cURL
202 Accepted
Keep the response attempt_id — it’s what the agent submits back on POST /campaigns/:id/dispositions once the call ends. Errors

Batch-dispatch voice-agent legs

POST /api/v1/dialer/campaigns/{id}/batch-dispatch
Scope: dialer:write. Paces a dispatch_mode='voice_agent' campaign: originates up to the campaign’s concurrency_limit simultaneous AI-voice-agent legs. Each answered leg connects to the campaign’s voice_agent_id.
number
Optional per-invocation cap on legs to originate. 1–200. The effective cap is the smaller of this and the campaign’s concurrency_limit.
A successful call returns one of three statuses:
  • 202 Accepted — every claimed leg was originated.
  • 207 Multi-Status — some legs originated and some failed. Inspect each legs[].ok for the per-leg result.
  • 204 No Content — no eligible contacts were pending, so nothing was dispatched on this tick.
202 Accepted / 207 Multi-Status
204 No Content — no pending contacts to dispatch on this tick. Errors

Worked sequences

The trim above cuts every operation after the fifteenth down to the cURL + TypeScript pair. The hero flows past that cut keep the full six-language group here. The preview-dial chain — claim one contact with GET /next-call, originate its leg with POST /dial, then submit the outcome with POST /campaigns/:id/dispositions — is the canonical dialer loop: poll a claim, bridge it, record what happened, repeat. It is the whole operator turn of a preview campaign in three calls. Each step below carries the previous step’s response forward: data.contact.id from the claim feeds the dial, and the dial’s attempt_id feeds the disposition, so one claimed contact is dialed and closed exactly once.

1. Claim the next contact

The claim returns "id": "dlc_xyz" inside data.contact (see Get the next contact (preview mode) for the full envelope). A 204 No Content means the campaign is not in preview mode and the pacing engine dials instead — there is nothing to claim.

2. Place the preview dial

POST /dial takes an operator session (Authorization: Bearer <token>; an API key is refused with 401 AGENT_REQUIRED), and contact_id is the claim’s data.contact.id from step 1.
Responses are 202 Accepted; the envelope carries data.attempt_id ("datt_abc123") plus the contact_id you dialed (full envelope under Place a preview dial). Hold the attempt_id for step 3.

3. Submit the disposition

attempt_id is data.attempt_id from step 2 ("datt_abc123"); the disposition string routes the contact’s lifecycle per the Dialer Dispositions matrix.
A callback_later disposition requires a future callback_scheduled_at within 90 days (422 VALIDATION_ERROR otherwise); do_not_call writes the number to your do-not-call list. The full matrix lives on Dialer Dispositions.

Voice-agent batch dispatch

The second hero flow paces a dispatch_mode='voice_agent' campaign instead of the human loop: one scheduler tick calls POST /campaigns/:id/batch-dispatch and the platform originates up to concurrency_limit AI-voice-agent legs. Pin one Idempotency-Key per tick below, so a retried tick replays the original dispatch instead of originating a second batch of legs; retry it unchanged if the call times out.
The response is 202 Accepted when every claimed leg originated, 207 Multi-Status when some failed (inspect legs[].ok), or 204 No Content when no contact was pending; the worked envelope is under Batch-dispatch voice-agent legs above.

SDK call flows

The full preview round trip — claim the next contact with GET /next-call, dial it with POST /dial — in every supported language. Only the Node SDK has typed dialer helpers today — the Python SDK and Go SDK are core-scope, so for the dialer you drop to the documented escape hatch: client.request(method, path, ...) in Python and client.Request(ctx, method, path, ...) in Go. The voice-agent batch-dispatch call follows the same pattern and is worked in full under Batch-dispatch voice-agent legs above; the disposition call follows it too. All languages carry the same auth, retry, and Idempotency-Key behavior as the typed resource.

FCC abandon-rate ceiling

max_abandon_rate is capped at 0.03 (3%) per the FCC Telemarketing Sales Rule. Any create or update that sends a higher value is rejected with 422 VALIDATION_ERROR. The pacing engine also auto-flips a campaign to status: 'aborted' if its rolling 30-day abandon rate trips the ceiling. aborted is terminal and cannot be re-activated — review the abort reason and create a new campaign.

See also