Skip to content
Documentation
Esc
↑↓navigate↵open⌘Jpreview
On this page

Authentication

API keys for the control plane, short-lived tokens for the media plane — what to use where, and how to handle both safely.

Every Orvyn API surface is authenticated, with two different credential types for two different planes:

Credential Where it is used Lifetime
API key All six /v1/ REST endpoints (headers) Long-lived, admin-managed
Client token The media WebSocket (?token= query parameter) Short-lived, 5 minutes

API key authentication

All /v1/ routes accept your API key via either header:

Authorization: Bearer <api_key>
x-api-key: <api_key>

Where keys come from

API keys are created and revoked through the admin surface of the API itself:

  • POST /v1/api-keys — create a key. The plaintext is shown exactly once at creation; Orvyn stores only a SHA-256 hash.
  • DELETE /v1/api-keys/{key_id} — revoke a key. Revocation takes effect immediately; keys do not expire on their own.

Auth errors

Every error body is the flat AVTR ErrorResponse:

{
  "code": "AVTR_AUTH_MISSING_CREDENTIALS",
  "message": "Missing API credentials.",
  "request_id": "3f2a9c1e-...",
  "doc_url": "https://<orvyn-public-api-host>/docs/errors"
}

Auth error codes, per the binding contract:

Status code Meaning
401 AVTR_AUTH_MISSING_CREDENTIALS No credential provided
401 AVTR_AUTH_INVALID_KEY Credential invalid, unrecognized, revoked — or (Clerk) rejected by the authorized-parties (azp) allowlist
401 AVTR_AUTH_EXPIRED_TOKEN Bearer token expired
403 AVTR_AUTH_FORBIDDEN Credential recognized but not permitted
403 AVTR_AUTH_MISSING_SCOPE Scope missing for this endpoint
403 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

See errors, limits & security for the full catalog and the retry_after rules.

Client token authentication

The media WebSocket (GET /v1/sessions/{session_id}/ws) is not API-key authenticated. It uses a short-lived HMAC-signed token passed as the ?token= query parameter:

wss://<orvyn-public-api-host>/v1/sessions/{session_id}/ws?token=...

Token properties

Property Value
Signing HMAC-SHA256
TTL 5 minutes (300 seconds)
Payload Session id, tenant id, purpose — never the raw API key
Binding HMAC-bound to one session and one tenant

The token is the authorization: anyone holding a valid token can connect to that session’s WebSocket. This is by design, but it means the token (and any URL you assemble with it) must be handled like a credential.

Token lifecycle

You receive the first token — client_token — in the POST /v1/sessions response, delivered exactly once (expires_at carries its epoch-ms expiry). For reconnects after the 5-minute expiry, mint a fresh one:

curl -X POST \
  "https://<orvyn-public-api-host>/v1/sessions/$SESSION_ID/client-token" \
  -H "Authorization: Bearer $AVTR_API_KEY"

The response is a bare ClientTokenResponse:

{
  "client_token": "<fresh-hmac-client-token>",
  "expires_at": 1718352600000,
  "transport": "websocket_fmp4",
  "stream_metadata": {
    "session_token": "<purpose-scoped-media-token>",
    "sfu_endpoint": null,
    "ice_servers": []
  }
}
Field Meaning
client_token Freshly-minted short-lived (5-minute) client token
expires_at Unix epoch milliseconds when the new token expires
transport Transport for this session: auto, webrtc_sfu, or websocket_fmp4
stream_metadata.session_token Purpose-scoped media-plane token — an alternative ?token= value
stream_metadata.sfu_endpoint SFU publisher endpoint; null on the websocket_fmp4 transport
stream_metadata.ice_servers ICE servers for WebRTC negotiation; may be empty for websocket_fmp4

Build the WebSocket URL as wss://<orvyn-public-api-host>/v1/sessions/{session_id}/ws?token=<client_token> with the fresh token.

What to never put in a URL

Never in a URL Use instead
API keys (Authorization values) Headers on REST calls
Long-lived provider credentials provider_config in POST /v1/sessions, minted per session by your backend
The client_token (or an assembled media URL) in logs/analytics Build the URL, open the connection, then discard both

Request correlation

Every /v1/ response includes an X-Request-Id header for tracing. You may supply your own (1–128 characters of [a-zA-Z0-9_-]); it is echoed back when valid, otherwise a fresh id is generated. Error responses echo the same id as request_id in the error body (ErrorResponse.request_id); success bodies are bare schema objects and carry no such field.

Response shape contract

Successful responses are bare schema objects — the exact shape documented for each endpoint (SessionResponse, SessionStatusResponse, EndSessionResponse, ClientTokenResponse, …). There is no envelope:

{
  "session_id": "session:8f3c...",
  "status": "active"
}

Errors keep the flat AVTR shape — {code, message, request_id, doc_url} plus retry_after on retryable codes only — see errors, limits & security.

Was this page helpful?