Organised by what you noticed. Every answer starts from GET /v1/calls/{session_id} — field reference on calls. Read, in order: snapshot.issues (every detected problem as {kind, message}), transcript (speech with every tool call and result inline), session.userdata (the state the call ended with), then events (the runtime trace) when the transcript looks fine but the call still went wrong. Find calls worth opening by outcome (from call analysis) or close_reason:
To see which endings dominate, read endings from GET /v1/observability.

The agent said something wrong or made it up

Look at the function_call_output items above the bad line.
  1. A tool returned the wrong value → fix the tool, and reproduce it with POST /v1/tools/{tool_id}/run (testing and publishing).
  2. Nothing in the prompt or any tool result contained the fact → the model guessed. Give it a source, or tell it what to say when it does not know (prompting).
  3. A template resolved to nothing → check session.userdata and the call’s vars (variables).
Post-call analysis fields never reach a live call; adding them fixes nothing here.

The agent was slow

Look at snapshot.response_latency.p95_ms (slow above snapshot.slow_response_threshold_ms, 3500) and the largest entry in snapshot.response_stages: snapshot.slow_tool_calls_in_silence counts tools over 8 seconds with nothing spoken over them. Fix with a say operation before the slow step or long_running_task on the tool (speech operations, tools). A poor snapshot.connection.caller is the caller’s network, not your stack.

The agent called the wrong tool or sent bad arguments

The function_call item’s metadata.data shows the tool name and the arguments the model chose. The model only knows a tool through its description and its json_schema property descriptions.
  • Say when to use the tool, when not to, and each argument’s format (schema and description).
  • Split a tool that does several things; remove overlaps between tools (or between a tool and a prompt section) that answer the same question.
  • Put ordering and preconditions (“confirm the spelling before calling create_ticket”) in the system prompt.
A call that ended agent_start_failed never got to choose: two attached tools exposed the same name; session.error says which.

A tool failed

The function_call_output has data.is_error: true and data.output is what the model was told; events has the tool.started/tool.ended pair. Counters: snapshot.tool_calls, tool_call_failures, unfinished_tool_calls (the call ended while it ran — not a failure).
  • If the caller heard something misleading, fix the tool’s on_error handling (tool failures).
  • Reproduce it with POST /v1/tools/{tool_id}/run.
  • A missing {{secrets.NAME}} raises rather than substituting empty (secrets).
snapshot.provider_failures / provider_recoveries are model-provider failovers, a separate problem (model fallbacks).

The agent hung up early

session.close_reason says who ended it: If it was the agent, find the end_call that fired and read the condition before it; a loose branch is the usual cause (end call). If the caller hung up during the greeting, analysis skips the call as too_short and it does not count against your success rate. Every value: close reasons.

The call never connected

The close reason’s bucket says whose problem it is: No call record at all means the call never reached an agent: check the number’s readiness is live and a published voice agent is assigned (phone numbers).

Someone was cut off mid-sentence

  • The agent stopped for noise. High snapshot.false_interruptions with low false_interruptions_resumed means resume_false_interruption is off (turn handling).
  • The audio path degraded. snapshot.noise_cancellation_failed: true means the call ran on raw audio (audio processing); snapshot.connection poor or lost on the caller side is their network.
  • The agent monologued. High snapshot.pace.longest_agent_turn_ms and talk_over_turns: shorten replies in the prompt.
  • The whole call ended at three hours. Every call is capped at three hours.

The agent forgot a returning caller

  1. config.conversation.context on the answering agent defaults to none (start clean). Set summary or transcript (conversation memory).
  2. Both calls must have the same contact_key; conversation_id changes per call.
  3. summary reads post-call analysis summaries; if analysis or its summary was turned off after publishing, summaries stop.
  4. initialize_userdata (default true) carries the person’s userdata into summary and transcript calls.
  5. Erased history (session.content_deleted_at, or retention) is gone (data retention).

Costs are higher than expected

Start with the cost section of GET /v1/observability: Also: voicemail is billed as an answered call; snapshot.pace.hold_ms (transfer hold) is inside the duration and bill; each analysis backfill is another model call on your key. Your credit balance only falls by the platform fee — model spend goes to your providers. See costs.

The call record itself looks wrong

Calls

Every field this page reads.

Close reasons

Every ending and its fix.