Voice agents used to require three separate stages: speech-to-text, a language model, and text-to-speech. Each hop added latency and stripped away some tone. OpenAI’s Realtime API combines that pipeline into a single speech-to-speech model, and gpt-realtime-2.1-mini is the lower-cost, lower-latency mini tier. It listens to audio, reasons over the conversation, and streams spoken responses over one connection.
This guide shows how to call gpt-realtime-2.1-mini end to end: which model ID to use, how to connect with WebSocket and WebRTC, how to configure a session, how to add tools, and how to test the workflow with Apidog before wiring it into an app. The implementation maps to the official OpenAI Realtime guide.
First, get the model name right
There are two common identifiers for the mini realtime model:
-
gpt-realtime-2.1-mini: the versioned ID. This appears on the OpenAI pricing page and pins you to the 2.1 generation. -
gpt-realtime-mini: the family alias. It points to the latest mini snapshot, currentlygpt-realtime-mini-2025-12-15.
Use aliases while prototyping. Pin a dated snapshot before production if you need stable behavior.
| Identifier | What it points to |
|---|---|
gpt-realtime-mini |
Latest mini snapshot, auto-updates |
gpt-realtime-2.1-mini |
The 2.1-generation mini model |
gpt-realtime-mini-2025-12-15 |
Pinned snapshot, current |
gpt-realtime-mini-2025-10-06 |
Pinned snapshot, previous |
Recommended workflow:
- Build with
gpt-realtime-miniorgpt-realtime-2.1-mini. - Test your prompts, tools, turn detection, and audio behavior.
- Pin a snapshot such as
gpt-realtime-mini-2025-12-15before release.
What gpt-realtime-2.1-mini does
gpt-realtime-2.1-mini is a speech-to-speech model. You stream audio in, and it streams audio back with natural intonation. You do not need to run a separate transcription model or TTS model.
It also supports text, so you can mix typed messages, spoken input, audio output, and transcripts in the same session.
From the model page:
| Property | Value |
|---|---|
| Input modalities | Text, image, audio |
| Output modalities | Text, audio |
| Context window | 32,000 tokens |
| Max output | 4,096 tokens |
| Connections | WebRTC, WebSocket, SIP |
| Voices | alloy, ash, ballad, coral, echo, sage, shimmer, verse, marin, cedar |
marin and cedar are the newest voices and are exclusive to the Realtime API. OpenAI recommends them for the most natural output. The older voices still work if you need a specific sound.
Use the mini tier when you want:
- Lower latency
- Lower cost
- Voice support bots
- Order-taking flows
- Voice front-ends for apps
- Lightweight interactive agents
Use the full gpt-realtime-2.1 model when the conversation needs heavier reasoning.
What it costs
Mini is significantly cheaper than the full realtime model. Current token rates from the pricing page:
| Model | Text input | Cached input | Audio input | Audio output |
|---|---|---|---|---|
gpt-realtime-2.1-mini |
$0.60 / 1M | $0.30 / 1M | $10 / 1M | $20 / 1M |
gpt-realtime-2.1 |
$4.00 / 1M | $0.40 / 1M | $32 / 1M | $64 / 1M |
Audio output is usually the dominant cost. The more your agent talks, the more you pay.
A practical optimization is to control verbosity in your session instructions:
Keep answers to one or two short sentences unless the user asks for detail.
Real-world per-minute costs for mini often land around $0.06 to $0.15, depending on how much the agent speaks. Always confirm against the live pricing page before forecasting production spend.
Prerequisites
You need:
- An OpenAI API key with Realtime access.
- The key available server-side as
OPENAI_API_KEY. - Node.js 18+ for the examples.
- The
wspackage if you want to test raw WebSocket. - HTTPS or
localhostfor browser microphone access withgetUserMedia.
Install the WebSocket dependency:
npm install ws
Important rule:
Never expose your real OpenAI API key in browser or mobile client code.
Browser and mobile apps should use short-lived ephemeral tokens. Your server creates those tokens with the real API key, then sends the temporary token to the client.
Pick a connection method
gpt-realtime-2.1-mini supports WebRTC, WebSocket, and SIP.
| Transport | Use it when | Auth |
|---|---|---|
| WebRTC | Audio is captured or played in a browser or mobile app | Ephemeral client secret |
| WebSocket | Your server handles raw audio or you want a fast prototype | API key, server-side |
| SIP | You are connecting a phone or telephony system | API key |
A practical implementation path:
- Start with WebSocket to confirm model access and event flow.
- Add audio output.
- Move to WebRTC when you need browser microphone input and low-latency playback.
- Add tools once the voice loop works.
Quickstart 1: WebSocket from your server
WebSocket is the fastest way to verify that your account, model ID, and event handling work.
Endpoint:
wss://api.openai.com/v1/realtime?model=gpt-realtime-2.1-mini
The GA interface uses a normal Authorization: Bearer header. You do not need the old OpenAI-Beta header.
Create realtime-text.js:
import WebSocket from "ws";
const url = "wss://api.openai.com/v1/realtime?model=gpt-realtime-2.1-mini";
const ws = new WebSocket(url, {
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
});
ws.on("open", () => {
// 1. Configure the session
ws.send(
JSON.stringify({
type: "session.update",
session: {
type: "realtime",
model: "gpt-realtime-2.1-mini",
output_modalities: ["text"],
instructions:
"You are a concise API support agent. Keep answers short.",
},
})
);
// 2. Add a user message
ws.send(
JSON.stringify({
type: "conversation.item.create",
item: {
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "What is an idempotent request?",
},
],
},
})
);
// 3. Ask the model to respond
ws.send(
JSON.stringify({
type: "response.create",
})
);
});
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (event.type === "response.output_text.delta") {
process.stdout.write(event.delta);
}
if (event.type === "response.done") {
ws.close();
}
});
ws.on("error", console.error);
Run it:
OPENAI_API_KEY=your_api_key node realtime-text.js
The basic Realtime flow is:
- Send
session.update. - Add user input with
conversation.item.create. - Trigger generation with
response.create. - Listen for streaming events.
- Stop when you receive
response.done.
Common server events:
| Event | Meaning |
|---|---|
session.created |
Session was created |
session.updated |
Your session config was accepted |
response.output_text.delta |
Text output chunk |
response.output_audio.delta |
Base64 audio output chunk |
response.output_audio_transcript.delta |
Transcript of spoken output |
response.done |
Turn is complete |
Switch the WebSocket example to audio output
To receive audio instead of text, update the session:
ws.send(
JSON.stringify({
type: "session.update",
session: {
type: "realtime",
model: "gpt-realtime-2.1-mini",
output_modalities: ["audio"],
instructions:
"You are a concise voice support agent. Keep answers short.",
audio: {
output: {
format: {
type: "audio/pcm",
rate: 24000,
},
voice: "marin",
},
},
},
})
);
Then listen for audio deltas:
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (event.type === "response.output_audio.delta") {
const audioChunk = Buffer.from(event.delta, "base64");
// Send this PCM chunk to your playback pipeline,
// media server, file writer, or audio stream.
console.log("Received audio bytes:", audioChunk.length);
}
if (event.type === "response.output_audio_transcript.delta") {
process.stdout.write(event.delta);
}
if (event.type === "response.done") {
ws.close();
}
});
For a production server-side voice pipeline, route those PCM chunks into your media layer.
Quickstart 2: WebRTC in the browser
Use WebRTC when the browser captures microphone input and plays the model’s audio response directly.
The authentication pattern is:
- Browser asks your server for an ephemeral token.
- Server calls OpenAI with your real API key.
- Server returns the short-lived token to the browser.
- Browser connects to the Realtime API with that ephemeral token.
Step 1: Mint an ephemeral token on your server
Example server-side code:
const r = await fetch("https://api.openai.com/v1/realtime/client_secrets", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
session: {
type: "realtime",
model: "gpt-realtime-2.1-mini",
},
}),
});
const { value } = await r.json();
// Send `value` to the browser.
// It is an ephemeral key and starts with "ek_".
Do not persist this token. Mint a fresh one per session.
Step 2: Connect from the browser
Add an audio element to your page:
<audio id="audio" autoplay></audio>
Then create the WebRTC connection:
// browser side: EPHEMERAL_KEY came from your server
const pc = new RTCPeerConnection();
// Play the model's remote audio stream
pc.ontrack = (event) => {
document.getElementById("audio").srcObject = event.streams[0];
};
// Capture and send microphone audio
const mic = await navigator.mediaDevices.getUserMedia({
audio: true,
});
pc.addTrack(mic.getTracks()[0]);
// Use a data channel for Realtime API events
const channel = pc.createDataChannel("oai-events");
channel.onmessage = (event) => {
console.log(JSON.parse(event.data));
};
// Create SDP offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// Exchange SDP with the Realtime API
const sdpResp = await fetch(
"https://api.openai.com/v1/realtime/calls?model=gpt-realtime-2.1-mini",
{
method: "POST",
body: offer.sdp,
headers: {
Authorization: `Bearer ${EPHEMERAL_KEY}`,
"Content-Type": "application/sdp",
},
}
);
await pc.setRemoteDescription({
type: "answer",
sdp: await sdpResp.text(),
});
Once connected:
- The model listens to the microphone track.
- The model speaks through
pc.ontrack. - JSON events flow through the
oai-eventsdata channel.
You can send the same event payloads used in the WebSocket example:
channel.send(
JSON.stringify({
type: "session.update",
session: {
type: "realtime",
model: "gpt-realtime-2.1-mini",
output_modalities: ["audio"],
instructions:
"You are a friendly booking assistant. Confirm details before acting.",
audio: {
output: {
voice: "marin",
},
},
},
})
);
Shape the session
The session object controls model behavior, output format, voice, and turn detection.
Here is a full audio session update:
{
type: "session.update",
session: {
type: "realtime",
model: "gpt-realtime-2.1-mini",
output_modalities: ["audio"],
instructions: "You are a friendly booking assistant. Confirm details before acting.",
audio: {
input: {
format: {
type: "audio/pcm",
rate: 24000
},
turn_detection: {
type: "semantic_vad"
}
},
output: {
format: {
type: "audio/pcm",
rate: 24000
},
voice: "marin"
}
}
}
}
Key fields:
| Field | What to configure |
|---|---|
instructions |
Persona, behavior, guardrails, and verbosity |
output_modalities |
Use ["audio"] for voice or ["text"] for transcript-only output |
audio.output.voice |
Use marin, cedar, or another supported voice |
audio.input.turn_detection |
Use semantic_vad for more natural turn-taking or server_vad for silence-based detection |
You can update the session mid-call by sending another session.update. You do not need to reconnect.
Add tools so the agent can act
A voice agent becomes useful when it can call tools: check an order, book a table, look up inventory, or trigger a workflow.
Realtime uses the same function-calling pattern as the rest of the OpenAI platform:
- Declare tools in the session.
- Let the model decide when to call a tool.
- Listen for the function-call event.
- Run your application code.
- Send the result back into the conversation.
- Trigger the next response.
If you have used tools with the chat API, the mental model is the same. For deeper schema examples, see OpenAI function calling and structured outputs.
At a high level, your event handler should look like this:
ws.on("message", async (raw) => {
const event = JSON.parse(raw.toString());
if (event.type === "response.function_call_arguments.done") {
const args = JSON.parse(event.arguments);
// Run your application logic
const result = await lookupOrder(args.order_id);
// Send the tool result back
ws.send(
JSON.stringify({
type: "conversation.item.create",
item: {
type: "function_call_output",
call_id: event.call_id,
output: JSON.stringify(result),
},
})
);
// Ask the model to continue with the tool result
ws.send(
JSON.stringify({
type: "response.create",
})
);
}
});
For more complex multi-step agents, OpenAI’s AgentKit provides a higher-level orchestration path.
Test the endpoints with Apidog before you build
Before debugging WebRTC in a half-built app, test each API surface independently. Apidog is useful here because you can validate both REST and WebSocket behavior.
1. Test the ephemeral token endpoint
Create a REST request:
POST https://api.openai.com/v1/realtime/client_secrets
Headers:
Authorization: Bearer YOUR_OPENAI_API_KEY
Content-Type: application/json
Body:
{
"session": {
"type": "realtime",
"model": "gpt-realtime-2.1-mini"
}
}
Expected result:
- A short-lived token starting with
ek_ - An expiry value
- Confirmation that your API key and account access work
This is the same kind of smoke test you would run for other OpenAI REST APIs, such as the Responses API.
2. Test the WebSocket event flow
Open a WebSocket connection in Apidog:
wss://api.openai.com/v1/realtime?model=gpt-realtime-2.1-mini
Add the auth header:
Authorization: Bearer YOUR_OPENAI_API_KEY
Send these messages manually, one at a time.
First, configure the session:
{
"type": "session.update",
"session": {
"type": "realtime",
"model": "gpt-realtime-2.1-mini",
"output_modalities": ["text"],
"instructions": "You are a concise API support agent."
}
}
Then add a user message:
{
"type": "conversation.item.create",
"item": {
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "Explain rate limits in one sentence."
}
]
}
}
Finally, request a response:
{
"type": "response.create"
}
Watch the streamed server events in Apidog. This makes the event sequence easier to understand before you implement client-side handlers.
If your team already uses API testing strategies, add these Realtime requests to your normal validation workflow.
When the transport layer works in isolation, app-level bugs are easier to diagnose. You can also Download Apidog to follow along locally.
Keep the bill under control
Audio output is the expensive part. Use these defaults:
-
Tell the model to be brief. Add “Keep answers to one or two sentences” to
instructions. -
Pin a production snapshot.
gpt-realtime-mini-2025-12-15will not drift;gpt-realtime-minican. -
Use
semantic_vad. It reduces awkward interruptions and wasted partial responses. - Cache stable instructions. Cached input is cheaper than fresh text input.
- Close idle sessions. Do not leave inactive connections open.
- Avoid unnecessary spoken confirmations. If your UI already shows something, the agent may not need to say it.
Example cost-focused instruction:
You are a concise support agent. Answer in one or two short sentences. Ask one clarifying question only when required. Do not repeat information already visible in the UI.
Common errors and fixes
| Error | Fix |
|---|---|
401 Unauthorized |
Check your API key. If using WebRTC, mint a fresh ephemeral token per session. |
Model not found |
Use gpt-realtime-2.1-mini, not gpt-realtime-mini-2.1. |
| No browser audio | Attach the remote stream in pc.ontrack and use HTTPS or localhost. |
| Microphone does not open | Confirm browser permissions and getUserMedia requirements. |
| Model talks over the user | Use semantic_vad and confirm the mic track is connected. |
| WebSocket auth fails | Use Authorization: Bearer ... and remove the old OpenAI-Beta header. |
| No text deltas | Confirm output_modalities includes ["text"]. |
| No audio deltas | Confirm output_modalities includes ["audio"] and that audio output is configured. |
FAQ
Is gpt-realtime-2.1-mini the same as gpt-realtime-mini?
Effectively, yes. gpt-realtime-2.1-mini is the versioned ID. gpt-realtime-mini is the alias that points to the latest snapshot, currently gpt-realtime-mini-2025-12-15.
Use the alias while building. Pin a snapshot for production.
Can I use it for plain transcription?
You can work with transcripts in a Realtime session, but the Realtime API is designed for interactive speech-to-speech conversations. For one-shot transcription, use a dedicated transcription model.
Do I need WebRTC, or is WebSocket enough?
Use WebSocket for server-side pipelines and quick prototypes. Use WebRTC when a browser or mobile app captures and plays audio directly.
Which voice should I pick?
Start with marin or cedar. They are the newest voices, are exclusive to the Realtime API, and are recommended for natural output. The other voices are alloy, ash, ballad, coral, echo, sage, shimmer, and verse.
How is this billed?
Billing is per token and split by modality. For mini:
-
$0.60 / 1Mtext input tokens -
$0.30 / 1Mcached input tokens -
$10 / 1Maudio input tokens -
$20 / 1Maudio output tokens
Audio output is usually the largest cost driver.
Can it call functions like chat models?
Yes. Realtime uses the same function-calling contract, so a voice agent can look up orders, check inventory, make bookings, or trigger application actions during a conversation.
Where to go next
You now have the implementation path:
- Confirm the model ID.
- Test access with the text-only WebSocket example.
- Switch
output_modalitiesto audio. - Move browser audio to WebRTC with ephemeral tokens.
- Configure voice, VAD, and instructions.
- Add tools when the conversation needs to take action.
- Test REST and WebSocket pieces in Apidog before shipping.
Start with the WebSocket prototype, then move to WebRTC once you need a real microphone and low-latency playback. Pin a snapshot, keep the model concise, and you can build a voice agent that is responsive, testable, and easier to control in production.

Top comments (0)