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 envelopeimport 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 envelopeA 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-tokenfor reconnects after expiry.