dialog-rsn-1.
Limits and pricing
Concurrency, session duration, context window, audio formats and the connected-minute rate.
OpenAI Realtime reference
Field-level documentation for every event. This page lists what we accept and where we differ.
Connect
Check the service is up with the health route, which takes the same key:
/realtime to the base URL and sends your key as a bearer token.
session_id ([A-Za-z0-9_.-], up to 128 characters) names the session for your own logs. It comes back as session.poly_session_id. The model query parameter that OpenAI clients send is accepted and ignored.
The server pings every 20 seconds. A connection that sends nothing for 60 seconds is closed by the gateway; a client that is streaming audio never meets this.
Authenticate
Send a workspace API key from Agent Studio in either header. They are equivalent.Configure the session
Sendsession.update after session.created. Updates are merged into the session, not replaced. A rejected update applies nothing and sends no session.updated, so what comes back in session.updated is exactly what is in effect.
session.update
Honoured
Accepted and ignored
Fields that stock clients send by default and that would change nothing a caller notices are accepted and left out ofsession.updated:
model(the session staysdialog-rsn-1),tracing,truncation,includeaudio.input.noise_reduction;audio.input.transcription(transcripts are always sent)- Turn-detection timing:
threshold,silence_duration_ms,prefix_padding_ms,eagerness.type: "semantic_vad"is reported back asserver_vad. The server finds the end of the turn: a voice-activity detector proposes a boundary and the model confirms it. audio.output.*on a text session;parallel_tool_calls(the model makes one call per turn)
Refused
Fields that would change what you get if silently ignored are refused with anerror naming the field in param. The whole update is dropped.
Audio in
Audio travels as base64 insideinput_audio_buffer.append. Binary WebSocket frames are refused.
- Send about 20 ms per frame (640 bytes at 16 kHz, 960 at 24 kHz). One frame may not exceed 256 KiB; a larger frame closes the connection with code
1009. - Keep streaming during silence. The server decides where the turn ends from the audio; a client that stops sending looks idle, not quiet.
- One turn may hold up to 300 seconds of audio; anything longer is dropped.
- Audio in
conversation.item.createis refused withunsupported_item_type. Stream caller audio; useconversation.item.createfor text, tool results and history.
Run a turn
Withcreate_response: true the sequence for one caller turn is:
- You stream
input_audio_buffer.appendframes. input_audio_buffer.speech_started(withpoly_confirmed: falseon the first guess), theninput_audio_buffer.speech_stoppedandinput_audio_buffer.committedwhen the model confirms the turn is over.conversation.item.addedandconversation.item.donefor the caller’s turn, withconversation.item.input_audio_transcription.deltaand.completedcarrying what the model heard.response.created,response.output_item.added,response.content_part.added, thenresponse.output_text.deltaframes untilresponse.output_text.done,response.content_part.done,response.output_item.doneandresponse.done.
create_response: false, step 4 starts when you send response.create.
- One response at a time. A
response.createwhile a response is open is refused withconversation_already_has_active_response. Sendresponse.cancelfirst. Aresponse.createsent while the caller is still speaking is held and answered by the response to their turn. response.createmay setinstructions,max_output_tokensandmetadata.conversation,output_modalities,tool_choiceandtoolsmust match the session or are refused withunsupported_parameter.- A confirmed barge-in always cancels the reply.
interrupt_responsecannot be turned off. The cancelled response ends with statuscancelled. - Tell the server what the caller heard. If you speak the text through your own TTS, send the
poly.output_audio.*events below so that an interrupted reply is kept in history as the words that were actually played.
Events
Every event is a JSON text frame with atype and an event_id. Fields follow the OpenAI Realtime reference; only the differences are listed here.
Client to server
Server to client
session.created, session.updated, conversation.item.added, .done, .deleted, .retrieved, .truncated, conversation.item.input_audio_transcription.delta, .completed, input_audio_buffer.speech_started, .speech_stopped, .committed, .cleared, response.created, response.done, response.output_item.added, .done, response.content_part.added, .done, response.output_text.delta, .done, response.function_call_arguments.delta, .done, error.
Not sent: rate_limits.updated, conversation.created, response.output_audio.*, MCP events, response.usage.
Differences a stock client notices
- Text and function-argument deltas arrive per model chunk, not per token. The concatenation is the same.
- Transcription deltas are true increments.
previous_item_idis set onconversation.item.*events, not onresponse.output_item.*.speech_started.audio_start_mscounts audio actually received, not wall-clock time.session.createdandresponse.createdomit optional fields that are not in effect (usage,max_output_tokens,metadataand others).
PolyAI extensions
Extensions are additive fields on existing events, all prefixedpoly_. Nothing is sent as a new server event type.
Playback reports. When you voice the reply yourself, send these so the server knows what the caller heard:
delta every 250 ms or so with the cumulative milliseconds played. On a barge-in, stopped carries what was dropped and the words that were actually spoken, and the reply is kept in history as those words.
Errors
code, never on message. error.event_id names the client frame the error is about, where there is one. type is invalid_request_error for something you sent and server_error for something that failed on our side; a server_error that ends a response also arrives on response.done with status failed and the same code.
Close codes
Handling a refused handshake and a timed-out session:

