Docs · API · Calls

Calls.

The Calls API is how you place outbound calls, list every call your agents handle — inbound and outbound alike — and fetch the full result of any one call: its recording, diarized transcript, the tools it called, and the outcome. Each call is a live conversation, not a recording you upload.

Call lifecycle#

A call moves through a small set of statuses. Outbound calls start at queued; inbound calls appear once the gateway answers. A terminal status is delivered to your webhook the moment the call ends.

FieldTypeDescription
queuedstatusAccepted and waiting for a line. This is what an outbound POST returns.
ringingstatusThe gateway is dialing the callee (outbound) or the call is being set up.
in_progressstatusConnected — the agent is on the call. Emitted as the call.started webhook.
completedstatusThe conversation ended normally. transcript, tool_calls, outcome, and recording_url are populated.
failedstatusThe call could not complete; error explains. No voice minutes are billed.
no_answerstatusThe callee never picked up before the ring timeout.
busystatusThe callee's line was busy.

Place an outbound call#

POST/v1/calls

Dials to with the given agent and returns the call at queued immediately — the conversation happens asynchronously. Poll GET /v1/calls/{id} or wait for the call.completed webhook for the result.

FieldTypeDescription
agent_idstringrequiredThe agent to run the call. It must be published — an outbound call to a draft agent returns 409 agent_not_published.
phone_number_idstringrequired for phoneThe phone number to place the call from. Required for PSTN calls; omit it for a web call. If the agent has no attachable number the request returns 409 number_unavailable.
tostringrequiredThe destination number in E.164 format, e.g. +966512345678.
varsobjectoptionalPer-call variables interpolated into the conversation — the first message, persona, and tool inputs. Reference them in agent copy as {{name}}. Echoed back on the call as vars.
metadataobjectoptionalArbitrary key/value pairs stored on the call and returned unchanged — useful for correlating with your CRM.

Recording is an organization-level compliance setting on the agent (record_calls) — there is no per-call override.

Request
curl -X POST https://voice.whizztech.ai/v1/calls \
  -H "Authorization: Bearer $WHIZZ_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-T-88213-attempt-1" \
  -d '{
    "agent_id": "ag_7Q2c51b84a07",
    "phone_number_id": "pn_5b8f0d21",
    "to": "+966512345678",
    "vars": { "name": "سلطان", "order": "49207" },
    "metadata": { "crm_ticket": "T-88213" }
  }'
Response · 201 Created
{
  "id": "call_1d84c0e795b2",
  "object": "call",
  "agent_id": "ag_7Q2c51b84a07",
  "engine": "dialect",
  "transport": "phone_out",
  "direction": "outbound",
  "status": "queued",
  "from_number": "+966920000123",
  "to_number": "+966512345678",
  "phone_number_id": "pn_5b8f0d21",
  "language": "ar",
  "dialect": "saudi",
  "ended_reason": null,
  "duration_sec": 0,
  "billed_sec": 0,
  "credits_charged": 0,
  "price_usd": 0,
  "latency": {},
  "campaign_id": null,
  "vars": { "name": "سلطان", "order": "49207" },
  "metadata": { "crm_ticket": "T-88213" },
  "created_at": "2026-07-13T09:33:02.000Z"
}

Idempotency#

A call is the one request in this API that spends money outside your wallet: it dials a real phone. Send an Idempotency-Key header — any string up to 255 characters, unique per call you intend to place — and a retry after a timeout returns the original call with "replayed": true instead of dialing a second time. Keys are scoped to your organization and never expire.

Errors#

FieldTypeDescription
400 invalid_requesterrorMissing agent_id / to, or to is not a valid E.164 number.
402 insufficient_creditserrorYour wallet is empty. Voice is metered per second at call end, so an empty wallet is refused before the dial.
403 do_not_callerrorto is on your do-not-call list. Campaigns filter this list too.
404 not_founderrorNo agent with that ID in your organization.
409 agent_not_publishederrorThe agent is a draft. Publish it first.
409 number_unavailableerrorphone_number_id is not yours, is not active, or was omitted and the agent has no number attached.
502 dial_failederrorThe dialer rejected the call. The call is returned at status failed with the reason — no voice minutes are billed.
503 telephony_unavailableerrorOutbound dialing is not configured for this deployment.

