Realtime (WebSocket)
Hold a live text-and-audio session with a model over one WebSocket, in the OpenAI Realtime protocol, with the route, the usage and the final price reported by the gateway.
Connect
Open a WebSocket to /v1/realtime?model=MODEL with your API key in the Authorization: Bearer header (or x-api-key). Browsers, which cannot set headers on a WebSocket, pass it as the subprotocol openai-insecure-api-key.<key> together with realtime. A key in the query string is refused: URLs end up in logs. The handshake response carries x-request-id; the same id is on every event the gateway sends and on the session's request in your usage.
Before anything reaches a provider the session passes the same checks an HTTP request does: key scopes and expiry, test keys against production projects, source allow-lists, rate limits (a session counts as one request), your routing policy (data retention, regions, providers, your own keys), budgets and a hold on your balance.
Events
The session speaks the OpenAI Realtime event protocol in both directions: session.update, input_audio_buffer.append / commit, conversation.item.create, response.create and response.cancel from you; session.created, the response deltas and response.done (with its usage) back. Clients built for that protocol work unchanged; the beta interface is selected with the openai-beta.realtime-v1 subprotocol or the OpenAI-Beta: realtime=v1 header.
Refused, with an error event while the session continues:
- a session.update that names another model (model_switch_unsupported): open a new session instead;
- input audio transcription (input_audio_transcription) and transcription sessions: the provider bills them separately and they are not offered through the gateway yet;
- binary frames and events that are not JSON objects with a type.
Your organization's content guardrails apply to the text an event carries (message text, instructions, function output): a blocking policy refuses that event and sends nothing, a redacting policy rewrites it before it leaves, a flagging policy records it. Audio is never scanned or modified.
Gateway events
yelno.session arrives first with status: "open": the request id, the model, the provider, the endpoint, the route-plan versions and the session's limits. When the session ends and you are still connected, a second yelno.session with status: "closed" carries the reason, the duration, the usage and the final charge. Send {"type": "yelno.session.close"} to end a session and receive it. yelno.warning reports guardrail redactions and flags.
A refusal is an error event whose error is the standard error envelope (with event_id when it answers one of your events), followed by the close.
Close codes
How a session is charged
You pay the model's listed final price for what the session used: text and audio tokens as the provider reports them for each response, cached input at its cached price, and connected time where the model is priced per minute. Nothing is added on top.
- A hold is placed when the session opens and grows while it runs, so it always covers what the session has used plus a minute ahead. If your balance, credit limit or a budget cannot cover the next extension, you get an insufficient_funds error and the session closes with 4402, charged for what it used.
- The session is settled once when it ends, whatever ends it. A response still being generated is cancelled first and its usage collected; if the provider never reports it, the output relayed to you is charged as a disclosed estimate.
- A session that used nothing billable is not charged and its hold is returned.
Limits
A session lasts at most 30 minutes and ends after 5 minutes without an event from you while no response is in progress (the exact values for your deployment are in the opening yelno.session, under limits). Each open session holds one of your concurrency slots until it closes. A session also ends if the provider ends it; it is never resumed on another endpoint.