Skip to main content

Errors and retries

Which failures are worth retrying, and which are not. Worth deciding before you write the retry loop.

HTTP status codes

CodeMeaningWhat to do
200Success
400Invalid parameters, or a rejected SDP/ICE payloadFix the request. Do not retry unchanged
401Missing or invalid API keyCheck the header — see Authentication
402Not enough credit to startSurface a top-up prompt. Retrying will not help
404Unknown or already-ended resourceFor a realtime session, start a new one
422Malformed bodyFix the request
500Server errorRetry with backoff
503Capacity or service unavailableRetry with backoff — see below

Two body shapes, both of which you have to handle

Most errors return a plain string in detail:

{ "detail": "Session not found: 550e8400-e29b-41d4-a716-446655440000" }

Conditions that carry a stable machine-readable code return an object instead:

{ "detail": { "code": "at_capacity", "message": "Realtime service is at capacity; please try again shortly" } }

So detail is a string or an object. Branch on the type:

const body = await response.json().catch(() => ({}));
const code = typeof body.detail === "object" ? body.detail?.code : undefined;
const message = typeof body.detail === "object" ? body.detail?.message : body.detail;

Branch on code, never on message — the wording is free to change.

At capacity is not an outage

Realtime analysis has finite concurrent capacity. When none is free, POST /v2/realtime/session returns 503 with code: "at_capacity".

This is a designed state. Show it as "busy, try again in a moment", not as an error. Retry with backoff — and not in a tight loop, which will not get you in sooner.

Two things that surprise people:

  • GET /v2/realtime/health keeps reporting healthy throughout. It reports whether the analysis service is reachable, not whether it has a free slot. A healthy response can be followed immediately by an at_capacity create.
  • Session creation can be slower than a typical REST call under load, because it reserves analysis capacity and mints TURN credentials. Set a generous client timeout.

Running out of credit mid-session

A realtime session can be ended by the server while streaming, if the balance is exhausted. You receive an error frame on the results socket:

{
"type": "error",
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"error": "Out of credits — realtime session ended",
"code": "insufficient_credits"
}

Two consequences:

  • Do not call /end afterwards. The session is already torn down, so that call 404s. The error frame is the end of the session.
  • This is the one path where a session ends without your asking. Handle it distinctly from a network drop, or your UI shows a hung stream.

Losing the connection

What happenedHow you see itRecovery
WebSocket dropsonclose with no error frameNot resumable. End the session, create a new one
WebRTC media stopsPeer connection state goes disconnected / failedNot resumable. End the session, create a new one
No frames arrivingSocket open, nothing receivedCheck the peer connection actually reached connected. An open WebSocket says nothing about whether media is flowing

There is no reconnect and no replay anywhere in the realtime API. Every recovery path is the same: end, create, renegotiate.

WebSocket close codes

The results socket refuses a connection with 1008 and one of these reasons: Missing authentication token, Invalid or expired token, Missing session_id query parameter, or Insufficient credits for realtime session.

Read the close reason — the four cases need different responses, and only the reason string distinguishes them.