Placing many calls at once? Don't loop this endpoint — create a campaign, which paces recipients at your plan concurrency and rolls up the results.

List calls#

GET/v1/calls

Returns both inbound and outbound calls, newest-first, with cursor pagination. All filters combine (logical AND).

QueryTypeDescription
agent_idstringoptionalOnly calls handled by this agent.
statusstringoptionalOne of queued, ringing, in_progress, completed, failed, no_answer, busy.
directionstringoptionalinbound or outbound.
phone_number_idstringoptionalOnly calls on this number.
campaign_idstringoptionalOnly calls placed by this campaign.
created_afterISO 8601optionalOnly calls created strictly after this timestamp.
created_beforeISO 8601optionalOnly calls created strictly before this timestamp.
limitintegerdefault: 25Page size, 1–100.
starting_afterstringoptionalCursor: a call ID from a previous page. Returns calls older than it.
Request
curl "https://voice.whizztech.ai/v1/calls?direction=outbound&status=completed&created_after=2026-07-01T00:00:00Z&limit=25" \
  -H "Authorization: Bearer $WHIZZ_KEY"
Response · 200 OK
{
  "object": "list",
  "data": [
    {
      "id": "call_1d84c0e795b2",
      "object": "call",
      "agent_id": "ag_7Q2c51b84a07",
      "engine": "dialect",
      "transport": "phone_out",
      "direction": "outbound",
      "status": "completed",
      "from_number": "+966920000123",
      "to_number": "+966512345678",
      "phone_number_id": "pn_5b8f0d21",
      "language": "ar",
      "dialect": "saudi",
      "ended_reason": "hangup_customer",
      "duration_sec": 52.4,
      "billed_sec": 53,
      "credits_charged": 44,
      "price_usd": 0.44,
      "latency": { "firstAudioMsP50": 410, "firstAudioMsP95": 690, "samples": 12 },
      "campaign_id": null,
      "vars": { "name": "سلطان", "order": "49207" },
      "metadata": { "crm_ticket": "T-88213" },
      "created_at": "2026-07-13T09:33:02.000Z"
    }
  ],
  "has_more": true
}

Results come newest-first. When has_more is true, pass the last id of the page as starting_after to get the next one:

Pagination
# next page: pass the last id of the previous page as the cursor
curl "https://voice.whizztech.ai/v1/calls?limit=25&starting_after=call_1d84c0e795b2" \
  -H "Authorization: Bearer $WHIZZ_KEY"

Get a call#

GET/v1/calls/{id}

The full call object. On a completed call, transcript, tool_calls, outcome, and (if recording is on) recording_url are all populated. IDs from other organizations return 404 not_found.

Request
curl https://voice.whizztech.ai/v1/calls/call_1d84c0e795b2 \
  -H "Authorization: Bearer $WHIZZ_KEY"
