Model APIs / Realtime

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.

GET/v1/realtime?model=…WebSocketinference:invoke
Which models. Realtime is available only on models whose provider offers it directly. A model that supports it lists realtime in its operations (GET /v1/models, and a Realtime badge on its catalog page); a model without it answers 4422 with the route that does serve it. A session runs on one endpoint from start to end: it is never moved to another provider mid-conversation. The Realtime tab of the catalog lists every such model, and the console playground opens a voice session with any of them.

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.

Connect
import asyncio, json, os
import websockets  # pip install websockets

URL = "wss://yelno.ai/v1/realtime?model=MODEL"

async def main():
    headers = {"Authorization": f"Bearer {os.environ['YELNO_API_KEY']}"}
    async with websockets.connect(URL, additional_headers=headers) as ws:
        await ws.send(json.dumps({"type": "conversation.item.create", "item": {
            "type": "message", "role": "user",
            "content": [{"type": "input_text", "text": "Say hello in one sentence."}]}}))
        await ws.send(json.dumps({"type": "response.create"}))
        async for message in ws:
            event = json.loads(message)
            if event["type"] == "response.done":
                await ws.send(json.dumps({"type": "yelno.session.close"}))
            elif event["type"] == "yelno.session" and event["status"] == "closed":
                print(event["cost"])  # {"state": "final", "currency": "USD", "customer_nanos": ...}

asyncio.run(main())

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.

yelno.session · closed
{
  "type": "yelno.session",
  "status": "closed",
  "request_id": "req_…",
  "model": "MODEL",
  "provider": "…",
  "reason": "client_requested",
  "duration_seconds": 42,
  "responses": 3,
  "request_state": "financially_finalized",
  "usage": { "units": { "input_uncached": 812, "input_audio": 1290, "output": 96, "output_audio": 2410, "session_second": 42 }, "source": "provider_final" },
  "cost": { "state": "final", "currency": "USD", "customer_nanos": 218752000 }
}

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

CodeMeaning
1000Normal end: you sent yelno.session.close, or the provider ended the session.
4400Invalid request, for example no model in the URL or a key in the query string.
4401Authentication: missing, invalid or expired key, or the provider refused the platform credential.
4402Insufficient funds: the opening hold or a later extension could not be placed, a budget is exhausted, or the session reached your price ceiling.
4403Permission: scope, test key on a production project, suspension, or a source address outside an allow-list.
4408The session reached its maximum duration or its idle timeout.
4422No route: the model is not offered for realtime, or your policy leaves no eligible endpoint.
4429Rate limit or concurrency limit.
4502 · 4503The provider failed or could not be reached.
1011Internal error.

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.