Something went wrong on a call. This page is organised by what you noticed, not by what the system calls it.

Read the run before theorizing

Open the call. Almost every question on this page is answered by three things that are already on GET /v1/calls/{session_id}:
  1. snapshot — the verdict. snapshot.ok is true when nothing was found; snapshot.issues is a list of plain sentences naming every problem, and it is written from the run’s own timings, errors and tool activity rather than from anything you have to interpret.
  2. transcript — what was said, with every tool call and its result inline. When a model did something unexpected, the reason is usually the line above it.
  3. session.userdata — the state the call ended with. A tool that wrote the wrong value shows up here before it shows up anywhere else.
events sits beside them: the durable runtime trace, with agent.ready, tool.started / tool.ended, provider.failed / provider.recovered, hold.started / hold.ended, session.error and more. It is where to look when the transcript reads fine and the call still went wrong. Everything below assumes you have that page open. See calls for the full field reference.

Finding the calls worth opening

outcome is the verdict post-call analysis reached; close_reason filters by how a call ended. To see which endings dominate rather than to list them, read endings from GET /v1/observability.

The playbook

Look at: the transcript, top to bottom, and specifically the function_call_output items above the bad line.The field that answers it: whether the wrong fact was already in the tool result. A tool that returned stale or empty data is a tool problem; a tool that returned the right data and an agent that said something else is a prompt problem.Then check, in this order:
  • Did a tool return it? If function_call_output carries the wrong value, fix the tool, not the prompt. Test it in isolation — see testing and publishing.
  • Did the agent have anything to answer from? If the fact should have come from a knowledge base, look for a knowledge_fetch call in the transcript. No call at all means the model did not think to make one: the knowledge map’s entry titles and summaries are what it routes on. If the call happened and came back without the fact, the page it needed may never have been transcribed, or may be sitting unplaced. See using knowledge in agents and editing.
  • Did the prompt invite it? An instruction that says what to do but never says what to do when the information is missing gets a plausible invention. Say “if you do not have it, say so” explicitly. See prompting.
  • Was it a variable that never resolved? Check session.userdata and the call’s vars. A template that resolved to nothing produces a sentence with a hole in it. See variables.
Do not fix this by adding fields to post-call analysis. Analysis is what a reader inferred after the fact; it never reaches a live call.
Look at: snapshot.response_latencyavg_ms, p95_ms, max_ms and samples. The p95 is the one that matters. A call that was sometimes slow is what callers complain about, and a mean hides it.The threshold ships on the response: snapshot.slow_response_threshold_ms is 3500. Past that, snapshot.issues says Slow responses: p95 time to first word was ….Then read the four stages, which are averaged independently and add up to it:Anything left over is transport and orchestration. A realtime agent has no separate STT or TTS leg, so those two stages are null on one — see realtime.Also check snapshot.slow_tool_calls_in_silence. A tool over slow_tool_threshold_ms (8000) with nothing being spoken over it is a caller sitting in silence. The fix is usually not a faster tool but a say operation before the slow step, or long_running_task on the tool. See speech operations and tools overview.snapshot.connection splits connection quality into agent and caller. A poor reading on the caller’s side is their network, not your stack, and it is reported separately for exactly that reason.
Look at: the function_call item in the transcript. data.name is the tool it picked and data.arguments is what it decided to send — not what your schema said, but what the model actually chose.The field that answers it: the tool’s description, and each property’s description in its json_schema. Those are the only instructions the model gets about a tool. If two tools’ descriptions could both plausibly cover the request, the model will pick one at random and you will see it here.The fixes, in order of how often they work:
  • Sharpen the descriptions. Say when to use the tool and when not to. Say the format each argument takes and what it means. See schema and description.
  • Split the tool. One capability per tool. A tool that does three things has a description that describes three things, and a model that guesses among them.
  • Look for an overlap. Two tools whose purposes shade into each other is the usual cause of “it called the wrong one”. A knowledge base plus a lookup tool that both answer “what are your hours” is the same problem.
  • Say it in the prompt. Ordering and preconditions (“always confirm the spelling before calling create_ticket”) belong in the system prompt, not in the tool description. See prompting.
If the call ended agent_start_failed instead, the model never got to choose: two attached tools presented the same name. Read session.error, which says which.
Look at: the function_call_output item, where data.is_error is true and data.output is what the model was told. Then events, for the tool.started / tool.ended pair with its timing.The counts are on the snapshot:The fixes:
  • Read what the caller heard. The model is handed the error text and speaks its own version of it. If the caller was told something misleading, the fix is the tool’s on_error handling, not the prompt. See tool failures.
  • Reproduce it outside a call. POST /v1/tools/{tool_id}/run executes the tree with arguments you supply and shows every operation’s result. See testing and publishing.
  • Check the credential. A {{secrets.NAME}} that does not exist raises rather than substituting an empty string, which is deliberate. See secrets.
  • Watch for the detached failure. A long_running_task tool acknowledges immediately and fails afterwards, so the model has already told the caller it is working on it. That failure appears on the trace and in the tool.failed webhook, not in the tool’s own return. See tool failures.