Response · 200 OK
{
  "id": "call_1d84c0e795b2",
  "object": "call",
  "agent_id": "ag_7Q2c51b84a07",
  "campaign_id": null,
  "engine": "dialect",
  "transport": "phone_out",
  "direction": "outbound",
  "status": "completed",
  "from_number": "+966920000123",
  "to_number": "+966512345678",
  "phone_number_id": "pn_5b8f0d21",
  "router_decision": { "mode": "lid", "detectedLanguage": "ar", "chosenEngine": "dialect", "confidence": 0.97, "ms": 240 },
  "language": "ar",
  "dialect": "saudi",
  "ended_reason": "hangup_customer",
  "started_at": "2026-07-13T09:33:06.000Z",
  "ended_at": "2026-07-13T09:33:58.000Z",
  "duration_sec": 52.4,
  "billed_sec": 53,
  "latency": { "firstAudioMsP50": 410, "firstAudioMsP95": 690, "samples": 12 },
  "credits_charged": 44,
  "price_usd": 0.44,
  "trace": [
    { "t": 0.4, "type": "tts", "text": "حياك الله في خدمة عملاء وِز، معك سلطان؟" },
    { "t": 5.1, "type": "asr", "text": "إي نعم، عندي استفسار عن الطلب." },
    { "t": 11.8, "type": "tool", "name": "lookup_order", "args": { "id": "49207" }, "result": { "status": "in_transit" } }
  ],
  "transcript_path": "gs://whizz-voice-recordings/…/transcript.json",
  "recording_path": "gs://whizz-voice-recordings/…/audio.mp3",
  "analysis": { "outcome": "resolved", "sentiment": "positive", "summary": "…", "csat": 4.6, "tags": ["order-status"] },
  "vars": { "name": "سلطان", "order": "49207" },
  "metadata": { "crm_ticket": "T-88213" },
  "created_at": "2026-07-13T09:33:02.000Z",
  "updated_at": "2026-07-13T09:34:01.000Z"
}

Call fields#

FieldTypeDescription
statusstringqueued · ringing · in_progress · completed · failed · no_answer · busy.
directionstringinbound (a caller reached the agent) or outbound (you placed the call).
transportstringphone_out (you dialed), phone_in (a caller dialed your number), or web (the browser widget).
enginestringWhich speech engine served the call: global or dialect.
from_number / to_numberstringThe two ends in E.164. For outbound, from_number is your number; for inbound, it is the caller's.
duration_secnumberWall-clock length of the connected conversation, in seconds (0 until the call ends). Measured by our server, never by the client.
billed_secintegerSeconds billed for voice — duration rounded up, subject to the 10-second minimum.
language / dialectstringThe language spoken (e.g. ar) and, for Arabic, the dialect (e.g. saudi).
ended_reasonstring | nullHow the call ended: hangup_customer · hangup_agent · transfer · dial_failed · error. null while in progress.
credits_chargedintegerCredits debited for the conversation at the agent's language tier. 1 credit = 1 US cent. 0 until the call ends.
price_usdnumberThe all-in retail price applied to the call, in USD.
latencyobjectTime-to-first-audio distribution for the call: { firstAudioMsP50, firstAudioMsP95, samples }.
varsobjectThe per-call variables you supplied, echoed back unchanged.
metadataobjectYour opaque key/value bag, echoed back unchanged.

GET /v1/calls/{id} additionally returns router_decision, started_at, ended_at, trace, transcript_path, recording_path, and analysis.

trace#

The ordered event log of the conversation — one entry per ASR turn, agent utterance, and tool call, each stamped with t, the offset in seconds from the start of the call.

FieldTypeDescription
type: "asr"eventWhat the caller said, as transcribed: { t, type, text }.
type: "tts"eventWhat the agent said: { t, type, text }.
type: "tool"eventA tool the agent invoked mid-call: { t, type, name, args, result }.

analysis#

Post-call analysis, populated once the call reaches completed. Empty ({}) before that.

FieldTypeDescription
outcomestringresolved · unresolved · transferred · voicemail.
sentimentstringOverall caller sentiment: positive · neutral · negative.
summarystringA short natural-language summary of what happened on the call.
csatnumberInferred satisfaction, 1–5.
tagsstring[]Topic labels derived from the conversation.

transcript_path / recording_path#

Storage paths for the full transcript and the call audio, not public URLs. Recording is written only when record_calls is on for the agent; otherwise recording_path stays null. Play or download the audio from the call page in your dashboard.

Web calls#

Calls placed from the embedded browser widget land in the same list and detail endpoints, with transport: "web" and no phone_number_id. Everything else — trace, analysis, recording — works the same as a phone call.