/v1/realtime speaks the OpenAI Realtime protocol: session.update,
input_audio_buffer.append, response.create, response.output_audio.delta.
Existing realtime clients work against it.
When to use it
Live conversation
A person talks, the agent answers, either can interrupt. This is what the
endpoint is for.
Live transcription
Transcripts of both sides arrive on the same socket as the audio, so you
get captions without a second service.
Phone and browser alike
The same session backs a telephony call and a WebRTC one — the agent does
not know or care which.
Not for recorded files
Have the audio already? Use
transcription — it is cheaper and it does not
need a socket held open.
Connect
Configure the session
Protocol version
/v1/realtime speaks protocol version 1 of EESI’s dialect. The first event on
every socket, session.created, declares it beside the OpenAI-shaped
session:
audio_input_rates— PCM16 rates the server accepts. The first is the default whenaudio.input.formatis omitted.audio_formats—pcm16(audio/pcm),g711_ulaw(audio/pcmu, 8 kHz) andg711_alaw(audio/pcma, 8 kHz).events— which optional events the speech server emits or honours. Version 1 emitseesi.recording_disclosure(below).resumeisfalsehere because resumption is a gateway feature, not a model-server one — see Resume a lost session;pingis reserved.
"".
Resume a lost session
Every session’s greeting carries a gateway-privateeesi.session event with
its resumable identity:
session.update you sent and every completed turn — before
your first frame is relayed. The model keeps its memory; at most the turn
that was in flight is lost. The greeting of a resumed session repeats the
same session_id and token (so it can be resumed again) and adds
"resumed": { "replayed_items": N }. A checkpoint lives for 15 minutes after
the last turn; an intentional close is not resumable. @eesi/realtime
decodes the event as sessionHandle, and the console, iPhone and Mac
clients send it back on every redial automatically.
Send audio
16 kHz mono PCM16, base64-encoded, in ~20 ms chunks (640 bytes). Pace the sends in real time — server-side VAD uses arrival timing to find turn boundaries, so dumping a whole file at once produces bad endpointing.Receive
Older OpenAI variants spell three of these without
output_:
response.audio.delta, response.audio_transcript.delta, and
response.audio_transcript.done. This server emits only the canonical
spellings above. If your client also talks to a backend that uses the legacy
names, handle both — handling one is how a client goes silent against exactly
one backend.Barge-in
Interruption is handled server-side. When the user starts speaking over the assistant, generation cancels and queued audio is dropped. Stop playback oninput_audio_buffer.speech_started rather than draining your buffer; the
cancelled turn then closes with response.done carrying
response.status: "cancelled".
Language
Nur is natively multilingual and code-switches mid-sentence without configuration. If you want it pinned to one language, say so ininstructions.
To interpret rather than converse — output the same words in another language
and nothing else — use Live translation. Instructions
alone won’t do it: every conversational session carries a persona that tells the
model to introduce itself and not to translate unasked.
Agent-to-agent
Two realtime sessions can be cross-piped so agents talk to each other — useful for adversarial evaluation and load testing. This needs the serving backend to run at least two sessions concurrently.Connecting from a browser
A browser cannot put anAuthorization header on a WebSocket handshake, so the
credential would have to ride the URL — and URLs get logged. Mint a ticket over
HTTPS instead, where the header is not logged, and hand the socket something
that dies on first use.
token, not ticket — the gateway reads exactly three
credential channels (the Authorization header, ?api_key=, ?token=), and a
ticket under any other name is a 401 Missing authentication.
Tickets are single-use and expire in 30 seconds — mint one per connection,
immediately before opening the socket, and mint a fresh one for every
reconnect. Clients that can set headers (the OpenAI SDKs, native apps) never
need this and should send the key directly.
If the handshake is refused, the close code says why: 1008 is a limit or a
rejection (auth, quota, an unknown model, the concurrent-session cap) — capacity
frees, so “try again in a moment” is honest; 1011 means the realtime backend
is unavailable, and retrying changes nothing until it is back. A drop after
session.created (1006, or 1011 on a backend restart) is worth redialing with
backoff; 1000 from the server is a deliberate end of session.
Going further
Latency
Where a turn’s two seconds go, and which knobs actually move them.