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.
| Field | Type | Description |
|---|---|---|
| queued | status | Accepted and waiting for a line. This is what an outbound POST returns. |
| ringing | status | The gateway is dialing the callee (outbound) or the call is being set up. |
| in_progress | status | Connected — the agent is on the call. Emitted as the call.started webhook. |
| completed | status | The conversation ended normally. transcript, tool_calls, outcome, and recording_url are populated. |
| failed | status | The call could not complete; error explains. No voice minutes are billed. |
| no_answer | status | The callee never picked up before the ring timeout. |
| busy | status | The callee's line was busy. |
Place an outbound call#
/v1/callsDials 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.
| Field | Type | Description | |
|---|---|---|---|
| agent_id | string | required | The agent to run the call. It must be published — an outbound call to a draft agent returns 409 agent_not_published. |
| phone_number_id | string | required for phone | The 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. |
| to | string | required | The destination number in E.164 format, e.g. +966512345678. |
| vars | object | optional | Per-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. |
| metadata | object | optional | Arbitrary 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.
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" }
}'{
"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#
| Field | Type | Description |
|---|---|---|
| 400 invalid_request | error | Missing agent_id / to, or to is not a valid E.164 number. |
| 402 insufficient_credits | error | Your wallet is empty. Voice is metered per second at call end, so an empty wallet is refused before the dial. |
| 403 do_not_call | error | to is on your do-not-call list. Campaigns filter this list too. |
| 404 not_found | error | No agent with that ID in your organization. |
| 409 agent_not_published | error | The agent is a draft. Publish it first. |
| 409 number_unavailable | error | phone_number_id is not yours, is not active, or was omitted and the agent has no number attached. |
| 502 dial_failed | error | The dialer rejected the call. The call is returned at status failed with the reason — no voice minutes are billed. |
| 503 telephony_unavailable | error | Outbound 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#
/v1/callsReturns both inbound and outbound calls, newest-first, with cursor pagination. All filters combine (logical AND).
| Query | Type | Description | |
|---|---|---|---|
| agent_id | string | optional | Only calls handled by this agent. |
| status | string | optional | One of queued, ringing, in_progress, completed, failed, no_answer, busy. |
| direction | string | optional | inbound or outbound. |
| phone_number_id | string | optional | Only calls on this number. |
| campaign_id | string | optional | Only calls placed by this campaign. |
| created_after | ISO 8601 | optional | Only calls created strictly after this timestamp. |
| created_before | ISO 8601 | optional | Only calls created strictly before this timestamp. |
| limit | integer | default: 25 | Page size, 1–100. |
| starting_after | string | optional | Cursor: a call ID from a previous page. Returns calls older than it. |
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"{
"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:
# 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#
/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.
curl https://voice.whizztech.ai/v1/calls/call_1d84c0e795b2 \
-H "Authorization: Bearer $WHIZZ_KEY"{
"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#
| Field | Type | Description |
|---|---|---|
| status | string | queued · ringing · in_progress · completed · failed · no_answer · busy. |
| direction | string | inbound (a caller reached the agent) or outbound (you placed the call). |
| transport | string | phone_out (you dialed), phone_in (a caller dialed your number), or web (the browser widget). |
| engine | string | Which speech engine served the call: global or dialect. |
| from_number / to_number | string | The two ends in E.164. For outbound, from_number is your number; for inbound, it is the caller's. |
| duration_sec | number | Wall-clock length of the connected conversation, in seconds (0 until the call ends). Measured by our server, never by the client. |
| billed_sec | integer | Seconds billed for voice — duration rounded up, subject to the 10-second minimum. |
| language / dialect | string | The language spoken (e.g. ar) and, for Arabic, the dialect (e.g. saudi). |
| ended_reason | string | null | How the call ended: hangup_customer · hangup_agent · transfer · dial_failed · error. null while in progress. |
| credits_charged | integer | Credits debited for the conversation at the agent's language tier. 1 credit = 1 US cent. 0 until the call ends. |
| price_usd | number | The all-in retail price applied to the call, in USD. |
| latency | object | Time-to-first-audio distribution for the call: { firstAudioMsP50, firstAudioMsP95, samples }. |
| vars | object | The per-call variables you supplied, echoed back unchanged. |
| metadata | object | Your 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.
| Field | Type | Description |
|---|---|---|
| type: "asr" | event | What the caller said, as transcribed: { t, type, text }. |
| type: "tts" | event | What the agent said: { t, type, text }. |
| type: "tool" | event | A tool the agent invoked mid-call: { t, type, name, args, result }. |
analysis#
Post-call analysis, populated once the call reaches completed. Empty ({}) before that.
| Field | Type | Description |
|---|---|---|
| outcome | string | resolved · unresolved · transferred · voicemail. |
| sentiment | string | Overall caller sentiment: positive · neutral · negative. |
| summary | string | A short natural-language summary of what happened on the call. |
| csat | number | Inferred satisfaction, 1–5. |
| tags | string[] | 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.