API reference
Calls API
Starting a call, listing call history, and fetching a call's transcript/recording. All endpoints require an API key or dashboard token.
Start a call
POST /agents/{agent_id}/calls
Body (optional):
{ "conversation_id": "9e3c..." }
Omit conversation_id to start a fresh conversation; pass a previous call's conversation_id to continue it with full prior history as LLM context (multi-turn context carries across calls, not just within one).
Response 201:
{
"call_id": "5b1a...",
"conversation_id": "9e3c...",
"ws_url": "/calls/5b1a.../talk",
"expires_at": "2026-08-17T12:05:00Z"
}
ws_url is relative and single-use — connect the talk WebSocket to it before expires_at. 402 if the account balance can't cover the call — no call_id is issued in that case.
List calls for an agent
GET /agents/{agent_id}/calls
[
{
"id": "5b1a...",
"agent_id": "...",
"conversation_id": "9e3c...",
"status": "ended",
"recording_status": "uploaded",
"created_at": "2026-08-17T12:00:00Z",
"duration_seconds": 47.2,
"cost_usd": 0.031
}
]
cost_usd is what was actually debited for the call — not an estimate.
List all calls (across agents)
GET /calls
Same shape as above plus agent_name, across every agent on the account — the cross-agent call log the dashboard's Calls tab uses. Newest first.
Get one call
GET /calls/{call_id}
Adds the full turn-by-turn messages array, a metrics block, recording_expires_at, and (once the recording is processed) a signed recording_url (valid ~1 hour, generate a fresh one by re-fetching):
{
"id": "5b1a...",
"status": "ended",
"duration_seconds": 47.2,
"cost_usd": 0.031,
"messages": [
{ "id": "...", "role": "user", "speaker": "caller", "content": "um what are your hours?", "timestamp": "...", "is_final": true },
{ "id": "...", "role": "assistant", "speaker": "agent", "content": "We're open...", "timestamp": "...", "is_final": true }
],
"metrics": {
"word_count": { "caller": 34, "agent": 88 },
"turn_count": 6,
"turns_by_speaker": { "caller": 3, "agent": 3 },
"talk_ratio": { "caller": 0.279, "agent": 0.721 },
"message_count": 6
},
"recording_url": "https://.../signed?token=...",
"recording_expires_at": "2026-09-26T12:00:00Z"
}
Transcript message schema (frozen)
The keys on each messages[] entry are stable and additive-only — existing keys never change type or disappear:
| key | notes |
|---|---|
id | message id |
role | user or assistant — unchanged, kept for back-compat |
speaker | caller or agent — stable label derived from role |
content | verbatim — filler words ("um", "uh", repeats) are preserved as spoken |
timestamp | when the message was stored (same value the old created_at held) |
is_final | always true — only finalised text is stored; partial hypotheses never are |
Wakili never rewrites stored transcript text. Pass ?cleanup=true to apply a light readability pass (filler-word and duplicate removal, capitalisation) to the response only — the stored rows are untouched. Default is off.
recording_status is one of recording (call just ended, upload in progress), uploaded, empty (no audio captured), failed, deleted (removed via the API), or expired (past the retention window). recording_url is only present when recording_status is uploaded.
Cost breakdown
GET /calls/{call_id}/cost
{
"call_id": "5b1a...",
"stt_seconds": 512.4,
"llm_prompt_tokens": 18422,
"llm_completion_tokens": 3110,
"tts_characters": 9004,
"total_charged_usd": 0.62
}
Per-component consumption plus the single dollar amount actually debited for the call, so you don't have to reassemble cost per provider. total_charged_usd is the real ledger debit, not a re-derived estimate.
Metrics only
GET /calls/{call_id}/metrics
Returns just the metrics block from GET /calls/{call_id} — word_count, turn_count, turns_by_speaker, talk_ratio, message_count.
Recording
GET /calls/{call_id}/recording # 302 redirect to a short-lived signed URL, 404 if none
DELETE /calls/{call_id}/recording # deletes the audio object, 204 (recording_status → "deleted")
Recordings are retained for a fixed window (30 days by default), surfaced as recording_expires_at on the call detail. After that they're purged and recording_status becomes expired.
Call status values
pending (created, WebSocket not yet connected) → active (WebSocket connected, in progress) → ended (WebSocket closed, either turn completed normally, idle timeout, or provider error — see talk WebSocket errors).
When a call reaches ended, Wakili fires a call.ended webhook to any endpoint you've registered.