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. |
| record | boolean | optional | Override the agent's record_calls compliance setting for this one call. When recording is off, recording_url stays null. |
| metadata | object | optional | Arbitrary key/value pairs stored on the call and returned unchanged — useful for correlating with your CRM. |
curl -X POST https://voice.whizztech.ai/v1/calls \
-H "Authorization: Bearer $WHIZZ_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "ag_7Q2c51b84a07",
"phone_number_id": "pn_5b8f0d21",
"to": "+966512345678",
"vars": { "name": "سلطان", "order": "49207" },
"record": true,
"metadata": { "crm_ticket": "T-88213" }
}'{
"id": "call_1d84c0e795b2",
"object": "call",
"agent_id": "ag_7Q2c51b84a07",
"direction": "outbound",
"status": "queued",
"from": "+966920000123",
"to": "+966512345678",
"phone_number_id": "pn_5b8f0d21",
"channel": "phone",
"duration_sec": 0,
"billed_sec": 0,
"language": "ar",
"dialect": "saudi",
"lang_tier": "arabic",
"outcome": null,
"sentiment": null,
"recording_url": null,
"vars": { "name": "سلطان", "order": "49207" },
"campaign_id": null,
"metadata": { "crm_ticket": "T-88213" },
"cost": { "voice_credits": 0, "telephony_usd": 0 },
"error": null,
"created_at": "2026-07-13T09:33:02.000Z",
"updated_at": "2026-07-13T09:33:02.000Z",
"ended_at": null
}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",
"direction": "outbound",
"status": "completed",
"from": "+966920000123",
"to": "+966512345678",
"channel": "phone",
"duration_sec": 52.4,
"billed_sec": 53,
"language": "ar",
"dialect": "saudi",
"outcome": "resolved",
"sentiment": "positive",
"campaign_id": null,
"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",
"direction": "outbound",
"status": "completed",
"from": "+966920000123",
"to": "+966512345678",
"phone_number_id": "pn_5b8f0d21",
"channel": "phone",
"duration_sec": 52.4,
"billed_sec": 53,
"language": "ar",
"dialect": "saudi",
"lang_tier": "arabic",
"outcome": "resolved",
"sentiment": "positive",
"recording_url": "https://…signed…",
"transcript": {
"segments": [
{ "speaker": "agent", "start": 0.4, "end": 4.8, "text": "حياك الله في خدمة عملاء وِز، معك سلطان؟" },
{ "speaker": "caller", "start": 5.1, "end": 9.7, "text": "إي نعم، عندي استفسار عن الطلب." },
{ "speaker": "agent", "start": 10.0, "end": 15.2, "text": "أبشر، لحظة أتحقق من حالة الطلب." }
],
"language_code": "ar",
"word_count": 118
},
"tool_calls": [
{ "name": "lookup_order", "args": { "id": "49207" }, "result": { "status": "in_transit" } },
{ "name": "expedite_shipment", "args": { "id": "49207" }, "result": { "ok": true } }
],
"vars": { "name": "سلطان", "order": "49207" },
"campaign_id": null,
"metadata": { "crm_ticket": "T-88213" },
"cost": { "voice_credits": 44, "telephony_usd": 0.013 },
"error": null,
"created_at": "2026-07-13T09:33:02.000Z",
"updated_at": "2026-07-13T09:34:01.000Z",
"ended_at": "2026-07-13T09:33:58.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). |
| from / to | string | The two ends in E.164. For outbound, from is your number; for inbound, from is the caller. |
| channel | string | phone (PSTN), web (browser widget), or sip (your own trunk). |
| duration_sec | number | Wall-clock length of the connected conversation, in seconds (0 until the call ends). |
| 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). |
| outcome | string | null | resolved · unresolved · transferred · voicemail. null until the call completes. |
| sentiment | string | null | Overall caller sentiment: positive · neutral · negative. null until the call completes. |
| recording_url | string | null | A short-lived signed URL to the audio. null when recording is off for the call. |
| error | string | null | Why the call failed, when status is failed / no_answer / busy. |
transcript#
| Field | Type | Description |
|---|---|---|
| segments | object[] | Diarized turns: { speaker, start, end, text } with speaker "agent" / "caller" and start/end in seconds. |
| language_code | string | The language the conversation was transcribed in, as an ISO code (e.g. ar). |
| word_count | integer | Total words across all segments. |
tool_calls#
| Field | Type | Description |
|---|---|---|
| name | string | The tool the agent invoked mid-call, as declared on the agent. |
| args | object | The arguments the agent passed, resolved from the conversation and vars. |
| result | object | What your tool returned. The agent speaks from this to continue the call. |
cost#
| Field | Type | Description |
|---|---|---|
| voice_credits | integer | Credits charged for the conversation at the agent's language tier. 1 credit = 1 US cent. |
| telephony_usd | number | Pass-through carrier cost for a phone call, at cost. 0 for web calls and bring-your-own SIP. |
Web calls#
Calls placed from the embedded browser widget land in the same list and detail endpoints, with channel: "web", no phone_number_id, and no telephony cost — cost.telephony_usd is 0. Everything else — transcript, tool_calls, outcome, recording — works the same as a phone call.