Realtime conversations
GET /v1/realtime?model= — OpenAI's Realtime API for speech-to-speech and text conversations.
On this page
Hold a live voice (or text) conversation over a WebSocket. The endpoint speaks OpenAI's Realtime API
(the GA interface, session type: "realtime"), so OpenAI's SDKs and realtime clients work unchanged;
the public model must support audio.converse.realtime. Today that means:
- Gemini's Live models:
gemini-3.8-live,gemini-3.8-live-extended-thinking,gemini-3.1-flash-live-preview,gemini-2.5-flash-native-audio-preview-12-2025, andgemini-3.5-live-translate-previewfor speech-to-speech translation. - OpenAI's realtime models:
gpt-realtime-2.1,gpt-realtime-2.1-mini,gpt-realtime-2andgpt-realtime-1.5, through OpenAI's own Realtime API.
Every model's catalog profile says what it accepts, and a session that asks for more is refused
with an error naming the field. Most of this page describes Gemini, whose protocol differs from
OpenAI's; OpenAI models lists what changes for them. For transcription only, see
Realtime transcription.
Connecting
Open a WebSocket to $GATEWAY/v1/realtime?model=<public-model>. Authenticate with your virtual key
in Authorization: Bearer, or, from a browser, which cannot set headers, as the subprotocol
openai-insecure-api-key.<key> next to realtime, the way OpenAI's browser clients do. A browser
should not hold your virtual key: give it a client secret instead.
The gateway answers with session.created. A connection without model, or with an intent other
than realtime, is refused with 400 before the upgrade. The model cannot change for the life of
the connection.
The provider session opens with the first event that needs it (audio, an item, or
response.create), so session.updates sent first configure it instead of reconnecting it.
Client secrets
Your backend mints a short-lived key for the browser with POST /v1/realtime/client_secrets, as
with OpenAI:
curl $GATEWAY/v1/realtime/client_secrets \
-H "Authorization: Bearer $AI_GATEWAY_KEY" -H "Content-Type: application/json" \
-d '{"expires_after": {"anchor": "created_at", "seconds": 600},
"session": {"type": "realtime", "model": "voice-agent", "instructions": "Be brief."}}'The answer carries value (ek_…), expires_at and the rendered session. The browser connects
with value in place of the key, and needs no ?model=: the connection starts from the session
the secret was minted with (type: "transcription" opens a transcription session). The URL and
session.update can still change it.
expires_after.secondsruns from 10 to 7200 (default 600). The secret stops opening sessions then; a session already open keeps going.- A secret can open several sessions until it expires. It acts with the minting key's allowed models, budget and rate limits, and it stops working if that key is disabled or expires.
- It opens
/v1/realtimeand nothing else: any other endpoint answers401. - The models in
sessionare checked against the key when minting (403if not allowed).
Session
{
"type": "session.update",
"session": {
"type": "realtime",
"instructions": "You are a concise support agent.",
"output_modalities": ["audio"],
"audio": {
"input": {
"format": { "type": "audio/pcm", "rate": 24000 },
"transcription": { "model": "gpt-transcribe", "keywords": ["AI Gateway"] },
"turn_detection": { "type": "server_vad", "silence_duration_ms": 600 }
},
"output": { "format": { "type": "audio/pcm", "rate": 24000 }, "voice": "Kore" }
},
"tools": [
{ "type": "function", "name": "order_status", "parameters": { "type": "object", "properties": { "id": { "type": "string" } } } },
{ "type": "google_search" }
]
}
}| Field | Behavior |
|---|---|
instructions | The model's system instruction. |
output_modalities | ["audio"] (default) or ["text"]. Gemini's Live models only speak, so a text session gets the transcript of the spoken reply as response.output_text.*; it is billed as audio output. |
audio.input.format | audio/pcm at 24 kHz (default) or 16 kHz, audio/pcmu, audio/pcma. |
audio.input.transcription | Turns on the transcript of the user's speech. Gemini transcribes it itself, so model is accepted and ignored; language/languages and keywords become its language hints and vocabulary. The gateway has Gemini transcribe the user's speech either way, because that transcript is how a reconfigured session remembers it (see below); it is billed at Gemini's text output rate, a fraction of a cent per minute. |
audio.input.turn_detection | server_vad (threshold, prefix_padding_ms, silence_duration_ms), semantic_vad (eagerness), or null for manual turns. interrupt_response: false lets the model finish while the user talks. create_response: false (OpenAI models only: Gemini always answers when a turn ends) and idle_timeout_ms (refused) complete the list. |
audio.output.format | audio/pcm at 24 kHz (default), audio/pcmu, audio/pcma. |
audio.output.voice | One of the model's voices: Gemini's prebuilt voices (Kore, Puck, Charon, Zephyr, …) or OpenAI's (marin, cedar, alloy, …); the full list is in the model's catalog profile, and a voice the model lacks is refused with it. The voice cannot change once the model has spoken. |
tools | Function tools, plus the AI Gateway extension { "type": "google_search" } for Gemini's web search. MCP tools are refused. |
tool_choice | auto (default) or none. |
max_output_tokens | Per session. |
reasoning.effort | Only on models that take it: low/medium/high on gemini-3.8-live-extended-thinking (which requires one: low, OpenAI's default, when unset), minimal to high on gemini-3.1-flash-live-preview. gemini-3.8-live reasons on its own and refuses it. |
truncation | auto (default) lets the provider compress old context so a conversation can run past its context window; disabled keeps everything (Gemini then ends audio sessions after 15 minutes). |
translation | AI Gateway extension, required by gemini-3.5-live-translate-preview: { "target_language": "es", "echo_target_language": false }. The source language is detected. Translation streams without turns, so each response is one phrase: it ends after 1.5 seconds without translated speech, and the silence between phrases is not sent. |
Gemini fixes a session's setup when it opens, and a resumed session keeps its old setup. So changing instructions, tools, voice, turn detection or any other provider setting mid-session opens a new provider session with the new setup, seeded with the conversation so far: the transcripts of both sides, the client's text and image items, and function calls with their results. The model keeps the content of the conversation, not the audio itself. The switch waits until neither the model nor the user is speaking.
Turns and responses
- Server VAD. Stream
input_audio_buffer.append; Gemini detects the turn, the gateway emitsinput_audio_buffer.speech_started/speech_stopped/committedand the user item, and the model answers on its own. - Manual turns (
turn_detection: null). Audio is buffered in the gateway untilinput_audio_buffer.commit(soinput_audio_buffer.clearreally discards it);response.createasks the model to answer. - Text and images.
conversation.item.createwith ausermessage (input_text,input_imageas a base64 PNG/JPEG/WebP data URL,input_audio) or anassistantmessage adds it to the conversation;response.createasks for an answer. Items can only be appended at the end. - Function calls. The model's call arrives as a
function_callitem withresponse.function_call_arguments.done, and the response ends there. Reply with afunction_call_outputitem, thenresponse.create. A JSON object output reaches Gemini as is; anything else is wrapped as{ "output": "…" }. - Interruptions. Speech during a reply cancels it:
response.donearrives withstatus: "cancelled"andreason: "turn_detected". Stop playback and sendconversation.item.truncateas usual; the gateway acknowledges it, but Gemini keeps its own record of what it said.
Responses stream as OpenAI's events: response.created, response.output_item.added,
response.content_part.added, response.output_audio.delta with
response.output_audio_transcript.delta, then the .done events and response.done with usage.
Gemini reports a turn over only once playing it would have finished, and its usage comes with that.
The content's .done events arrive when generation ends; response.done waits for the usage, or is
sent at once (with usage: null) when the client creates another response or the user speaks.
OpenAI models
OpenAI's realtime models take the session much as OpenAI defines it:
session.updateapplies on the same connection, with no new session.create_response: false,audio.output.speedandreasoning.effortup toxhigh(2.x models) are accepted.audio.input.transcription.modelpicks OpenAI's transcription model (defaultgpt-transcribe). OpenAI bills that transcription on its own; it is not in the session's logged usage.response.cancelstops the reply upstream.- Input audio is resampled to 24 kHz, the only rate OpenAI takes.
What the gateway does not support yet is refused for them too: out-of-band responses, per-response
overrides, idle_timeout_ms, item deletion, MCP tools and stored prompts.
What Gemini cannot do
These are refused with an error event naming the field, and the session stays open:
- out-of-band responses (
response.conversation: "none",response.input) and per-response tools, voice, format, modalities, reasoning or token limits; systemmessages mid-conversation (useinstructions), client-createdfunction_callitems, and inserting items anywhere but the end (previous_item_id);conversation.item.delete: Gemini cannot forget what it heard;output_audio_buffer.clear, which is WebRTC-only.
response.create with instructions and nothing else to answer sends them to the model as a user
turn, since Gemini has no per-response instructions; this covers the common "greet the user" case.
response.cancel ends the response and drops the rest of the model's turn, but Gemini finishes
generating it, and it is billed.
Limits and billing
- A connection lasts at most 60 minutes. Gemini ends each provider connection after about ten minutes; the gateway resumes the session on a new one behind the same socket. The translation model has no resumption and reconnects without context.
REALTIME_WEBSOCKET_MAX_CONNECTIONSandREALTIME_WEBSOCKET_MAX_CONNECTIONS_PER_KEYcap open sessions per process and per key.- Each connection is logged as one
realtime.conversationoperation with the usage Gemini reported and its cost. Live models price audio and text separately (audioInputCentsPerMTokens,audioOutputCentsPerMTokensin the catalog). If the provider reports no usage, the gateway estimates it (25 tokens per second of audio, about 4 characters per text token) and marks the operationusageEstimated.