API reference

View raw .md

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:

keynotes
idmessage id
roleuser or assistant — unchanged, kept for back-compat
speakercaller or agent — stable label derived from role
contentverbatim — filler words ("um", "uh", repeats) are preserved as spoken
timestampwhen the message was stored (same value the old created_at held)
is_finalalways 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.