Skip to main content

Errors & Lifecycle

How errors are reported, when sockets close, and what you should do about it.

Error message shapes​

Errors come in two envelope shapes. You should be ready for both.

Structured errors (content_type: "json")​

Used at handshake time and for session-lifecycle errors. The code, message, and any extra fields live inside content.

{
"type": "error",
"content_type": "json",
"content": {
"code": "ERROR_CODE",
"message": "human-readable message",
"...": "additional fields per code"
}
}

Used for: INVALID_INPUT_FORMAT, INVALID_INPUT_SAMPLE_RATE, INVALID_OUTPUT_FORMAT, INVALID_OUTPUT_SAMPLE_RATE, INVALID_FRAME_DURATION_MS, QUOTA_EXCEEDED (at handshake), TIMED_OUT.

Plain-text errors (content_type: "text")​

Used for runtime errors during a session. content is a human-readable string. metadata.code is set on some errors but not all.

{
"type": "error",
"content_type": "text",
"content": "human-readable message",
"metadata": {
"code": "ERROR_CODE"
}
}

Used for: AUDIO_STREAM_NOT_STARTED, mid-session voice quota errors (no code field today), tool execution errors (no code field).

const code = msg.content_type === "json"
? msg.content?.code
: msg.metadata?.code;

const message = msg.content_type === "json"
? msg.content?.message
: msg.content;

After some errors the server closes the socket. Others are recoverable mid-session.

Error codes​

CodeWhenEnvelopeRecoverable?
INVALID_INPUT_FORMATaudioConfiguration.input.format is not audio/pcm or audio/opus.jsonFix the handshake; reconnect.
INVALID_INPUT_SAMPLE_RATEaudioConfiguration.input.sampleRate is not 8000 or 16000.jsonSame.
INVALID_OUTPUT_FORMATaudioConfiguration.output.format is not audio/pcm or audio/opus.jsonSame.
INVALID_OUTPUT_SAMPLE_RATEaudioConfiguration.output.sampleRate is not 8000 or 16000.jsonSame.
INVALID_FRAME_DURATION_MSOpus chosen on input or output, but the matching side's frameDurationMs is missing or not 20/40/60.jsonSame.
QUOTA_EXCEEDED (handshake)Daily quota already exhausted at session start. content includes current_usage, limit, remaining, percentage.jsonNo — wait until quota resets, or have an admin raise the cap.
QUOTA_EXCEEDED (mid-session, voice)Quota crossed during a voice turn. No code field is set today — match on the human-readable text or on metadata.percentage >= 100. metadata includes current_usage, limit, percentage.textNo.
AUDIO_STREAM_NOT_STARTEDBinary audio bytes were received before an audio_stream_start control message.text (metadata.code set)Send audio_stream_start first; resume streaming.
TIMED_OUTThe WebSocket reached its server-side TTL. content.retryAfterSeconds suggests how long to wait.jsonYes — reconnect (with the same conversationId to resume).

Quota error payload (handshake)​

{
"type": "error",
"content_type": "json",
"content": {
"code": "QUOTA_EXCEEDED",
"message": "Daily quota exceeded",
"current_usage": 102000,
"limit": 100000,
"remaining": 0,
"percentage": 102
}
}

Use these fields to render a friendly message in your UI rather than the raw error text.

Timeout error payload​

{
"type": "error",
"content_type": "json",
"content": {
"code": "TIMED_OUT",
"message": "Chat Session timed out. Please reconnect.",
"retryAfterSeconds": 5
}
}

Honor retryAfterSeconds. Your reconnect can pass the same conversationId to pick up where you left off — see Reconnect.

Close codes​

When the server closes the socket, you get a code and (optionally) a reason:

Close codeMeaningWhat to do
1000Normal close. The server uses 1000 for most error-and-close paths today, including malformed handshakes, audio-config validation failures, and QUOTA_EXCEEDED at handshake. Always read the preceding error message — don't assume 1000 means "everything was fine".Decide based on the error code, not the close code. Don't blindly retry on protocol errors; do retry on transient ones.
1001Going away. Used for TTL expiry (preceded by TIMED_OUT) and server shutdowns / deploys.Reconnect with backoff.
1003Unsupported data. Currently emitted only for unknown conversationType and unsupported voice models.Fix the client; don't blindly retry.
1008Policy violation — emitted on authentication failure.Refresh the JWT, reconnect.

You can also see standard browser/transport-level close codes (1006 etc.) — treat those as transient and reconnect.

note

Most error-driven closes use code 1000 today rather than a more specific code. The error message that precedes the close is the authoritative signal — your client should key off error.metadata.code (text envelopes) or error.content.code (json envelopes), not off the close code alone.

TTL​

The server-side WebSocket TTL is 930 seconds (15.5 minutes) by default. Operators can lower it; the hard ceiling is 1200 s (the ALB idle timeout).

Before close, the server sends:

{
"type": "error",
"content_type": "json",
"content": {
"code": "TIMED_OUT",
"message": "Chat Session timed out. Please reconnect.",
"retryAfterSeconds": 5
}
}

…then closes with 1001.

This isn't a sign of trouble — it's expected. Just reconnect.

Heartbeats​

The application protocol doesn't require explicit heartbeats. Browsers and standard WebSocket clients respond to transport-level pings automatically. The actual ping behavior is whatever the underlying ASGI server (Uvicorn) and load balancer (ALB) are configured for; the gateway itself doesn't add an application-layer ping/pong. The hard ceiling on idle is the ALB's 1200-second idle timeout.

Recoverable mid-session errors​

A few errors come without a close — the socket stays open and you can keep going:

  • AUDIO_STREAM_NOT_STARTED — you sent bytes too early. Send audio_stream_start, then resume the binary stream.
  • QUOTA_EXCEEDED — when checked between turns; you can stay connected (in case quota gets raised), but the agent won't reply until the quota frees up.

For everything else, expect a close to follow soon after the error.

Defensive client policy​

A solid pattern:

  1. Always handle error. Read content.code for json envelopes and metadata.code for text envelopes; show the human in content.message or content respectively.
  2. Always handle close. Decide retry vs. fatal based on the close code.
  3. Backoff on retries. 1, 2, 4, 8, 16 seconds caps at 5 attempts before giving up.
  4. Log metadata.code to telemetry. Helps operators spot patterns (recurring quota hits, audio format mismatches from a particular client build, etc.).
  5. Don't trust the agent has all the context after a reconnect — show the user a "reconnected" indicator if the conversation continues mid-stream.

Reference​

On this page