Calls
The call object
Every call in Voxa, outbound, inbound or in the browser, is stored as one call record, and this page describes each of its fields, how to read them, and what they look like for common outcomes.
Read a call
curl https://voxa.abhinavyadav.in/api/v1/calls/$CALL_ID -H "X-API-Key: $VOXA_API_KEY"
The same record, plus a transcript_text field, is sent in the call.completed webhook as data.call. See Webhook events.
A complete example
An outbound call where the caller confirmed an appointment and the agent ended the call:
{
"id": "00b1d622a8f04f4f8f3a3c2d9e5b7a11",
"agent_id": "1b81a241c0e44f5c9d1f0e2a7c3b9d10",
"agent_name": "Appointment reminder",
"channel": "phone",
"direction": "outbound",
"from_number": "+918035001234",
"to_number": "+919812345678",
"status": "completed",
"provider": "plivo",
"provider_call_id": "b7a5f8de-1c2b-4c55-9d1e-7f3a2b6c9e10",
"provider_request_id": "0f6a1c4e-58b2-11f1-9a3e-0242ac110002",
"user_data": {"customer_name": "Rohan", "slot": "11 am"},
"transcript": [
{"role": "assistant", "text": "Hello Rohan, this is City Clinic calling about your appointment tomorrow.", "at": "2026-10-01T14:44:02.410000+05:30", "interrupted": false},
{"role": "user", "text": "Yes, I'll be there at eleven.", "at": "2026-10-01T14:44:07.950000+05:30", "interrupted": false},
{"role": "assistant", "text": "Great, we'll see you tomorrow at 11 am. Goodbye!", "at": "2026-10-01T14:44:09.120000+05:30", "interrupted": false}
],
"usage": {"stt_seconds": 14.0, "tts_characters": 121, "llm_input_tokens": 1840, "llm_output_tokens": 41, "llm_calls": 1},
"recording_path": "/data/recordings/00b1d622a8f04f4f8f3a3c2d9e5b7a11.wav",
"recording_seconds": 14.1,
"hangup_by": "agent",
"hangup_reason": "agent ended the call",
"error": "",
"created_at": "2026-10-01T14:43:51.002000+05:30",
"queued_at": "2026-10-01T14:43:51.001000+05:30",
"dequeued_at": "2026-10-01T14:43:52.318000+05:30",
"answered_at": "2026-10-01T14:44:01.870000+05:30",
"ended_at": "2026-10-01T14:44:16.090000+05:30",
"duration_seconds": 14.2,
"first_audio_ms": 1050,
"cost_paise": 95,
"providers": {"llm": "gemini", "llm_model": "gemini-3.5-flash-lite", "stt": "cartesia", "tts": "cartesia", "telephony": "plivo"},
"provider_cost_usd": 0.0061,
"ignore_call_window": false,
"instance": "blue",
"summary": "City Clinic called Rohan to confirm tomorrow's 11 am appointment. Rohan confirmed he will come. The agent said goodbye and ended the call.",
"extracted": {"confirmed": true, "new_day": null}
}
Fields
Identity
| Field | Type | Notes |
|---|---|---|
id | string | The call id. 32 hex characters. |
agent_id | string | The agent that ran the call. |
agent_name | string | The agent’s name when the call was created. Kept even if the agent is renamed or deleted. |
channel | phone or web | Phone (Plivo) or browser. |
direction | outbound, inbound or web | How the call started. See Call flow. |
from_number | string | E.164. Outbound: your caller ID. Inbound: the caller. Browser: empty. |
to_number | string | E.164. Outbound: the number called. Inbound: your number. Browser: empty. |
user_data | object | What you passed when placing the call. Empty for inbound calls. |
ignore_call_window | boolean | Whether the call was allowed to skip calling hours. |
Status
| Field | Type | Notes |
|---|---|---|
status | string | queued, ringing, in-progress, completed, failed, no-answer, busy or canceled. The last five are final. See Call flow. |
error | string | Why a call failed. Empty otherwise. A voice-service failure during a call is also written here, even if the call goes on. |
How the call ended
| Field | Type | Notes |
|---|---|---|
hangup_by | string | caller, agent, api, error, system, or empty when the call never connected (no-answer, busy, and Plivo failures). |
hangup_reason | string | A short reason in plain English. |
Common pairs:
hangup_by | hangup_reason | Status |
|---|---|---|
caller | caller hung up | completed |
agent | agent ended the call | completed |
agent | hang-up prompt: conversation complete | completed |
agent | caller was silent | completed |
agent | maximum call length reached | completed |
agent | credits ran out | completed |
api | canceled from the console | canceled |
api | ended from the console | completed |
error | speech services unavailable: ..., speech recognition stopped | failed |
system | expired in queue, agent was deleted | canceled |
system | out of credits, the phone provider refused the call: ..., server restarted during the call | failed |
| (empty) | Plivo’s hang-up cause, for example NO_ANSWER | no-answer, busy, failed or canceled |
Timing
All times are ISO 8601. In /api/v1 responses they use your time zone (India time by default); in webhooks they are UTC.
| Field | Type | Notes |
|---|---|---|
created_at | datetime | When the record was created. |
queued_at | datetime or null | Outbound only: when the call joined the queue. |
dequeued_at | datetime or null | Outbound only: when a slot was taken and dialling started. |
answered_at | datetime or null | When the agent went live. |
ended_at | datetime or null | When the call ended. |
duration_seconds | number | Seconds from answered_at to the end, to one decimal. This is what you are billed for. 0 if the call never connected. |
first_audio_ms | integer or null | The average time, over the agent’s replies, from the caller finishing a turn to the agent’s first audio. The welcome message is not counted. null if the agent never replied. |
Conversation
| Field | Type | Notes |
|---|---|---|
transcript | array | Every turn, in order. Not included in GET /api/v1/calls lists. |
transcript[].role | assistant, user or tool | Agent, caller, or a tool call. |
transcript[].text | string | What was said. For an interrupted reply, only what the caller heard. For a tool, name({args}). |
transcript[].at | datetime | When the turn was saved. |
transcript[].interrupted | boolean | true if the caller cut the agent off. |
The webhook copy of the record adds transcript_text: the transcript as plain text, one role: text line per turn, without tool turns.
Analytics
| Field | Type | Notes |
|---|---|---|
summary | string | The post-call summary, if analytics.summarize is on. Filled a few seconds after the call ends. |
extracted | object | Fields from analytics.extraction_prompt. {} if off or failed. |
See Analytics tab.
Usage and cost
| Field | Type | Notes |
|---|---|---|
cost_paise | integer | What the call cost your workspace, in paise. Per-second billing, rounded up to the paisa. See Limits and billing. |
usage.stt_seconds | number | Seconds of caller audio sent to the transcriber. |
usage.tts_characters | integer | Characters sent to the voice. |
usage.llm_input_tokens | integer | LLM input tokens, including the hang-up check. |
usage.llm_output_tokens | integer | LLM output tokens, including thinking tokens and the hang-up check. |
usage.llm_calls | integer | LLM requests made during the call. |
providers | object | Which services ran the call: llm, llm_model, stt, tts, telephony (plivo or web). |
provider_cost_usd | number | Voxa’s estimate of what the providers charged for this call, in US dollars. For information; you are billed cost_paise. |
Recording
| Field | Type | Notes |
|---|---|---|
recording_seconds | number | Length of the recording. 0 if there is none. |
recording_path | string | Where the file is on the Voxa server. Use GET /api/v1/calls/{id}/recording to download it. |
See Recording.
Telephony and internals
| Field | Type | Notes |
|---|---|---|
provider | string | plivo or web. |
provider_call_id | string | Plivo’s call UUID, once the call is answered. |
provider_request_id | string | Plivo’s request id for an outbound dial. |
instance | string | The Voxa server process that ran the call’s audio. Useful when reporting a problem. |
What a record looks like for other outcomes
No answer. status: "no-answer", hangup_by: "", hangup_reason is Plivo’s cause, duration_seconds: 0, cost_paise: 0, empty transcript. Webhooks get call.ended but no call.completed.
Canceled while queued. status: "canceled", hangup_by: "api", hangup_reason: "canceled from the console", dequeued_at: null, cost_paise: 0.
Expired in the queue. status: "canceled", hangup_by: "system", hangup_reason: "expired in queue".
Caller went silent. status: "completed", hangup_by: "agent", hangup_reason: "caller was silent", usually a transcript with the welcome message and no user turns.
Other call endpoints
| Request | Returns |
|---|---|
GET /api/v1/calls | A page of calls, newest first, without transcripts. |
GET /api/v1/calls/{id} | One call with its transcript. |
GET /api/v1/calls/{id}/events | The call’s timeline. |
GET /api/v1/calls/{id}/recording | The WAV recording (?download=true adds a file name). |
POST /api/v1/calls/{id}/hangup | Cancel or end the call. See Outbound calls. |
All need calls.view, except hang-up, which needs calls.place.
List calls
curl "https://voxa.abhinavyadav.in/api/v1/calls?agent_id=$AGENT_ID&status=completed,failed&limit=20" \
-H "X-API-Key: $VOXA_API_KEY"
{"items": [{"id": "00b1d622a8f04f4f8f3a3c2d9e5b7a11", "status": "completed", "duration_seconds": 14.2, "cost_paise": 95, "...": "..."}], "total": 37}
| Query parameter | Default | Notes |
|---|---|---|
agent_id | Only these agents’ calls: one id, or several separated by commas. | |
status | One status, or several separated by commas. | |
direction | outbound, inbound or web, or several separated by commas. | |
since, until | Calls created at or after since and before until (ISO 8601, e.g. 2026-10-01T00:00:00+05:30). | |
call_id | A call id, or its beginning. | |
phone_number | Your side of the call: the caller ID of an outbound call, the dialled number of an inbound one. Several separated by commas. | |
customer_number | The other side of the call, or any part of it (98123 finds +919812345678). | |
batch_id | Only the calls of this campaign. | |
q | Text to find in the call id, either number, or the agent name. Case-insensitive. | |
sort | -created_at | created_at, duration_seconds or cost_paise; a leading - sorts in descending order. |
limit | 50 | 1 to 200. |
skip | 0 | For paging: skip this many calls. |
total is the number of calls that match, for paging.
Timeline
curl https://voxa.abhinavyadav.in/api/v1/calls/$CALL_ID/events -H "X-API-Key: $VOXA_API_KEY"
[
{"at": "2026-10-01T14:43:51.004000+05:30", "type": "call.queued", "level": "info", "message": "queued at position 1", "data": {"position": 1, "by": "key:backend"}, "latency_ms": null},
{"at": "2026-10-01T14:44:01.880000+05:30", "type": "call.started", "level": "info", "message": "outbound call with Appointment reminder", "data": {"channel": "phone", "stt": "cartesia/ink-2", "llm": "gemini-3.5-flash-lite", "...": "..."}, "latency_ms": null},
{"at": "2026-10-01T14:44:09.010000+05:30", "type": "tts.first_audio", "level": "info", "message": "agent started speaking", "data": {}, "latency_ms": 1050}
]
Each entry is one event, oldest first. Timelines are kept for a limited number of days; the call record itself is kept.
Exporting calls
Download calls as CSV, newest first, one row per call. In the console: Dashboard → Download → Calls (CSV), for the period on screen, or Logs → Calls → Export with the filters on screen. Over the API:
curl -o calls.csv "https://voxa.abhinavyadav.in/api/v1/calls/export?days=30&timezone=Asia/Kolkata" \
-H "X-API-Key: $VOXA_API_KEY"
| Parameter | Default | What it does |
|---|---|---|
days | 30 | Calls from the last 1 to 365 days (ignored when since is given). |
agent_id, status, direction, since, until, call_id, phone_number, customer_number, batch_id, q | The same filters as List calls. | |
timezone | Asia/Kolkata | The zone created_at is written in. |
Columns: call_id, created_at, agent, direction, status, from_number, to_number, duration_seconds, cost_inr, first_audio_ms, hangup_by, hangup_reason, summary, then one extracted.<key> column per extracted field found in the period (lists are joined with ; ). At most 50,000 rows per export. Each export is recorded in the audit log.