Cosmo Realtime SDK
Concepts

Errors

Full RealtimeErrorCode table — when each error fires and how to recover.

The server sends error frames over the data channel. Each frame has a code, message, and fatal flag. The SDKs additionally raise typed exceptions for local failures (session start, dialing, sends before connect).

Server error codes

These come from the wire protocol (RealtimeErrorCode).

CodeFatalWhen it firesRecovery
auth_failedyesAPI key or minted token missing, expired, or invalidRefresh the credential. Do not retry with the same one.
workspace_forbiddenyesThe authenticated credential does not have access to the requested resourceVerify the key belongs to the workspace you think it does.
voice_disabledyesThe workspace does not have realtime voice enabledEnable voice in the dashboard or contact support.
upstream_disconnectnoThe upstream model session dropped (go-away or network error)The server attempts transparent reconnect (you may see a reconnecting event). If fatal: false, the session may continue. If fatal: true, start a new session.
internal_errorvariesUnexpected server-side failureRetry. If persistent, file a support issue with the session_id.
invalid_messagenoThe client sent a malformed or unrecognized control messageCheck SDK version. If hand-crafting messages, validate against the protocol schema.
version_mismatchyesThe client's version field is incompatible with the server's protocol versionUpgrade the SDK.

Typed session-start rejections (e.g. unknown_server_tool, invalid_tool_config, version_mismatch) arrive as non-2xx REST responses instead, carried in the standard error envelope (error.type, error.code, error.message) — the SDKs surface them as start failures, not error frames.

TypeScript client-side error codes

The TypeScript SDK maps server codes and local failures to its own RealtimeErrorCode union:

CodeSourceMeaning
mic_deniedclientBrowser refused microphone permission
screen_deniedclientBrowser refused screen share permission
screen_start_failedclientScreen share could not be started (no video track, etc.)
session_start_failedclientSession-start HTTP call failed
session_rejectedclientServer rejected the session (4xx response)
auth_errormapped from auth_failed / workspace_forbiddenAuthentication or authorization failure
not_readyclientsendText(), setMuted(), etc. called before ready
transport_connect_timeoutclientLiveKit room join timed out
transport_disconnectmapped from upstream_disconnectTransport closed unexpectedly
unsupported_browserclientWebRTC not available in this browser
server_errormapped from internal_error etc.Generic server-side failure

Subscribing to errors

TypeScript

session.on('error', (error) => {
  if (error === null) return;  // null = error cleared on recovery
  console.error(`[${error.code}] ${error.message}`);

  if (error.code === 'mic_denied') {
    // Show mic permission instructions
  } else if (error.code === 'auth_error') {
    // Refresh the credential
  } else if (error.code === 'transport_disconnect') {
    // The session is gone — offer a "reconnect" action that starts a new one
  }
});

useRealtimeError() is the React hook equivalent:

import { useRealtimeError } from 'cosmo-ai';

function ErrorBanner() {
  const error = useRealtimeError();
  if (!error) return null;
  return <div role="alert">{error.message}</div>;
}

Pythonerror frames arrive as RealtimeError events on the session stream; local failures raise typed exceptions:

from cosmo_ai import (
    DialError,
    NotConnectedError,
    RealtimeError,
    SessionStartError,
)

try:
    async with agent.start() as session:
        async for event in session:
            if isinstance(event, RealtimeError):
                print(f"[{event.code}] {event.message} (fatal={event.fatal})")
                if event.fatal:
                    break   # the terminal session-ended item follows
except SessionStartError as exc:
    print(f"session start rejected: [{exc.code}] {exc}")
  • SessionStartError — the session-start REST call was rejected; code carries the server's rejection code (VersionMismatchError is the version_mismatch subclass).
  • DialError — a session.dial(...) call failed; code carries the rejection code.
  • NotConnectedError — a send (send_text, set_muted, …) was attempted on a session that is not connected.

Swifterror frames are .error cases on the event stream; start failures throw from start(_:config:):

do {
    let session = try await RealtimeSession.start(options, config: config)
    for try await event in session.events {
        if case .error(let e) = event {
            print("server error [\(e.code.rawValue)] \(e.message) fatal=\(e.fatal)")
            // e.fatal == true → the terminal .sessionEnded follows; start a new session
        }
    }
} catch let error as RealtimeSessionError {
    // e.g. .handshakeFailed(status:code:detail:), .insecureBaseURL
    print("start failed: \(error)")
}

fatal flag

fatal: true means the session is dead and cannot continue — the stream will finish with its terminal session-ended item; start a new session. fatal: false means this turn or operation failed but the session itself is still alive — you may continue speaking.

Recovery recipes

auth_failed or workspace_forbidden

These are almost always configuration errors. Check:

  1. The API key (or minted token) is valid and not expired.
  2. The key has the voice:use scope.
  3. The key belongs to the workspace whose agents and tools you are referencing.

upstream_disconnect (non-fatal)

The server attempts a transparent upstream-session rotation. The client receives reconnecting on the data channel; the session and transport survive. If instead the frame was fatal: true, start a new session (optionally with resume_session_id).

version_mismatch

Upgrade the SDK. The protocol version is pinned at "1.0". If your SDK sends a different version, it is out of date. See Protocol version.

invalid_message

If you are using a supported SDK version and not hand-crafting messages, file a bug. If you are hand-crafting, validate your JSON against the OpenAPI schema at sdks/cosmo-realtime/external-openapi.json.

Pitfalls

  • The error event in TypeScript fires with null when the error is cleared (e.g. on the next session start). Handlers must check for null: if (error === null) return;.
  • transport_disconnect in TypeScript does not map 1:1 to the server's upstream_disconnect. The TS code maps both upstream_disconnect and network-level closes to transport_disconnect.
  • The wire RealtimeErrorCode is the server enum; it does not include client-side codes like mic_denied (those only exist in the TypeScript SDK).
  • A non-fatal error never ends the session — do not tear down on every frame. Gate teardown on fatal, the session_ended event, or the lifecycle reaching disconnected.

See also

  • Sessions — the version field on session-config for version negotiation
  • Lifecycle — when error fires in the event sequence, and the typed disconnect reasons
  • Protocol version — how breaking changes work

On this page