snapshot.provider_failures and provider_recoveries are the different question of a model provider failing over to its backup mid-call. See model fallbacks.
Look at: session.close_reason first. It answers who ended the call, and the answer is not what the name suggests.If it was the agent (user_initiated or task_completed), find the end_call in the tool tree that fired and read the condition above it in the transcript. An end_call operation reached by a branch whose condition is too loose is the usual cause — see end call. Check also that the goodbye is not the only thing on that path: an end_call with nothing before it hangs up mid-sentence.If it was the caller, read the two turns before the disconnect. A caller hanging up on the greeting is usually a wrong number or an unwanted call, and call analysis will have skipped it as too_short rather than counting it against your success rate.If it was transferred, nobody hung up: the call went to a person and our session ended there. session.transfer.outcome says whether it connected. Both the duration and the recording measure our part only. See transfers.The full list is in close reasons.
Look at: session.close_reason and its bucket. This is the one symptom where the bucket does most of the work, because it says whose problem it is.The four configuration reasons worth knowing by sight:
  • missing_published_definition / unpublished_agent_failedpublish the agent. A draft cannot answer a call. See versions.
  • insufficient_credits — top up. See pricing and credits.
  • prepare_failed — the definition would not compile for this call. Run validate on the agent; see validation.
  • agent_start_failed — it compiled and died before its first word. Read session.error on the call.
If there is no call record at all, the call never reached an agent. Check the number’s readinesslive is the only value that answers calls — and that a published voice agent is assigned to it. See phone numbers.
Look at: snapshot.interruptions, snapshot.false_interruptions and snapshot.false_interruptions_resumed.A false interruption is the agent stopping because it thought the caller had started speaking when they had not — a cough, a door, background speech. false_interruptions_resumed counts the ones where the agent picked its own sentence back up, which is what resume_false_interruption is for. A high false_interruptions with a low resumed count is the setting being off. See turn handling.Then check the audio path:
  • snapshot.noise_cancellation_failed — when true, the enhancer turned itself off and the call ran on raw audio, which makes every endpointing decision worse. It appears in snapshot.issues as Noise cancellation failed and the call ran on raw audio. See audio processing.
  • snapshot.connectionpoor or lost on the caller’s side explains clipping that has nothing to do with your configuration.
If the agent was cut off rather than the caller, look at snapshot.pace.talk_over_turns and pace.longest_agent_turn_ms. A very long agent turn is a monologue callers interrupt; shortening it in the prompt fixes more of this than any endpointing knob.If the whole call was cut off, check the duration. A PSTN call is force-ended at three hours, and a leg with no media for more than 120 seconds is dropped by the carrier side — which is what a transfer left on hold too long looks like.
Look at: config.conversation.context on the agent that answered. It defaults to none, which starts every call clean. Nothing else about memory matters until this is set.Then check, in order:
  • Is contact_key the same? Memory is keyed to the person, and contact_key is your stable id for them, not conversation_id — which is new on every call. Two calls with different contact_key values are two different people as far as the platform is concerned. See conversations.
  • Is analysis writing summaries? context: "summary" reads the summaries post-call analysis writes, and nothing else produces them. Publishing an agent that asks for "summary" with analysis or its summary switched off is refused — but analysis can be switched off after publishing, and then the summaries quietly stop. See call analysis.
  • Is initialize_userdata on? It is what carries what the agent learned about the person into the next call. It applies to summary and transcript only.
  • Was the history erased? session.content_deleted_at on the old calls, or a retention policy that has passed. Erasing a person’s last remaining call also erases what your agents remember about them. See data retention.
Full reference: conversation memory and userdata.
Look at: the cost section of GET /v1/observability before any single call. It splits the range by kind, provider and model, and the answer is normally one line dominating the rest.Then the things that surprise people:
  • Voicemail is billed as an answered call. There is no answering-machine detection, so an outbound call that reaches a greeting connects, runs, records and bills.
  • Hold time is inside the bill. snapshot.pace.hold_ms is time parked on hold music during a bridged transfer — inside the duration, inside the recording, and silent.
  • A re-analysis is charged again. Backfilling analysis inserts a new usage row rather than replacing the old one, because both model calls really happened. The platform fee does not move; your provider bill does.
  • Provider cost is not debited from credits. If your credit balance is falling faster than you expect, it is the per-minute platform fee, not model spend — that goes to your own providers directly.
Full breakdown: costs.

When the call record itself is the problem

Calls

Every field on the record this page reads.

Close reasons

Every ending, its bucket and its fix.