Realtime API (OpenAI)

The Realtime API is OpenAI’s bidirectional audio + text streaming endpoint — the only path for sub-second voice conversations with GPT models. Architecturally it’s an event-driven WebSocket (or WebRTC) connection rather than a request-response REST call; clients send a stream of session.update, input_audio_buffer.append, conversation.item.create, response.create events and receive a stream of response.audio.delta, response.text.delta, response.function_call_arguments.delta, response.audio_transcript.delta, input_audio_buffer.speech_started, and ~40 other event types. The current models are gpt-realtime-2 (frontier, 2025-released) and gpt-4o-realtime-preview (older). Audio is PCM16 at 24 kHz, with G.711 (μ-law / a-law) supported for telephony interop. Function calling works mid-stream. Voice activity detection (VAD) is server-managed; interruption (“barge-in”) is first-class.

See also

1. WebSocket vs WebRTC

TransportBest forTradeoffs
WebSocketServer-side bridges (your backend talking to OpenAI on behalf of a user)Need to handle audio I/O yourself; simpler protocol; works in Node / Python
WebRTCBrowser / mobile clients connecting directlyBrowser handles audio I/O natively; lower latency on client; SDP signaling overhead

WebSocket connection

URL: wss://api.openai.com/v1/realtime?model=gpt-realtime-2
Headers:
  Authorization: Bearer $OPENAI_API_KEY
  OpenAI-Beta: realtime=v1

WebRTC connection

Sign an ephemeral session token server-side first:

session = client.beta.realtime.sessions.create(
    model="gpt-realtime-2",
    modalities=["text", "audio"],
    voice="alloy",
    instructions="You are a friendly voice assistant.",
)
ephemeral_key = session.client_secret.value
# Send ephemeral_key to browser; browser uses it for WebRTC SDP offer

Browser then opens a peer connection with https://api.openai.com/v1/realtime using the ephemeral key.

2. Session configuration

# Server-side (Python SDK over WebSocket would use a lib like openai-realtime-api-beta)
session_config = {
    "model": "gpt-realtime-2",
    "modalities": ["text", "audio"],
    "instructions": "You are a friendly voice assistant.",
    "voice": "alloy",
    "input_audio_format": "pcm16",          # or "g711_ulaw", "g711_alaw"
    "output_audio_format": "pcm16",
    "input_audio_transcription": {"model": "whisper-1"},  # transcribe user input
    "turn_detection": {
        "type": "server_vad",                # or "none"
        "threshold": 0.5,
        "prefix_padding_ms": 300,
        "silence_duration_ms": 500,
    },
    "tools": [...],                          # function declarations
    "tool_choice": "auto",
    "temperature": 0.8,
    "max_response_output_tokens": 4096,
}

Send this as a session.update event right after connection.

3. Audio formats

FormatSample rateUse case
pcm1624 kHzDefault; raw little-endian 16-bit PCM
g711_ulaw8 kHzTelephony (USA / Japan)
g711_alaw8 kHzTelephony (rest of world)

PCM16 is the highest quality. G.711 formats are for SIP / phone-system integration where 8 kHz is required.

Audio is sent as base64-encoded chunks in input_audio_buffer.append events:

import base64
 
# Pseudocode for chunking + sending
chunk = audio_bytes[i:i+8192]
event = {
    "type": "input_audio_buffer.append",
    "audio": base64.b64encode(chunk).decode(),
}
ws.send(json.dumps(event))

Commit a buffer to finalize input:

ws.send(json.dumps({"type": "input_audio_buffer.commit"}))

Or rely on server-side VAD (see below) to auto-commit on detected silence.

4. Voice activity detection (VAD)

ModeBehavior
server_vad (default)Server detects start + end of speech automatically
semantic_vadServer uses content cues for turn detection
noneClient manually commits buffers
"turn_detection": {
  "type": "server_vad",
  "threshold": 0.5,             // 0-1, sensitivity
  "prefix_padding_ms": 300,     // ms of audio before speech start to include
  "silence_duration_ms": 500    // ms of silence to declare turn end
}

