Getting an API key
You create the key in Agent Studio at studio.poly.ai:- Go to studio.poly.ai and sign up, or log in if you already have an account.
- Open the API Keys tab in your workspace’s top bar, then select API key.
- Give the key a name. Leave Agents on All agents, and leave at least one Resource Access permission on. The form needs one, and Dialog-RSN-1 accepts the key whichever you pick.
- Select Create, then copy the key. It’s only shown once.
POLY_API_KEY, or in a secret manager rather than in source control.
A connection is a conversation
You open one WebSocket and keep it open. One connection carries the whole conversation: you authenticate once, and every turn travels over the same socket as JSON text frames. To open it, connect and send your API key in theX-API-KEY header:
connect
Authorization: Bearer YOUR_API_KEY works too, so an existing realtime client, including the official openai SDK, keeps its auth and only needs a new URL and key.
The moment the connection is accepted, before you’ve sent anything, the server sends you a session.created event. You’re already in a session, running default settings you’ll want to change: 24 kHz PCM16 audio in, server turn detection, and spoken replies.
Telling it how to behave
Connecting gives you a working session before you configure anything, so configuration is a separate step rather than a set of required connection parameters. Sendsession.update with your instructions, tools, and anything else; the server replies with session.updated, echoing back the full configuration in effect.
you send: session.update
The shape of a turn
Every turn is the same two-part move. Adding something to the conversation and asking for a response to it are separate events:you send: conversation.item.create
you send: response.create
Watching the answer arrive
The reply comes back as a short burst of events rather than one block of JSON. Three of them matter at first:response.output_audio_transcript.deltacarries the words of the reply. Append them to see what it says.response.output_audio.deltacarries the speech: base64 PCM16 audio, 24 kHz mono. Play it, or collect it.response.donetells you the turn is over and how it ended.
server sends: the response
dialog-rsn-1: “The capital of France is Paris.”The audio streams at speaking pace, so
response.done arrives once the reply has been spoken. The events between response.created and response.done track which output item the server is writing and where its content part begins and ends. A fuller integration will use them. For a first pass, the deltas and the final status are enough to hold a conversation.
That’s the loop: add an item, ask for a response, collect the deltas until response.done. Repeat it for a longer conversation. Each new message builds on the conversation so far, so you have no separate history to maintain.
All of it, together
The same sequence as one script, running on the standardwebsockets package. It prints the reply as it streams and saves the speech to reply.wav. You need Python 3.10 or later, pip install 'websockets>=14', and POLY_API_KEY set in your shell.
The version floor matters: connect() took extra_headers before websockets 14 and additional_headers from 14 on, so the script below raises TypeError on an older install rather than failing to authenticate.
python
output
reply.wav in any audio player, or with afplay reply.wav on macOS.
The error branch is not optional padding. Almost every failure on this API answers with an error event and leaves the connection open, so a turn that fails arrives as an event rather than a dropped socket. The few that close the socket, such as an oversized frame or an idle connection, are listed under close codes. Skip the branch and a failed generation prints nothing and exits zero. The same goes for session.update: a refused update answers with error instead of session.updated and applies nothing, which is why the script checks the reply.
Bring your own text-to-speech
The server’s voice is the quickest way to a spoken reply. When you want your own voice, your own TTS vendor, or control over playback, ask for text instead. Setoutput_modalities to ["text"]:
you send: session.update
response.output_text.delta, with no audio events, and you speak it however you like. In the script above, add "output_modalities": ["text"] to the session and read response.output_text.delta in place of response.output_audio_transcript.delta. The streaming text-to-speech guide takes it from there. Ask for one or the other: ["text", "audio"] is refused.
Beyond a single turn
The same add-then-respond shape extends in two directions:- Function calling: declare tools in
session.tools; a call arrives asresponse.function_call_arguments.done, and you answer it with afunction_call_outputitem, followed by anotherresponse.create, the same two-part pattern from earlier, with a tool result in place of a user message. - Voice in: append PCM16 audio via
input_audio_buffer.appendinstead of creating a text item; Dialog-RSN-1 transcribes speech as part of the same response, streaming the transcript alongside the reply.
Overview
What realtime streaming is, and how turn-taking, generation, and prompting fit together.
Dialog-RSN-1 reference
Every event, the session object, function calling, and error codes.
Guides
Build a voice agent from scratch, or on top of a framework you already use.
Error codes
The platform-wide error reference.

