Documentation Index
Fetch the complete documentation index at: https://docs.opper.ai/llms.txt
Use this file to discover all available pages before exploring further.
POST /v3/realtime-sessions
Returns a single-use client_secret your browser uses to open a /v3/realtime WebSocket. Browsers can’t set an Authorization header on a WebSocket constructor, so this endpoint exists to bridge the gap: the customer’s backend authenticates with its normal API key, optionally pre-binds session config fields, and returns the ticket to the browser.
See the Realtime voice guide for the end-to-end flow.
Authentication
Standard bearer auth with a project-scoped runtime API key. Management keys (opmak-…) are rejected.
Authorization: Bearer <api-key>
Request body
| Field | Type | Description |
|---|
config | object | Optional. Any non-zero fields are bound to the ticket and cannot be overridden when the browser sends session.start or session.update. See session config reference. |
locked_fields | string[] | Optional. Names of config fields that must be enforced even when their value in config is the zero value — use this to force a boolean off (e.g. ["output_transcription"] with config.output_transcription = false prevents the browser from enabling it). Field names match the JSON tag on the config schema. A locked_fields list with no config at all is a valid “unconditionally forbid these” shape: each listed field is force-set to its zero value. Unknown field names are rejected with 400. |
ttl_seconds | integer | Optional. Ticket lifetime in seconds. Default 60, max 300. Values outside the range are clamped. |
Pre-binding
Bound fields win over whatever the browser sends — this is the safety guarantee tickets provide. Recommended minimum binding: model. Tighter setups also bind instructions, tools, and voice.
{
"config": {
"model": "openai/gpt-realtime-2",
"voice": "marin",
"instructions": "You are a concise voice assistant.",
"tools": [
{ "name": "lookup_order", "description": "...", "parameters": { /* ... */ } }
]
},
"ttl_seconds": 60
}
Response
| Field | Type | Description |
|---|
client_secret | string | Opaque single-use ticket. Treat as a short-lived bearer credential and discard after one WebSocket upgrade. |
expires_at | string (RFC3339) | UTC timestamp after which the ticket is rejected even if unused. |
ws_url | string | Optional. Pre-built WebSocket URL including the ticket query string. May be empty. |
{
"client_secret": "Z2VtaW5pLWFkLWhvY...",
"expires_at": "2026-05-13T15:32:00Z",
"ws_url": "wss://api.opper.ai/v3/realtime?ticket=Z2VtaW5pLWFkLWhvY..."
}
Errors
| Status | Cause |
|---|
400 Bad Request | Invalid JSON; pre-bound model is unknown or not a realtime model; locked_fields contains a name that doesn’t match any config field (typo guard — error names the offending value). |
401 Unauthorized | Missing, non-runtime, or project-less API key. |
503 Service Unavailable | Realtime tickets not enabled on this deployment. |
Redemption
The browser presents the ticket on the WebSocket upgrade. Two transports are accepted; prefer the subprotocol header — credentials in the URL query string end up in access logs, browser history, and Referer headers, while the subprotocol header is request-only.
Recommended (subprotocol header):
GET /v3/realtime
Sec-WebSocket-Protocol: opper-ticket.<client_secret>
new WebSocket("wss://api.opper.ai/v3/realtime", [`opper-ticket.${clientSecret}`]);
Fallback (query parameter) — only for environments that can’t set a subprotocol:
GET /v3/realtime?ticket=<client_secret>
Replays return 401.
See also