- Control-plane API —
originate,transfer,hangupand the other control calls run over tRPC to the control-plane API. Failures come back as an HTTP status plus aVoiceErrorin the SDK. Fix these in your integration code or credentials. - The call itself — a call that reaches the carrier can still end
unanswered, busy, or rejected. This outcome shows after the fact on the
CDR as a coarse
statusplus a raw Q.850 cause on the terminalclearedevent. It does not show as an exception on youroriginatecall. - Transport & tools — the media/data plane (WebTransport/MoQT) and any agent tool calls fail independently of the control plane. Connection state reports transport failures. Structured tool-error envelopes report tool failures.
When
originate returns success, the engine accepted the request and
started to dial. Success does not mean that the far end answered. Watch the
call lifecycle events (initiated → delivered → established → cleared) or the
CDR status to learn the outcome. See
Call lifecycle.Call-failure causes
Every call ends with acleared lifecycle event. The event carries a Q.850
cause and a talk duration. The CDR pipeline folds these into a coarse
status. The reports and CDR views read this status. The rule is simple:
any call with talk time > 0 is completed. The cause labels a
zero-duration call.
The CDR builder derives
status. duration_sec > 0 always wins and marks the
call completed, even for cause 16 with a very short talk time. The Q.850
cause classifies only zero-duration calls. Unmapped causes fall through to
failed. The CDR keeps the raw q850_cause (0 when unknown), so you can see
the exact carrier cause behind a coarse failed.cleared call you get
the coarse status, the raw q850_cause, the direction, ANI/DNIS, the trunk,
and the talk duration. When the call had engine-anchored media, you also get
per-call QoS: packet loss, jitter, and an
E-model MOS.
Auth errors
Control-plane calls authenticate with your API key and workspace (org) id. The SDK validates required inputs before it sends any request. If an input is missing, the SDK throws aVoiceError. If the API rejects a request, the SDK
shows the server’s HTTP status in the same VoiceError.
- TypeScript
- Python
SIP-side auth is a separate surface. Trunk and softphone REGISTER/INVITE
run through the SIP gateway’s internal registrar. The registrar uses
multidomain digest auth and a source-IP ACL. A rejected credential returns
401/403 on the SIP dialog, not a VoiceError. An INVITE from a source that
is not on the allowlist gets a 403 Forbidden answer. When STIR/SHAKEN policy
is set to reject, an INVITE with an invalid attestation gets a
438 Invalid Identity Header answer. A common trunk-registration gotcha:
un-seal sealed credentials before the digest computation, or the carrier
reports “Digest auth failed.” See
SIP trunking and
SIP trunk troubleshooting.Transport errors
The media/data plane is QUIC/HTTP-3 with MoQT over WebTransport on a single:443. Transport failures are connection-state transitions, not thrown
exceptions. The client auto-reconnects with capped backoff. The client also
replays your publishes and subscribes. Treat a transient Reconnecting as
normal.
For one-way or missing audio (a call that connected but has no media), the
fault is usually media-plane, not control-plane. See
One-way audio.
Tool errors
When an agent calls a tool, the runtime does not throw failures. The runtime returns a structured error envelope to the model as the tool result. The agent can then say “I can’t do that right now” or fall back to another tool. Every tool error has the same shape:string
The human-readable failure reason. This field is present only on failure. A
successful tool returns its raw result body (an empty result is normalized to
{}).HTTP and MCP tool timeouts are bounded: the runtime clamps the
timeout_ms
that you set to [100ms, 60000ms]. A tool that hangs returns the timeout
envelope. The tool does not stall the turn.Handling patterns
1
Separate acceptance from outcome
Treat a successful
originate as “dialing started.” Decide success from the
lifecycle events or the CDR status. Never decide success from the absence of
an exception.2
Branch on the coarse status, drill on Q.850
Route your retry logic on
status. busy and no-answer are often retryable;
rejected usually is not. Log the raw q850_cause, so you can later split a
bucket of failed calls by their real cause.3
Catch VoiceError at the control-plane boundary
Wrap control calls and inspect
err.message. The SDK folds both client-side
validation and the server HTTP status into one VoiceError. A 401/403 in
the message is an auth/entitlement problem, not a call problem.4
Treat transport state as advisory
Let the client auto-reconnect. Show an error to your user only after a
sustained
Failed. Remember that the browser SDK does not fall back to
WebSocket.5
Feed tool errors back to the model
The runtime already returns tool failures as
{ "error": … } to the agent. You
generally do not need to intercept them. Monitor them in the agent traces to
find a flaky endpoint or a client tool that is still preview.Related
Call lifecycle
The
initiated → delivered → established → cleared event flow that carries the Q.850 cause.Call not connecting
Diagnose INVITE-time failures, codec/SDP mismatches, and trunk rejects.
SIP & RTP debugging
Pull SIP traces and per-call QoS to see the real cause behind a failed call.
Agent tools
How you declare and invoke HTTP, MCP, client, and telephony tools.

