Errors, limits & security
The error envelope, retry guidance, rate limits, and the security rules every integration must follow.
Every failure has a stable, machine-readable identity — and every credential has a strict handling rule. This page is the operational summary; the generated API reference is authoritative for per-endpoint response codes.
The error envelope
All errors use the same shape, with the HTTP status preserved:
{
"success": false,
"error": {
"code": "not_found",
"message": "Session not found."
},
"timings": { "latency_ms": 123 }
}
Match on error.code, not on the message — codes are contract, messages are
prose.
Core error catalog
The codes you will actually meet in the canonical flow:
| Code | Status | Meaning | What to do |
|---|---|---|---|
missing_api_key |
401 | No Authorization header |
Send Authorization: Bearer <api_key> |
invalid_api_key |
403 | Key unrecognized or revoked | Check the key; create a new one if revoked |
invalid_body |
400 | Body is not valid JSON | Fix serialization |
invalid_session_id |
400 | Session id malformed | Ids start with session: |
not_found |
404 | No route matched, or session absent/not yours | Verify id and key ownership |
session_conflict |
409 | Session state conflicts with the operation | See the lifecycle guide |
rate_limited |
429 | Too many requests in the window | Wait Retry-After seconds, then retry |
internal_error |
500 | Unexpected server error | Retry with backoff; contact support if persistent |
modal_start_failed |
502 | Rendering backend rejected the start | Retry with a new session |
modal_unreachable |
500 | Rendering backend unreachable | Retry after a brief wait |
api_auth_not_configured |
503 | Server has no keys configured | Platform-team action required |
The full per-endpoint response sets — including the 4xx/5xx entries for each of the six operations — are rendered on their generated pages in the API reference.
Error taxonomy on the media WebSocket
The media WebSocket has its own error surface
({ "type": "error", "code": "...", "fatal": false } messages on the socket):
- Fatal (socket closes): only
invalid_session— recover by creating a new session. - Lifecycle (socket stays open): e.g.
avatar_not_found,session_already_active. - Pipeline, per-turn (session survives):
stt_failed,llm_failed,tts_failed,avatar_render_failed— the avatar may miss the turn, but the next one can succeed.
Details in the WebSocket guide.
Retries
- Retry with backoff:
429(afterRetry-After),500,502,503transport-class errors. - Do not blind-retry:
409conflicts mean the session state moved on — re-read the session status instead. - Idempotent-by-nature: status reads are always safe to retry.
- End is deliberately not idempotent: a
409on re-end means the session is already terminal — treat it as success in cleanup logic.
Rate limits
All /v1/ routes carry per-tenant abuse-control limits. They are safety nets,
not billing quotas — nothing is counted or billed.
| Category | Limit (per 60 s window) |
|---|---|
Session create (POST /v1/sessions) |
20 |
| Read (GET routes) | 120 |
| Cleanup (end calls) | Exempt |
Limits are enforced per tenant and per edge location, with a 60-second window.
When exceeded, requests return 429 rate_limited with a Retry-After header
carrying the remaining window:
{
"success": false,
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded for category \"session_create\". Retry after 60 seconds."
},
"timings": { "latency_ms": 2 }
}
Correlation ids for support
Every /v1/ response carries an X-Request-Id header (also in the timings
object of JSON responses). Supply your own on request to correlate across your
systems, and include it when contacting support — it links your request to the
platform’s server-side traces end to end.
Security rules
Credential boundaries
| Credential | Lives where | Never |
|---|---|---|
| API key | Backend secret / env binding | In frontend code, URLs, source, or logs |
Provider tokens (client_secret, access_token, …) |
Your backend, minted per session, passed in provider_config |
Sent to the browser, persisted, or logged |
WebSocket token (?token= in ws_url) |
The browser, for the connection only | Logged, screenshotted, or stored in analytics |
Provider credentials are memory-only
Provider credentials passed in POST /v1/sessions are held in memory for the
session lifetime only, then zeroed. They are never written to any database,
cache, file, or log. Your backend must mint a fresh provider token for every
session — treat provider tokens as short-lived by design.
Treat ws_url as a credential
The ?token= query parameter authorizes the media WebSocket on its own:
- Do open the connection with the URL as-is (no extra headers).
- Do use client-token refresh for reconnects.
- Do not log it, put it in error reports, or persist it in browser storage.
Key hygiene
- Create keys via
POST /v1/api-keys; the plaintext is shown exactly once. - Store only backend-side; rotate by revoking (
DELETE /v1/api-keys/{key_id}) and re-creating. - A revoked key stops working immediately.
Reporting a security concern
Found a potential vulnerability? Do not open a public issue. Contact the platform team directly through your account channel.