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

Quickstart

Get from zero to a live avatar session in five minutes — create the session, connect the stream, end it.

Create and start a live avatar session in one API call, connect your frontend to the media stream, and end the session from your backend. Everything below uses the six canonical /v1/ endpoints — you can copy-paste the whole flow.

Base URL

All examples use the placeholder below. Replace it with your assigned API host.

https://<orvyn-public-api-host>

The 60-second path

Create and start a session

One call creates the session, starts rendering, and returns a short-lived client_token — delivered exactly once.

Connect the WebSocket

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

Receive `session.ready` + media

The server sends session.ready, then fMP4 init and media segments.

End the session

Clean up from your backend to release GPU resources.

Step 1 — Create and start a session

The session must specify exactly one provider_type and a matching provider_config. Provider credentials are ephemeral — your backend mints a fresh token for every session and Orvyn holds it in memory only for the session lifetime. Never send provider credentials to the frontend.

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>" }
    }
  }'
const response = await fetch("https://<orvyn-public-api-host>/v1/sessions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AVTR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    provider_type: "openai_realtime",
    provider_config: {
      openai_realtime: { client_secret: "<ephemeral-client-secret>" },
    },
  }),
});
const session = await response.json(); // bare SessionResponse — no envelope
import os
import requests

response = requests.post(
    "https://<orvyn-public-api-host>/v1/sessions",
    headers={
        "Authorization": f"Bearer {os.environ['AVTR_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "provider_type": "openai_realtime",
        "provider_config": {
            "openai_realtime": {"client_secret": "<ephemeral-client-secret>"}
        },
    },
)
session = response.json()  # bare SessionResponse — no envelope

A successful response is a bare SessionResponse object — no envelope. The client_token is returned exactly once, here and nowhere else:

{
  "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": []
  }
}
Field What it is
session_id Stable id, always prefixed with session: — save it; every session-scoped call uses it
client_token Short-lived (5-minute) HMAC-signed token authorizing media-plane connections — exactly once, never in GET responses
expires_at Unix epoch milliseconds when client_token expires
transport auto, webrtc_sfu, or websocket_fmp4
stream_metadata.session_token Purpose-scoped media-plane token — an alternative ?token= value for the WebSocket
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

Step 2 — Connect the WebSocket

POST /v1/sessions already created AND started the session — there is no separate start step. Build the media WebSocket URL and append the token as the ?token= query parameter:

const wsUrl = `wss://<orvyn-public-api-host>/v1/sessions/${encodeURIComponent(
  session.session_id,
)}/ws?token=${encodeURIComponent(session.client_token)}`;

const ws = new WebSocket(wsUrl);
ws.binaryType = "arraybuffer";
// No auth headers — the ?token= value authorizes the connection on its own.

ws.onmessage = (event) => {
  if (typeof event.data !== "string") return; // binary audio frames
  const msg = JSON.parse(event.data);

  switch (msg.type) {
    case "session.ready":
      console.log("Avatar ready:", msg.avatarId);
      break;
    case "avatar.fmp4.init":
      console.log("fMP4 init segment received");
      break;
    case "avatar.fmp4.segment":
      console.log("Media segment", msg.sequence);
      break;
    case "session.ended":
      console.log("Session ended");
      break;
    case "error":
      console.error("Server error:", msg.code, msg.message);
      break;
  }
};

The full message catalog — lifecycle, fMP4 media, and speech pipeline messages — is documented in the WebSocket guide.

Step 3 — End the session

Always end the session from your backend to release resources:

curl -X POST "https://<orvyn-public-api-host>/v1/sessions/$SESSION_ID/end" \
  -H "Authorization: Bearer $AVTR_API_KEY"
await fetch(
  `https://<orvyn-public-api-host>/v1/sessions/${sessionId}/end`,
  {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.AVTR_API_KEY}` },
  },
);
requests.post(
    f"https://<orvyn-public-api-host>/v1/sessions/{session_id}/end",
    headers={"Authorization": f"Bearer {os.environ['AVTR_API_KEY']}"},
)

A successful end returns a bare EndSessionResponse:

{
  "session_id": "session:8f3c...",
  "status": "ended",
  "ended_at": 1718352182000
}

status is ended after graceful cleanup, or ended_with_warnings with an extra warning field when teardown timed out — the session is terminal either way.

Refreshing the client token

The token expires after 5 minutes. To reconnect after expiry (network drop, browser sleep), mint a fresh one:

curl -X POST \
  "https://<orvyn-public-api-host>/v1/sessions/$SESSION_ID/client-token" \
  -H "Authorization: Bearer $AVTR_API_KEY"
{
  "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": []
  }
}

Build the reconnect WebSocket URL from the fresh client_token (or stream_metadata.session_token), exactly as in step 2. Refreshing does not revoke previously issued tokens — they remain valid until their own expiry.

Handle the client token safely

The ?token= query parameter is a short-lived bearer credential (5-minute TTL). Anyone holding a valid token can connect to the session’s WebSocket.

  • Do build the WebSocket URL with the token and open the connection.
  • Do not log the token or the assembled URL, screenshot it, or store it in analytics or browser history.
  • Do call POST /v1/sessions/{session_id}/client-token for reconnects after expiry.

Next steps

Was this page helpful?