When VAD detects the user finished speaking, the server emits input_audio_buffer.speech_stopped and automatically generates a response — no need for the client to call response.create.

For push-to-talk UX, use type: "none" and manually commit buffers + create responses.

5. Interruption (barge-in)

User speaks while the model is generating audio:

  1. Server detects new speech (input_audio_buffer.speech_started)
  2. Server automatically truncates the current response (sends response.audio.done with truncated: true)
  3. Client should stop playing the in-flight audio immediately
  4. Server generates a new response based on the interruption

Client must handle this:

on("input_audio_buffer.speech_started", () => {
  audioElement.pause();
  audioElement.currentTime = 0;
  audioBuffer.clear();
});

The model’s awareness of what it actually said vs what it intended to say (which may differ if interrupted mid-sentence) requires you to send a conversation.item.truncate event with the actually-played duration:

{
  "type": "conversation.item.truncate",
  "item_id": "msg_abc123",
  "content_index": 0,
  "audio_end_ms": 2400
}

This tells the server “the user only heard 2.4 seconds of that response” so the next turn’s context is accurate.

6. Function calling mid-stream

session_config = {
    ...,
    "tools": [
        {
            "type": "function",
            "name": "get_weather",
            "description": "Get current weather for a city.",
            "parameters": {
                "type": "object",
                "properties": {"city": {"type": "string"}},
                "required": ["city"],
            },
        },
    ],
    "tool_choice": "auto",
}

When the model calls a function:

  1. Server emits response.function_call_arguments.delta (incrementally)
  2. Then response.function_call_arguments.done with full args
  3. Client executes function locally
  4. Client sends back:
{
  "type": "conversation.item.create",
  "item": {
    "type": "function_call_output",
    "call_id": "call_abc",
    "output": "{\"temp\": 22, \"condition\": \"sunny\"}"
  }
}
{"type": "response.create"}    // trigger model to continue

Then response.audio.delta events arrive with the model’s spoken answer.

7. Key events — outbound (client to server)

Event typePurpose
session.updateUpdate session config (instructions, voice, tools, VAD)
input_audio_buffer.appendSend audio chunk
input_audio_buffer.commitFinalize current audio buffer
input_audio_buffer.clearDiscard pending audio
conversation.item.createInject text message / function output
conversation.item.truncateMark how much of an audio response was played
conversation.item.deleteRemove an item from conversation
response.createGenerate a response
response.cancelCancel in-progress response

8. Key events — inbound (server to client)

Event typePurpose
errorError in session
session.created / session.updatedConfirms session state
conversation.createdNew conversation started
conversation.item.createdNew item (message / function_call / function_call_output)
conversation.item.input_audio_transcription.completedUser speech transcribed (if Whisper enabled)
input_audio_buffer.speech_startedVAD detected speech onset
input_audio_buffer.speech_stoppedVAD detected speech end
input_audio_buffer.committedServer committed input buffer
response.created / response.doneResponse lifecycle
response.output_item.added / .doneNew output item in response
response.content_part.added / .doneNew content part
response.text.delta / .doneText generation increments
response.audio.delta / .doneAudio output increments (base64 PCM16 chunks)
response.audio_transcript.delta / .doneTranscript of model’s audio response
response.function_call_arguments.delta / .doneFunction call arg streaming
rate_limits.updatedRate-limit state changes

40+ events total. The official openai-realtime-api-beta library wraps these into higher-level callbacks.

9. Reference client libraries

Official (Node)

npm install openai/openai-realtime-api-beta
import { RealtimeClient } from "@openai/realtime-api-beta";
 
const client = new RealtimeClient({ apiKey: process.env.OPENAI_API_KEY });
await client.connect();
client.updateSession({
  instructions: "You are a friendly voice assistant.",
  voice: "alloy",
  turn_detection: { type: "server_vad" },
});
 
client.on("conversation.updated", ({ item, delta }) => {
  if (delta?.audio) {
    playAudio(delta.audio);   // your audio output
  }
});
 
