Skip to main content
A call is one outbound phone call, fired server-to-server. It is the primitive to reach for when your own system decides who to ring and when — a CRM trigger, a queue worker, a workflow step — and you don’t want to model it as a campaign. Each call is a durable record that owns its own retries, calling window and concurrency slot. Your workspace’s calls form a queue: they dial in the order they come due, as concurrency slots free up. Every dial creates a session, so one call can have several sessions attached to it.
Three ways to start a voice conversation, and they are not interchangeable:
  • POST /calls — one outbound phone call, API key only, no human in the loop. This page.
  • POST /sessions — a web (browser) call for a signed-in user, or a dashboard test phone call (one test call of either kind at a time per workspace); requires a bearer login and returns a participant_token.
  • Campaigns — bulk outreach over a recipient list, with scheduling and per-recipient state.

Endpoints

POST /calls rejects bearer logins and agent-session tokens even though both can carry sessions:write. An agent-session token lives inside every running voice worker for the duration of a call, so accepting one would let the credential minted for a call originate the next one. Use a workspace API key.

Place a call

The agent must be published and channel=voice.

Request fields

The body is a strict allowlist — an unrecognised field is a 400 naming it, so a setting can never appear to take effect when it didn’t. custom_variables values must be strings, numbers, booleans or null: they are substituted verbatim into the prompt, so a nested object would render as raw data inside the agent’s instructions. The one exception is the built-in {{callee_name}} (see Agents): it is Title-Cased for speech, and when custom_variables.callee_name is absent or blank it comes from the top-level callee_name.

call_config

A retry re-checks the calling window, so a backoff can never push a dial outside it. A declined call (the callee actively rejected it) is never retried.

Fields that are refused

smart_formatter (extract_first_name, transliteration, transliteration_language) is accepted, stored and echoed back, but not applied to the call. It exists for contract compatibility with migrating integrations; don’t rely on it for behaviour.

Response

POST /calls answers 200 with the call record. Accepting a call is asynchronous — it may still be waiting for its calling window or queued behind your workspace’s concurrency cap — so there is no dispatch id to report yet. Poll GET /calls/{id} or subscribe to call_completed. If call orchestration is briefly unavailable, the call is still accepted as queued and dials once it recovers. That cap counts every live call in the workspace: API calls, campaign dials, inbound calls, and a test call — browser or phone — someone has started from the dashboard. So while a dashboard test call is running, one fewer slot is free for your calls.
The response never echoes to_number — you supplied it, and repeating it would put a subscriber number into every orchestrator’s response logs. to_number_masked is enough to reconcile against your own record. There is no participant_token: this is a phone call, not a browser session.

Lifecycle

queued, scheduled, dialing and in_progress are active; completed, failed and cancelled are terminal. A call waiting for a retry is scheduled, with scheduled_for saying when it may dial again; its outcome stays empty until the call is final. session_id is the latest dialled session. Cancel works from any active status. A queued or scheduled call is never dialled; a call that is ringing or live is hung up. Cancelling a terminal call returns 409.

Inspect a call

GET /calls/{call_id} returns the record above plus one attempts entry per dial:
Each session_id is a full session — transcript, cost and recording all hang off it. outcome uses the same vocabulary everywhere in the platform: completed, no_media, no_answer, busy, declined, failed, user_disconnected, expired. sentiment is populated only for agents that opted into sentiment analysis. GET /calls lists them, filtered by status, agent_id, created_from and created_to, with standard offset pagination.

Idempotency

Send an idempotency_key and a repeat of the same request is refused with 409 naming the existing call, rather than dialling the number twice. This is the safe way to retry a POST /calls whose response you never saw.

Errors

The 402 and destination checks run before the call is queued, so no call record is created on refusal. Where a deployment restricts outbound destinations, a to_number outside the permitted prefixes is refused with 400 invalid_request (“to_number is not a permitted destination”).

Outbound webhooks

Get call_completed pushed to you instead of polling.

Sessions

Transcript, cost and recording for each dial.