Errors, limits & security
The flat error shape, the AVTR_* error catalog, rate-limit headers, 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 shape
Every 4xx/5xx response uses the flat AVTR error shape — no envelope, no nested
error object:
{
"code": "AVTR_SESSION_NOT_FOUND",
"message": "Session not found.",
"request_id": "3f2a9c1e-...",
"doc_url": "https://<orvyn-public-api-host>/docs/errors"
}
| Field | Present | Meaning |
|---|---|---|
code |
always | Stable AVTR_* identifier — match on this, never on message |
message |
always | Human-readable, provider-agnostic prose |
request_id |
always | Correlation UUID; echoes the X-Request-Id response header |
doc_url |
always | Absolute HTTPS URL documenting this code |
retry_after |
retryable codes only | Seconds to wait (1–3600) before retrying |
retry_after is required on retryable codes and forbidden on
non-retryable codes — its presence tells you which kind you got. Honor the
value and apply jitter when retrying.
Error catalog
All codes carry the AVTR_ prefix. The taxonomy has two classes:
Retryable codes (retry_after required)
| Code | Typical situation | What to do |
|---|---|---|
AVTR_PROVIDER_DOWNSTREAM_FAILED |
Upstream provider returned an error or a downstream step failed | Retry after retry_after |
AVTR_PROVIDER_TIMEOUT |
Provider call timed out | Retry after retry_after |
AVTR_PROVIDER_UNAVAILABLE |
Provider unavailable, session state unavailable, or capacity exhausted | Retry after retry_after |
AVTR_RATE_LIMIT_CONCURRENT_SESSIONS |
Concurrent-session limit hit | Wait, then retry |
AVTR_RATE_LIMIT_EXCEEDED |
Too many requests in the window | Honor Retry-After, then retry |
AVTR_RATE_LIMIT_PROVIDER_QUOTA |
Provider quota exhausted | Wait, then retry |
AVTR_RENDER_CAPACITY |
Rendering capacity exhausted | Retry after retry_after |
AVTR_RENDER_TIMEOUT |
Provider or render timeout | Retry after retry_after |
AVTR_SESSION_STATE_UNAVAILABLE |
Session state temporarily unavailable | Retry after retry_after |
AVTR_SFU_CONNECTION_FAILED |
SFU connection failed | Retry after retry_after |
AVTR_SFU_NEGOTIATION_FAILED |
SFU negotiation failed | Retry after retry_after |
AVTR_SFU_ROOM_FULL |
SFU room at capacity | Retry after retry_after |
Non-retryable codes (retry_after forbidden)
| Code | Typical situation | What to do |
|---|---|---|
AVTR_AUTH_EXPIRED_TOKEN |
Bearer credential expired | Obtain a fresh credential |
AVTR_AUTH_FORBIDDEN |
Credential recognized but not permitted — API keys: key lacks the caller’s required permission; Clerk: valid token rejected by the authorized-parties (azp) allowlist |
Use a key with the required permission / present the token to an allowed origin |
AVTR_AUTH_CLERK_ORG_UNMAPPED |
Valid Clerk credential but the Clerk org is not mapped to an AVTR org | Map the org or contact support |
AVTR_AUTH_INVALID_KEY |
Key unrecognized or revoked | Check the key; create a new one if revoked |
AVTR_AUTH_MISSING_CREDENTIALS |
No key provided | Send Authorization: Bearer <api_key> or x-api-key |
AVTR_AUTH_MISSING_SCOPE |
Key lacks the route’s scope | Re-create the key with the needed scope |
AVTR_PROVIDER_MISCONFIGURED |
provider_type / provider_config mismatch or unknown provider |
Fix the request body |
AVTR_RENDER_FAILED |
Rendering failed terminally | Create a new session |
AVTR_RENDER_MISCONFIGURED |
Render configuration invalid | Fix the session configuration |
AVTR_SESSION_CANCELLED |
Session was cancelled | Create a new session |
AVTR_SESSION_CONFLICT |
/end posted against created / ending / failed |
Treat as cleanup-success or re-read status |
AVTR_SESSION_ENDED |
/end re-posted against ended / ended_with_warnings |
Treat as success in cleanup logic |
AVTR_SESSION_NOT_FOUND |
Session absent — or belongs to another tenant (identical response either way) | Verify the id and key ownership |
AVTR_SFU_NOT_CONFIGURED |
SFU requested but not available for the account | Use another transport |
The per-endpoint status codes are documented on each generated reference page; the catalog above tells you which class a code belongs to and how to react.
Error taxonomy on the media WebSocket
The media WebSocket has its own error surface
({ "type": "error", "code": "...", "fatal": false } messages on the socket) —
separate from the REST taxonomy above:
- 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: retryable codes — the response tells you so by
carrying
retry_after(and429responses carry theRetry-Afterheader). Honor the value, add jitter. - 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
Every /v1/ response carries rate-limit headers:
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
Maximum requests allowed in the current window |
X-RateLimit-Remaining |
Requests remaining in the current window |
X-RateLimit-Reset |
Unix epoch seconds at which the window resets |
When a request exceeds the limit it returns 429 with a Retry-After header —
seconds until the rate-limit window resets — and a flat error body whose
code is one of the rate-limit codes above (AVTR_RATE_LIMIT_EXCEEDED,
AVTR_RATE_LIMIT_CONCURRENT_SESSIONS, or AVTR_RATE_LIMIT_PROVIDER_QUOTA),
with the matching retry_after value in the body:
{
"code": "AVTR_RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Retry later.",
"request_id": "3f2a9c1e-...",
"doc_url": "https://<orvyn-public-api-host>/docs/errors",
"retry_after": 30
}
Correlation ids for support
Every /v1/ response carries an X-Request-Id header, and error responses
echo the same value as request_id in the error body
(ErrorResponse.request_id). Success bodies are bare schema objects and carry
no such field.
Supply your own on request (1–128 characters of [a-zA-Z0-9_-]) 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 |
Client token (?token= on the media WebSocket) |
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 the client token as a credential
The ?token= query parameter authorizes the media WebSocket on its own:
- Do build the WebSocket URL with the token and open the connection as-is (no extra headers).
- Do use client-token refresh for reconnects.
- Do not log the token or the assembled URL, 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.