Skip to main content
Dialog-RSN-1 is a real-time voice model. You stream the caller’s audio over one WebSocket and get the reply back as text, with the model deciding when the caller has finished speaking. You bring your own text-to-speech. The wire format is the OpenAI Realtime API (GA schema). A stock OpenAI Realtime client works by changing the base URL and the key. The model name on the wire is 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

The Agent Studio instance and API host must match. A key generated from studio.poly.ai only works against api.studio.poly.ai. To call api.us.poly.ai, generate the key from studio.us.poly.ai (and likewise for eu and uk). A mismatch returns 401 Unauthorized.
Check the service is up with the health route, which takes the same key:
Open a connection and read the first event. The OpenAI SDK appends /realtime to the base URL and sends your key as a bearer token.
The optional query parameter 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.
There is no query-string key and no WebSocket subprotocol credential. Browsers cannot set either header on a WebSocket, so put a relay in front of browser clients. A refused handshake answers with an HTTP status and a JSON body. A WebSocket library that cannot surface the HTTP response sees the matching close code instead.

Configure the session

Send session.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 of session.updated:
  • model (the session stays dialog-rsn-1), tracing, truncation, include
  • audio.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 as server_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 an error naming the field in param. The whole update is dropped.

Audio in

Audio travels as base64 inside input_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.create is refused with unsupported_item_type. Stream caller audio; use conversation.item.create for text, tool results and history.

Run a turn

With create_response: true the sequence for one caller turn is:
  1. You stream input_audio_buffer.append frames.
  2. input_audio_buffer.speech_started (with poly_confirmed: false on the first guess), then input_audio_buffer.speech_stopped and input_audio_buffer.committed when the model confirms the turn is over.
  3. conversation.item.added and conversation.item.done for the caller’s turn, with conversation.item.input_audio_transcription.delta and .completed carrying what the model heard.
  4. response.created, response.output_item.added, response.content_part.added, then response.output_text.delta frames until response.output_text.done, response.content_part.done, response.output_item.done and response.done.
With create_response: false, step 4 starts when you send response.create.
Rules that catch people out:
  • One response at a time. A response.create while a response is open is refused with conversation_already_has_active_response. Send response.cancel first. A response.create sent while the caller is still speaking is held and answered by the response to their turn.
  • response.create may set instructions, max_output_tokens and metadata. conversation, output_modalities, tool_choice and tools must match the session or are refused with unsupported_parameter.
  • A confirmed barge-in always cancels the reply. interrupt_response cannot be turned off. The cancelled response ends with status cancelled.
  • 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 a type 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_id is set on conversation.item.* events, not on response.output_item.*.
  • speech_started.audio_start_ms counts audio actually received, not wall-clock time.
  • session.created and response.created omit optional fields that are not in effect (usage, max_output_tokens, metadata and others).

PolyAI extensions

Extensions are additive fields on existing events, all prefixed poly_. 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:
Send a 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

Branch on 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:

Examples

There is no PolyAI SDK for this API; any OpenAI Realtime client works. Working integrations with Pipecat and LiveKit are in the realtime-dev-kit repository, which is the conformance harness we run against OpenAI’s own traces.
Last modified on October 12, 2026