Skip to main content

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

FieldTypeDescription
configobjectOptional. 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_fieldsstring[]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_secondsintegerOptional. 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

FieldTypeDescription
client_secretstringOpaque single-use ticket. Treat as a short-lived bearer credential and discard after one WebSocket upgrade.
expires_atstring (RFC3339)UTC timestamp after which the ticket is rejected even if unused.
ws_urlstringOptional. 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

StatusCause
400 Bad RequestInvalid 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 UnauthorizedMissing, non-runtime, or project-less API key.
503 Service UnavailableRealtime 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