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; requires a bearer login and returns aparticipant_token.- Campaigns — bulk outreach over a recipient list, with scheduling and per-recipient state.
Endpoints
Place a call
channel=voice.
Request fields
The body is a strict allowlist — an unrecognised field is a400 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.
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.
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. Cancelling a terminal call returns 409.
Inspect a call
GET /calls/{call_id} returns the record above plus one attempts entry per dial:
session_id is a full session — transcript, usage 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 anidempotency_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”).
Migrating an integration built against Ringg’s individual-call endpoint?
/cu1/v1/calls and /cu1/v2/calls accept that request shape unchanged and translate onto this API — see Individual-call compatibility.Outbound webhooks
Get
call_completed pushed to you instead of polling.Sessions
Transcript, usage and recording for each dial.

