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

Session lifecycle

What happens when you create a session — states, ownership, ending semantics, and how to recover from conflicts.

A session is one live avatar interaction. This guide walks its life from create-and-start through ending, and the rules that protect it: per-call states, tenant ownership, and non-idempotent cleanup.

Life of a session

Create + start (one call)

POST /v1/sessions creates the session, starts rendering, and returns the client_token with stream metadata.

Connect + interact

The browser connects the media WebSocket and exchanges turns with the avatar.

End

Your backend posts /end — resources are released and the session is terminal.

Session states

The status field tracks where a session is. The lifecycle enum has exactly seven values:

Status Meaning
created Created and started in the single POST /v1/sessions call; ready for the WebSocket connect
activating Start is in flight — provider/render resources are being provisioned
active Rendering and interacting — media is flowing
ending An end call is in flight
ended Fully torn down (terminal)
ended_with_warnings Torn down, but cleanup reported a warning field (terminal)
failed Startup error — the session is dead (terminal)

Create-and-start in one call

The canonical create is a single atomic call. There is no separate start step in the canonical V0 flow:

curl -X POST "https://<orvyn-public-api-host>/v1/sessions" \
  -H "Authorization: Bearer $AVTR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider_type": "openai_realtime",
    "provider_config": {
      "openai_realtime": { "client_secret": "<ephemeral-client-secret>" }
    }
  }'

The response is a bare SessionResponse carrying everything the frontend needs:

  • session_id — save it; every session-scoped call uses it.
  • client_token — the short-lived media token, delivered exactly once (it never appears in a GET response).
  • expires_at — epoch ms when client_token expires.
  • transport + stream_metadata — the media-plane connection metadata.
{
  "session_id": "session:8f3c1a2b-...",
  "client_token": "<hmac-client-token>",
  "transport": "websocket_fmp4",
  "expires_at": 1718352300000,
  "status": "created",
  "stream_metadata": {
    "session_token": "<purpose-scoped-media-token>",
    "sfu_endpoint": null,
    "ice_servers": []
  }
}

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

Checking status

GET /v1/sessions/{session_id} returns the authoritative state as a bare SessionStatusResponse. It never includes client_token:

curl "https://<orvyn-public-api-host>/v1/sessions/$SESSION_ID" \
  -H "Authorization: Bearer $AVTR_API_KEY"
{
  "session_id": "session:8f3c...",
  "transport": "websocket_fmp4",
  "status": "active",
  "provider_type": "openai_realtime",
  "created_at": 1718352000000,
  "started_at": 1718352001000,
  "ended_at": 1718352182000
}
Field Always present? Meaning
session_id yes session:-prefixed id
transport yes auto, webrtc_sfu, or websocket_fmp4
status yes One of the seven lifecycle states
provider_type yes Provider selected at creation (e.g. openai_realtime)
created_at, started_at, ended_at when set Unix epoch milliseconds

Ending a session

End sessions from your backend:

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

A successful end returns a bare EndSessionResponse:

{
  "session_id": "session:8f3c...",
  "status": "ended",
  "ended_at": 1718352182000
}
Field Always present? Meaning
session_id yes session:-prefixed id
status yes ended or ended_with_warnings
ended_at yes Unix epoch ms when the session ended
warning only on ended_with_warnings What timed out or partially failed during cleanup

Append ?force=true to skip the graceful disconnect handshake and terminate backend resources immediately.

Session ownership

Every session is bound to the tenant that created it:

  • All session-scoped calls verify that the caller’s tenant matches the stored owner.
  • A session that doesn’t exist or belongs to another tenant returns the identical 404 AVTR_SESSION_NOT_FOUND — there is no way to distinguish them, and no information leaks.
  • Ownership is set once at creation and never changes.

Recovering a dropped connection

If the browser disconnects (network drop, sleep), don’t create a new session — mint a fresh token for the existing one:

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

Build the WebSocket URL from the fresh client_token (or stream_metadata.session_token) and continue. The 5-minute token TTL is the only thing that expired — the session itself is unaffected.

Error reference for this guide

Code Status Meaning
AVTR_SESSION_NOT_FOUND 404 Session doesn’t exist or belongs to another tenant (identical response either way)
AVTR_SESSION_ENDED 409 /end re-posted against an ended / ended_with_warnings session
AVTR_SESSION_CONFLICT 409 /end posted against a created / ending / failed session
AVTR_SESSION_STATE_UNAVAILABLE 503 Session state temporarily unavailable — retry with retry_after

The generated reference for each operation is authoritative — see POST /v1/sessions, GET /v1/sessions/{session_id}, and POST /v1/sessions/{session_id}/end.

Was this page helpful?