🎙 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

check_circle

Phone greeting and routing, call-centre triage, voice ordering, in-car assistants, accessibility — anywhere the user speaks and expects an immediate answer.

info

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

WSS /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.

HTTP
GET /v1/realtime?model=openai/gpt-realtime-1.5 HTTP/1.1
Host: api.onysoft.com
Upgrade: websocket
Connection: Upgrade
Authorization: Bearer sk-ony-...
warning

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:

  1. You connect; the server sends session.created as the first message.
  2. You send session.update with audio format, voice, turn detection and transcription settings.
  3. You base64-encode microphone audio and stream it in chunks with input_audio_buffer.append.
  4. The server detects the end of speech (server VAD), emits input_audio_buffer.speech_stopped and starts generating.
  5. Answer audio arrives chunk by chunk as response.output_audio.delta events, which you play back.
  6. 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:

FieldDescriptionSuggested
audio.input.formatInput audio format. G.711 for telephony, pcm16 for web.audio/pcmu
audio.output.formatOutput audio format. It need not match the input, but keeping them the same is simplest on a phone line.audio/pcmu
audio.output.voiceThe model voice. marin is female-toned, cedar male-toned; both read Turkish naturally.marin
audio.input.turn_detectionTurn detection. server_vad measures silence server-side; with create_response true the answer starts automatically.server_vad
audio.input.transcriptionTranscription of what the user said. Needed for records or writing into a CRM.whisper-1 / tr
instructionsBehaviour instructions. Set language, tone and boundaries here.
max_output_tokensMaximum output tokens the model may produce in one turn. Handy for capping cost.4096
session.update
{
  "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.

FormatWhereNote
audio/pcmuTelephony (SIP/PSTN, G.711 µ-law)Common on Turkish and North American phone lines. 8 kHz.
audio/pcmaTelephony (G.711 A-law)Common on European phone lines. 8 kHz.
audio/pcm (16-bit)Web, mobile app24 kHz, uncompressed. The highest-quality option.

Common Events

The full list is long; these are the ones you need day to day.

EventDirectionWhat it does
session.updateSentUpdates session settings. Should be the first message after connecting.
input_audio_buffer.appendSentAppends an audio chunk. Audio is sent base64-encoded.
response.createSentStarts a response manually. Not needed when using server_vad.
response.cancelSentCancels the response in progress. Send this when the user interrupts.
session.createdReceivedSession is ready. Returns the effective settings.
input_audio_buffer.speech_startedReceivedThe user started speaking. A good signal to stop playback.
input_audio_buffer.speech_stoppedReceivedThe user stopped; the model is starting its answer.
response.output_audio.deltaReceivedA chunk of answer audio. Base64-decode and play it.
conversation.item.input_audio_transcription.completedReceivedTranscript of what the user said.
response.doneReceivedThe turn is complete. The usage field carries the token breakdown.
errorReceivedSomething went wrong. Check error.message.

Example

The smallest working example that connects, sends settings and handles incoming events.

Node.js
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") }));
Python
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

LimitValueNotes
Limit — oturum30 minutesMaximum session length. For longer calls the client must reconnect and, if needed, carry a summary into the new session.
idle10 minutesA connection with no data flowing is closed. It will not trigger during a normal call, since audio streams continuously.
concurrencyBy agreementContact us about concurrent session limits.
accessPer keyRealtime 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.

savings

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

CodeMeaningWhat to do
401Key is invalid, inactive or expired.Check the key in your panel.
402Insufficient balance.Top up and reconnect.
403This key is not authorised for the realtime model.Ask support to enable access.
502Could not reach the provider.Wait briefly and retry; tell us if it persists.
503Model or provider not configured.Check the model name.

Common Mistakes

Want help finding the right model?