// Send audio
client.appendInputAudio(audioChunk);

Agents SDK (Python)

The openai-agents-python package wraps Realtime in a RealtimeAgent class — gives you handoffs, tools, sessions across voice conversations. See agents-sdk-openai.

Community libraries

Many open-source Realtime wrappers exist (pipecat, livekit-agents, etc.) that handle audio I/O, VAD bridging, and platform integration.

10. Pricing

Per platform.openai.com/docs/pricing:

ModelText inputText outputAudio inputAudio output
gpt-realtime-2(varies — premium)(varies)premium per-MTokpremium per-MTok
gpt-4o-realtime-preview$5 / MTok$20 / MTok$100 / MTok$200 / MTok
gpt-4o-mini-realtime-previewcheapercheapercheapercheaper

Audio tokens are roughly 20× more expensive than text tokens. A 1-minute voice conversation (input + output) typically costs ~$0.05 - $0.20 depending on model.

Cached input also applies on Realtime — long system prompts benefit from automatic caching same as the REST APIs.

11. Browser security pattern

Don’t expose your OPENAI_API_KEY to browsers. Use ephemeral session tokens:

# Server endpoint that mints tokens
@app.post("/realtime-session")
def create_session():
    session = client.beta.realtime.sessions.create(
        model="gpt-realtime-2",
        modalities=["text", "audio"],
        voice="alloy",
        instructions="...",
    )
    return {"client_secret": session.client_secret.value}
// Browser
const { client_secret } = await fetch("/realtime-session", { method: "POST" }).then(r => r.json());
const pc = new RTCPeerConnection();
// ... use client_secret in WebRTC offer SDP ...

The ephemeral token is scoped to that session and expires quickly. Safer than long-lived API keys in browser-resident code.

12. Common pitfalls

  1. Forgetting OpenAI-Beta: realtime=v1 header — WebSocket connection fails or returns no model events. Required for WebSocket transport.
  2. Audio sample rate mismatch — sending 16 kHz when format is pcm16 (24 kHz) produces high-pitched output. Resample client-side.
  3. Not handling interruption — old audio keeps playing while user is talking → confusing UX. Always handle speech_started event.
  4. Forgetting conversation.item.truncate after interruption — model’s context drifts (thinks it said the whole response). Always truncate.
  5. server_vad too sensitive — cuts off mid-sentence on a pause. Increase silence_duration_ms to 700-1000 for natural pacing.
  6. server_vad too insensitive — long pauses leave user hanging. Decrease threshold.
  7. WebSocket reconnection — Realtime sessions don’t survive disconnections. On reconnect, recreate session from scratch. Persist conversation state client-side or via the new conversation API.
  8. Browser audio APIsAudioContext and MediaStreamTrack have quirks; use a well-tested library for production browser apps.
  9. Function calls don’t auto-resume responses — after submitting function_call_output, you must send response.create to continue.
  10. Cost surprise — audio output at $200/MTok can hit $1+ per minute on gpt-4o-realtime-preview. Budget carefully; monitor; use gpt-4o-mini-realtime for cost-sensitive workloads.

13. Use cases

  • Voice agents — customer support, scheduling, transactional voice UX
  • Real-time interpreters / translators — listen-translate-speak in near-real-time
  • Language tutoring — pronunciation feedback, conversational practice
  • Phone IVR — replace touch-tone with natural conversation (via G.711 + Twilio bridge)
  • Accessibility — voice interfaces for users who can’t type quickly

14. Comparison to other vendors

AspectOpenAI RealtimeGemini Live APIAnthropic
StatusGA (gpt-realtime-2)GA (Gemini Live)No direct equivalent
TransportWebSocket / WebRTCWebSocketn/a
Audio formatPCM16 24kHz, G.711PCM16 16kHz, MP3n/a
Latencysub-second TTFTsub-secondn/a
Function calling mid-streamYesYesn/a
Voice options6-10 voices, modulatableSeveral voicesn/a
Cost (per minute)$0.05-$0.20similar rangen/a

15. Further reading