Skip to content
Documentation
Esc
navigateopen⌘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 stream metadata — status created.

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:

Status Meaning
created Created and started in the single POST /v1/sessions call; ready for the WebSocket connect
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 carries everything the frontend needs:

  • session_id — save it; every session-scoped call uses it.
  • stream.ws_url — the ready-to-use WebSocket URL with the embedded short-lived token.
  • stream.token_expires_at — when the token expires.
{
  "success": true,
  "data": {
    "session_id": "session:8f3c1a2b-...",
    "status": "created",
    "created_at": 1718352000000,
    "stream": {
      "transport": "websocket_fmp4",
      "available_transports": ["websocket_fmp4"],
      "ws_url": "wss://<orvyn-public-api-host>/v1/sessions/session:.../ws?token=eyJ...",
      "token_expires_at": 1718352300000
    }
  },
  "timings": { "latency_ms": 9 }
}

Checking status

GET /v1/sessions/{session_id} returns the authoritative state and timings:

curl "https://<orvyn-public-api-host>/v1/sessions/$SESSION_ID" \
  -H "Authorization: Bearer $AVTR_API_KEY"
{
  "success": true,
  "data": {
    "session_id": "session:8f3c...",
    "status": "active",
    "created_at": 1718352000000,
    "started_at": 1718352001000,
    "ended_at": null,
    "stream": { "transport": "websocket_fmp4", "ws_url": "...", "status_url": "..." }
  },
  "timings": { "latency_ms": 45 }
}

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"
{
  "success": true,
  "data": {
    "session_id": "session:8f3c...",
    "status": "ended",
    "end_ok": true
  },
  "timings": { "latency_ms": 1820 }
}

On graceful cleanup the response carries status: "ended". If backend cleanup timed out or partially failed, it carries status: "ended_with_warnings" with a warning field describing what happened — the session is still terminal.

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 not_found — there is no way to distinguish them, and no information leaks.
  • Ownership is set once at creation and never changes.

Duplicate and racing calls

The lifecycle is hardened against races:

  • A start already in progress makes concurrent lifecycle calls return 409 session_starting until it resolves.
  • Ending a session while its start is still in flight cancels the start cleanly — the start returns 409 session_cancelled and the session stays ended. There is no resurrection.
  • Cleanup calls (end) are exempt from rate limits — you can always end a session even if you have exhausted other limits.

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"

Connect the returned ws_url 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
not_found 404 Session doesn’t exist or belongs to another tenant (identical response either way)
invalid_session_id 400 Session id is malformed
session_conflict 409 Session in a conflicting state for the requested operation
session_starting 409 A start is already in progress
session_cancelled 409 The start was cancelled by an end call

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?