🎙 Realtime API — Live Voice Conversations
Low-latency voice assistants over WebSocket. Audio in, audio out — no transcription round-trip.
The Realtime API opens a two-way audio channel between your phone system or web app and the model. The model listens while the user speaks, answers on its own once they stop, and streams the audio back in chunks. In the classic record, transcribe, generate, synthesise chain every step queues behind the last and latency piles up; here everything flows over one connection, so the user hears the start of the answer far sooner.
When to use it
Phone greeting and routing, call-centre triage, voice ordering, in-car assistants, accessibility — anywhere the user speaks and expects an immediate answer.
It is not for one-off text jobs, batch generation, long document analysis, or cases where audio is produced ahead of time and stored; /v1/chat/completions and /v1/audio/generate are cheaper and simpler for those.
Connecting
/v1/realtime?model=openai/gpt-realtime-1.5
A standard WebSocket connection. Send your API key in the Authorization header — there is no additional step. Your key stays with us; upstream provider credentials are never sent to the client.
GET /v1/realtime?model=openai/gpt-realtime-1.5 HTTP/1.1
Host: api.onysoft.com
Upgrade: websocket
Connection: Upgrade
Authorization: Bearer sk-ony-...Do not connect straight from a browser: the WebSocket API cannot send custom headers and your key would be exposed to the client. Open the connection from your own server and relay audio to your client from there.
Event Flow
Once connected, everything happens through JSON event messages. A typical turn goes like this:
- You connect; the server sends session.created as the first message.
- You send session.update with audio format, voice, turn detection and transcription settings.
- You base64-encode microphone audio and stream it in chunks with input_audio_buffer.append.
- The server detects the end of speech (server VAD), emits input_audio_buffer.speech_stopped and starts generating.
- Answer audio arrives chunk by chunk as response.output_audio.delta events, which you play back.
- response.done closes the turn and reports the tokens it consumed — billing is based on this.
Session Settings
The first message you send after connecting should be session.update. These are the fields you will use most:
| Field | Description | Suggested |
|---|---|---|
audio.input.format | Input audio format. G.711 for telephony, pcm16 for web. | audio/pcmu |
audio.output.format | Output audio format. It need not match the input, but keeping them the same is simplest on a phone line. | audio/pcmu |
audio.output.voice | The model voice. marin is female-toned, cedar male-toned; both read Turkish naturally. | marin |
audio.input.turn_detection | Turn detection. server_vad measures silence server-side; with create_response true the answer starts automatically. | server_vad |
audio.input.transcription | Transcription of what the user said. Needed for records or writing into a CRM. | whisper-1 / tr |
instructions | Behaviour instructions. Set language, tone and boundaries here. | — |
max_output_tokens | Maximum output tokens the model may produce in one turn. Handy for capping cost. | 4096 |
{
"type": "session.update",
"session": {
"type": "realtime",
"audio": {
"input": {
"format": { "type": "audio/pcmu" },
"turn_detection": { "type": "server_vad", "create_response": true },
"transcription": { "model": "whisper-1", "language": "tr" }
},
"output": {
"format": { "type": "audio/pcmu" },
"voice": "marin"
}
},
"instructions": "Türkçe konuş. Kısa ve net yanıt ver.",
"max_output_tokens": 4096
}
}Audio Formats
The wrong format is the most common problem: audio flows but the other side hears noise. Pick the one that matches your line.
| Format | Where | Note |
|---|---|---|
audio/pcmu | Telephony (SIP/PSTN, G.711 µ-law) | Common on Turkish and North American phone lines. 8 kHz. |
audio/pcma | Telephony (G.711 A-law) | Common on European phone lines. 8 kHz. |
audio/pcm (16-bit) | Web, mobile app | 24 kHz, uncompressed. The highest-quality option. |
Common Events
The full list is long; these are the ones you need day to day.
| Event | Direction | What it does |
|---|---|---|
session.update | Sent | Updates session settings. Should be the first message after connecting. |
input_audio_buffer.append | Sent | Appends an audio chunk. Audio is sent base64-encoded. |
response.create | Sent | Starts a response manually. Not needed when using server_vad. |
response.cancel | Sent | Cancels the response in progress. Send this when the user interrupts. |
session.created | Received | Session is ready. Returns the effective settings. |
input_audio_buffer.speech_started | Received | The user started speaking. A good signal to stop playback. |
input_audio_buffer.speech_stopped | Received | The user stopped; the model is starting its answer. |
response.output_audio.delta | Received | A chunk of answer audio. Base64-decode and play it. |
conversation.item.input_audio_transcription.completed | Received | Transcript of what the user said. |
response.done | Received | The turn is complete. The usage field carries the token breakdown. |
error | Received | Something went wrong. Check error.message. |
Example
The smallest working example that connects, sends settings and handles incoming events.
import WebSocket from "ws";
const ws = new WebSocket(
"wss://api.onysoft.com/v1/realtime?model=openai/gpt-realtime-1.5",
{ headers: { Authorization: "Bearer sk-ony-..." } } // anahtar SUNUCUDA kalir
);
ws.on("open", () => console.log("bagli"));
ws.on("message", (raw) => {
const e = JSON.parse(raw);
switch (e.type) {
case "session.created":
ws.send(JSON.stringify(sessionUpdate)); // ilk mesaj
break;
case "input_audio_buffer.speech_started":
stopPlayback(); // soz kesme
ws.send(JSON.stringify({ type: "response.cancel" }));
break;
case "response.output_audio.delta":
play(Buffer.from(e.delta, "base64"));
break;
case "response.done":
console.log("token:", e.response.usage); // faturalama bu
break;
case "error":
console.error(e.error.message);
}
});
// mikrofon sesini base64 kodlayip akit
ws.send(JSON.stringify({ type: "input_audio_buffer.append", audio: chunk.toString("base64") }));import json, base64, websocket
ws = websocket.create_connection(
"wss://api.onysoft.com/v1/realtime?model=openai/gpt-realtime-1.5",
header=["Authorization: Bearer sk-ony-..."],
)
while True:
e = json.loads(ws.recv())
if e["type"] == "session.created":
ws.send(json.dumps(session_update))
elif e["type"] == "response.output_audio.delta":
hoparlore_yaz(base64.b64decode(e["delta"]))
elif e["type"] == "response.done":
print(e["response"]["usage"]) # token dokumu
elif e["type"] == "error":
print("HATA:", e["error"]["message"])Interruptions (Barge-in)
In a real conversation the user cuts the model off. Handle it badly and two voices overlap. The correct behaviour: the moment you receive input_audio_buffer.speech_started, stop playback, flush your queue and send response.cancel. With server_vad on, the server starts the new turn by itself.
Function Calling
If the assistant needs to look up an order, book an appointment or write into a CRM, declare tools inside session.update. The model signals a call with response.function_call_arguments.done; you run it, return the result with conversation.item.create, then send response.create so the model continues.
Duration and Limits
| Limit | Value | Notes |
|---|---|---|
| Limit — oturum | 30 minutes | Maximum session length. For longer calls the client must reconnect and, if needed, carry a summary into the new session. |
| idle | 10 minutes | A connection with no data flowing is closed. It will not trigger during a normal call, since audio streams continuously. |
| concurrency | By agreement | Contact us about concurrent session limits. |
| access | Per key | Realtime access is not enabled on every key; if it is off the connection is refused with 403. |
Billing
You are charged on actual token usage. When a session closes, the input and output tokens it consumed are deducted from your balance; there is no separate charge for connection time. Audio tokens cost noticeably more than text tokens, because one second of audio carries far more tokens than a few words of text.
To keep cost down: keep instructions short, cap each turn with max_output_tokens, close the connection during long silences, and do not leave idle sessions open.
Error Codes
| Code | Meaning | What to do |
|---|---|---|
401 | Key is invalid, inactive or expired. | Check the key in your panel. |
402 | Insufficient balance. | Top up and reconnect. |
403 | This key is not authorised for the realtime model. | Ask support to enable access. |
502 | Could not reach the provider. | Wait briefly and retry; tell us if it persists. |
503 | Model or provider not configured. | Check the model name. |
Common Mistakes
- Sending audio without base64 encoding — the server cannot decode it and the audio is effectively lost.
- Not matching input and output format to your line; pcm16 on a phone line produces crackle.
- Not handling interruptions, so two voices overlap when the user speaks over the model.
- Keeping a session open to the 30-minute limit without planning a reconnect.
- Embedding the key in a browser. Always open the connection from your own server.