Skip to main content
Connect to wss://prism.api.rime.ai/v1/realtime from your backend, and send your API key on the upgrade request as Authorization: Bearer YOUR_API_KEY. Each connection is one conversation. Your backend sends the caller’s audio as base64 16 kHz mono PCM16. Prism finds the end of each caller turn, and replies with text and with 24 kHz audio in the same format. Use this API when your backend uses a language other than Python, Node.js, Go, or Rust. Otherwise, the Rime SDK implements this protocol for you. The WebSocket quickstart makes a first call with a plain WebSocket client, and Errors and limits tells you what to do about each error code.

Session at a glance

Guarantees

Complete example

This Python client holds one short conversation with the raw WebSocket API. It streams a recorded question at speaking pace, then keeps sending silence, as a microphone does. When Prism asks for the cafe’s opening hours, the client runs get_opening_hours, returns the result, and asks Prism to continue, as Tools explains. It prints the caller’s transcript, the tool call, and Prism’s replies, and saves the spoken replies to reply.wav. It stops with an error if Prism refuses one of its requests or a reply does not complete. It also stops if no event arrives for 30 seconds while it waits for a reply. The client does not play audio or report playback. A live client does both, as Stop playback when the caller talks over a reply explains. The client needs Python 3.11 or later, and websockets 14 or later for the additional_headers argument. Before you run it, do these steps:
  1. Install the package with pip install "websockets>=14".
  2. Set RIME_API_KEY in the environment of your backend.
  3. Download the quickstart’s sample question.
To use your own recording, convert it first with FFmpeg. This command reads input.m4a, or any format that FFmpeg can decode. It writes question.wav as 16 kHz mono PCM16, and it keeps the WAV header.
Record one question without long pauses. Prism decides when the caller has finished, and it can take a long pause as the end of the question.
prism_ws.py
Run the client with the file name, for example python prism_ws.py question.wav. A run with the sample question printed this output:
Prism sends two replies. The first reply makes the tool call, and the second reply uses its result. While the call is open, Prism speaks a short phrase, such as “One moment,” and its wording varies. The phrase is also in the text of the first reply, so the Prism: line of your run shows it too. reply.wav holds both replies, in the aurora voice. If a run fails, send the request and session IDs to support.

Rime SDK steps as WebSocket events

Each step in Live conversations has a WebSocket event. The tool steps are in Tools. The SDK handles two waits. Without the SDK, your backend must handle them:
  1. Send session.update before any audio or response.create, and then wait for session.updated, which confirms that Prism accepted the update. Until Prism accepts it, Prism refuses both with session_not_initialized.
  2. Send a typed turn or a nudge only after prism.typed_input.ready. If Prism refuses it as busy, send it again after the next prism.typed_input.ready. Prism does not keep a refused request for later.

Connect

Authentication

Send Authorization: Bearer YOUR_API_KEY on the upgrade request. The WebSocket API in a browser cannot set this header. Open the connection from your backend, and keep the key there. Prism has no ephemeral keys or tokens for client devices. Browsers and phones send their audio to your backend, and your backend sends it to Prism.

Upgrade refusals

Prism can refuse the upgrade with an HTTP response, before any event, and no error event follows. A successful upgrade returns 101 Switching Protocols.

Close codes

Audio appends do not count as activity, so a conversation that streams only silence still closes after 5 minutes. Any other client event, detected speech, or a reply starts the timer again. Each reconnect starts a new conversation with no memory of the old one. Send session.update again, and add the history that the agent needs with conversation.item.create messages.

Audio formats

Send the caller’s audio all the time, as Stream audio for the whole call explains. Include silence, because Prism uses the audio after the caller stops to decide that the turn has ended. Keep sending while Prism speaks, because Prism hears an interruption only in the audio that it receives. While the caller is muted, send silence. If no audio arrives for several seconds, turn detection works less well for the rest of the call, and Prism sends no error. The reply text holds every word of the reply audio, including these short lines that Prism adds to a spoken reply: Use the text for captions and logs. A reply can also have no audio at all. If a reply makes only tool calls and says no phrase, the reply has no audio, no response.audio.done, and no prism_drain_token. Prism says no phrase in a tool continuation, in a nudge with prism_call_id, or for a tool with prism_filler {"type": "none"}.

Session configuration

These are the fields of the session object in session.update. Every field is optional. In the first update, a field that you leave out takes its default. In a later update, a field that you leave out keeps its current value. Prism refuses any other field with invalid_client_event, and the error’s param names the field. A later update can change these fields: These fields keep the values from the first update. A later update can repeat the current value, but Prism refuses a new one: Prism reads the tool definitions itself, so say in instructions when to call each tool, as Tools explains. parameters is a JSON Schema object. Each tool can use either OpenAI shape, and if a tool has both, Prism uses the nested function:
  • Flat: {"type": "function", "name": ..., "description": ..., "parameters": {...}}
  • Nested: {"type": "function", "function": {"name": ..., "description": ..., "parameters": {...}}}
Prism accepts the first session.update that passes its checks, and that update sets up the session. After that, you can send session.update at any time, even while a reply runs. Prism refuses a new eagerness or prism_end_of_turn_timeout_ms with session_setting_fixed, and a new modalities value with unsupported_modality. Prism rejects the entire update if any field is invalid. Correct the field and send the update again. A reply that is already running keeps the settings that it started with, and the next reply uses the new settings. prism_settings_revision in the session object counts the updates that Prism accepted, and each reply carries the revision that it started with.

When the conversation outgrows the context

With truncation "auto", Prism leaves whole turns out of the prompt, oldest first, when the conversation no longer fits the context. Prism cuts until the history fills at most 80% of the context left after the instructions, tools, current turn, and reply. It cuts more than it must, so that the next turns keep the same cut and stay fast. A retention_ratio sets a share other than 80%. The turns that Prism leaves out stay in the conversation, and prism.conversation.get still lists them. When Prism leaves turns out of a reply’s prompt, that reply’s response.done says so in prism_truncation. A system message that you add goes out with its turn, so put lasting rules in instructions. Truncation leaves out only history, so if the instructions, tools, and current turn alone leave no room for the 512-token reply, the reply fails with context_length_exceeded.

Turns and interruptions

How a turn ends explains how Prism ends the caller’s turns, what interrupt_response changes, and how your backend handles an interruption.

Report playback

Stop playback when the caller talks over a reply explains when to send conversation.item.truncate, and Report when a reply finishes playing explains prism.playback.drained.

Tools

Tools explains a full tool round, from the call to the continuation. Timeouts and late results explains the two waits that prism_tool_waits reports. Over the WebSocket API, a tool round uses these events: Without the SDK, wait for these events before you continue:
  • The response.done of the parent reply
  • A conversation.item.created for every result
Prism refuses an earlier continuation with tool_continuation_not_ready, and does not keep the early continuation for later. The complete example runs one tool round this way.

Typed turns, nudges, and history

Typed turns and nudges explains when Prism accepts a response.create without new speech, and how to add history with conversation.item.create. Over the WebSocket API, you can also remove items with conversation.item.delete and list the conversation with prism.conversation.get.

Client events

Any client event can carry an event_id, a string that you choose, such as "evt_setup". No client event requires one. When a request has one, Prism copies it into prism_request_event_id on the acknowledgment, or into error.event_id on a refusal. Prism runs a request without an event_id too. Its acknowledgment then has no prism_request_event_id, and a refusal of it has error.event_id null. Prism refuses an event_id that is not a string, such as null or a number, with invalid_event. Prism does not check that an event_id is unique. Send an event_id on session.update, conversation.item.create, conversation.item.delete, prism.conversation.get, and response.create. Your backend waits for the answer to each of these, and the ID matches each answer to its request. Without an event_id, Prism suppresses these refusals: tool_call_not_found for a tool result that names a call this conversation never made, and response_superseded or nudge_superseded for a requested reply that does not run because the caller spoke first.

session.update

Sets up the conversation, and later changes its settings. Send the first update right after session.created. Prism answers with session.updated when the session is ready, up to 5 seconds later. Prism keeps audio and response.create that you send after Prism accepts the first update, even before session.updated arrives. Before Prism accepts an update, it refuses both with session_not_initialized. Send another update at any time to change a field that Session configuration lists as changeable, such as the voice. Prism answers each accepted update with session.updated, and the next reply uses the new settings. Refusals: invalid_client_event, invalid_session, unsupported_modality, invalid_voice, voice_required, and session_setting_fixed. A refused update changes nothing. The first update sets up the session:
A later update needs only the fields that it changes. This one changes the voice and the instructions:

input_audio_buffer.append

The caller’s audio, as you capture it. Prism sends no acknowledgment, and an append does not count as activity for the idle timeout. Refusals: invalid_audio for audio that is not base64, and session_not_initialized for audio that arrives before Prism accepts a session.update. Prism drops that early audio. It reports the first dropped append at once, and then sends at most one report every 5 seconds. The base64 value is shortened here:

input_audio_buffer.clear

Drops the caller’s speech that Prism has not committed as a turn, including speech that Prism holds while a reply plays, and resets speech detection. If Prism already sent a speech_started for that speech, no speech_stopped or committed follows. Prism answers with input_audio_buffer.cleared.

conversation.item.create

Adds a tool result, a message, or a past tool call to the conversation, without starting a reply. Prism answers with conversation.item.created. The new item goes at the end of the conversation, unless previous_item_id puts it somewhere else. A function_call_output item returns the result of a tool:
Refusals: tool_call_not_found for a call that this conversation did not make, and conflict for a second, different result. If a result for an unknown call has no event_id, Prism drops it with no error. A message item adds history, and it stays in the history until you delete it with conversation.item.delete. Its words can be up to 4,000 code points, and Prism refuses a longer message with item_too_long.
This request adds a note for the model, with an ID that the client chose:
This request puts a caller message right after the item msg_client_1:
A function_call item adds a past tool call, for example one from an earlier conversation. Prism shows the call to the model only after you add its result as a function_call_output with the same call_id. A past call never starts a tool round.
Prism also refuses these requests, and each refusal changes nothing:

response.create

Asks for a reply without new speech. response.metadata.prism_cause names the kind of reply, and Prism refuses a missing or unknown value with invalid_response_cause. Prism checks the whole request first, so a refused request changes nothing. Prism answers an accepted request with response.created. For a typed turn, Prism first sends conversation.item.created for the caller’s item. A typed turn:
A tool continuation:
A nudge:
Refusals for each kind of reply:

response.cancel

Stops the running reply. If you set response_id, Prism stops the reply only if it is the running reply. The response.done of that reply follows, with status cancelled and reason client_cancelled, and it carries the event_id of this request as prism_request_event_id. After that, the next waiting reply starts. Refusals: response_cancel_not_active when no reply with that ID is running. You cannot cancel a reply that is still waiting to start.
A cancel is not an interruption:
  • Prism removes the words of the cancelled reply from the history, unless the reply made tool calls.
  • Prism’s next reply does not acknowledge an interruption.
  • Prism does not undo tool calls that it already sent.

conversation.item.truncate

Reports how much of the reply audio the caller heard. Send it after an interruption, as What Prism remembers of an interrupted reply describes. Prism answers with conversation.item.truncated. The report also lets Prism acknowledge the interruption with a word, such as “Okay,” at the start of its next spoken reply. Prism does this when the report shows that the caller did not hear 300 ms or more of the reply audio. Refusals: item_not_found for an unknown item_id, invalid_content_index for a content_index other than 0, and truncate_not_current for a reply from an earlier period of playback.

conversation.item.delete

Removes an item from the conversation. The next reply no longer sees the item, but a reply that is already running still uses it. Prism answers with conversation.item.deleted at once, even while a reply runs, and even for an item that is already deleted. Refusals: item_not_found for an ID that this conversation never had.
Four cases work differently after a delete:
  • A tool call or its result: when you delete one of them, Prism removes both from what the model sees. The other calls of the same reply stay.
  • One part of a spoken turn: prism.conversation can list a spoken turn in several parts, each with its transcript. When you delete one part, the model no longer sees its transcript. The model still hears the audio of the turn while another part of the turn remains.
  • A tool call that waits for its result: after you delete the call, the tool round still waits. When the result arrives, Prism acknowledges it with conversation.item.created, but the model never sees the call or the result.
  • An item with an ID that you chose: after the delete, you can give that ID to a new item.

prism.conversation.get

Requests the stored conversation, including turns that truncation may omit from the next prompt. Prism answers with prism.conversation at once, even while a reply runs.

prism.playback.drained

An optional receipt that tells Prism that your player played the last sample of a reply. Prism does not acknowledge it. Only the most recent reply that sent audio has a live token, and Prism ignores a receipt with any other token.
Prism also accepts prism.client.turn_metrics, a timing report from the client, with an optional event_id. Prism does not read or acknowledge the report, and you do not need to send it.

Server events

Every server event has its own unique event_id, such as "evt_4a4fe962c23bf40b". The tables below list the other fields. Long values in the examples are shortened.

session.created

The first event on every connection, with the session’s defaults. Its prism_settings_revision is 0.

session.updated

Prism sends this event for every session.update that it accepts, including an empty update or one that repeats the current values. The event contains the whole session, with the settings in effect and the new prism_settings_revision. After the first update, prism.typed_input.ready follows, because a new conversation is idle. This event answers the first update, which sets up the session:
This event answers a later update that changes the voice and the instructions while a reply runs. The running reply keeps revision 2, and the next reply uses revision 3:

The session object

session.idle_timeout

The conversation was idle for idle_timeout_s seconds. Prism closes the connection with 1000 “idle” immediately after this event.

prism.typed_input.ready

Prism is idle and can accept a typed turn or a nudge now. Prism sends this event after session.updated and each time it becomes idle again, but does not repeat it while it stays idle. The event is true only at the moment Prism sends it, because the caller can start to speak before your request arrives. Treat typed input as unavailable from response.created, input_audio_buffer.speech_started, or an accepted typed turn until the next prism.typed_input.ready. The event has no fields other than type and event_id:

input_audio_buffer.speech_started

The caller started to speak. When interruption is on, stop playback and drop the queued audio on every speech_started, even if response.done already arrived. Prism also cancels any reply that it is still producing.

input_audio_buffer.speech_stopped

The caller stopped speaking. input_audio_buffer.committed for the same item follows, unless pending_speech_overflow discards the turn.
Prism can add values later, so accept a value that you do not know.

input_audio_buffer.committed

Prism committed the caller’s turn, and a reply to the turn follows. Prism can commit more turns while the next reply waits to start, and that reply answers all of these turns together.

prism.input_audio_buffer.speech_hint

An early, advisory hint that the caller may have started to speak during a reply, while the reply can still be playing. The Rime SDKs deliver it as SpeechHint in Python and speech.hint in Node.js. Lower playback on the early speech hint tells you how to use it.

input_audio_buffer.cleared

Acknowledges input_audio_buffer.clear.

conversation.item.input_audio_transcription.delta

More of the caller’s words, for display while the caller speaks. Prism replies to the caller’s audio, not to these transcripts.

conversation.item.input_audio_transcription.completed

The caller’s words for the whole turn. It can arrive before or after the reply to the turn. If this transcript differs from the deltas, use this transcript, because Prism keeps this one in the history.

conversation.item.input_audio_transcription.failed

Prism could not make a transcript of the turn, and the reply continues without it. This is not an error event.

response.created

A reply started. response.output_item.added for the reply’s message follows immediately. This reply answers a spoken turn:
When every reply slot is busy, a reply can wait after response.output_item.added. The reply has a first-output timeout of 20 seconds. If no slot becomes free in that time, the reply fails with capacity_exceeded.

response.output_item.added

One output item of the reply, without its content. The assistant message comes first, at output_index 0, and each tool call follows in order, from 1.

response.text.delta

The next piece of the reply’s text, as Prism writes it.

response.audio.delta

The next piece of the reply’s speech, with the same fields as response.text.delta. Here, delta holds base64 of headerless 24 kHz mono PCM16, little-endian, in whole samples. Deltas arrive faster than real time. The base64 value is shortened here:

response.function_call_arguments.done

A tool call for your backend to run, with all of its arguments. Prism sends no argument deltas. Tools lists the events of the full round.

response.output_item.done

The complete item of a tool call. Prism sends it only for tool calls, right after response.function_call_arguments.done. The content of the assistant message arrives in response.done.

response.text.done

The full text of the reply. Prism sends it before response.done for every reply, including cancelled, failed, and empty replies. A reply that only made tool calls and said no phrase has empty text.

response.audio.done

Prism finished sending the reply’s speech, and playback can still continue. Prism sends this event after response.text.done, only for a reply that sent audio. A reply that only made tool calls and said no phrase does not send it. This event has the same fields as response.text.delta, without delta.

response.done

Ends a reply. Prism sends exactly one for each response.created. It ends generation, not playback. If Prism sent any of the reply’s audio, the message’s content part is {"type": "audio", "transcript": ...}. If not, it is {"type": "text", "text": ...}. In both cases, the content part holds the reply text from response.text.done, including the spoken lines that Audio formats lists. This reply to a spoken turn makes a tool call:

conversation.item.created

Acknowledges conversation.item.create. It also acknowledges the caller’s item of a typed turn, before the response.created of that turn. It names the item, but does not repeat its text or output.
This event acknowledges the past call in the conversation.item.create example:

conversation.item.truncated

Acknowledges conversation.item.truncate. It repeats the values of the report.

conversation.item.deleted

Acknowledges conversation.item.delete, even if the item is already deleted. From the next reply on, the model does not see the item.

prism.conversation

Answers prism.conversation.get with the items of the conversation, in the order that the model sees them. The list also has any turns that truncation leaves out of the prompt. Each item has only the fields that its row lists: Prism lists each item from the moment that the last column shows, until you delete the item: In this listing, msg_root was added with previous_item_id "root", and msg_after_name with previous_item_id "msg_client_1":

error

Reports a refused request or a failure. Errors describes its fields.

Errors

An error event tells you what went wrong and what it affects. Base your logic on code and scope, never on message. Errors and limits lists all 41 codes and what to do about each one. These reply errors report a failure inside Prism and a conversation that is too long. Reply failures lists the other reply errors. This error answers a session.update that sets temperature:

Differences from OpenAI Realtime

Prism uses the event names of the OpenAI Realtime beta interface. An OpenAI Realtime client needs the changes below before it works with Prism. Move from OpenAI Realtime walks through them step by step.

The current OpenAI interface

OpenAI’s current interface renamed some events and nested the session settings. Prism uses the beta names and the flat session, so map them back: Client event names are the same in both interfaces. Prism refuses the nested session with invalid_client_event, and the error names session.audio, session.type, or session.output_modalities. GPT-Live is a different OpenAI API with its own events, so these tables don’t map it. Prism’s own events and fields have a prism prefix. The exceptions are session.idle_timeout, the session fields version and request_id, the usage field context_window, the error fields correlation_id, scope, and owner, and the reasons stop, disconnected, turn_failed, and response_timeout.

Limits

Errors and limits lists every limit and what happens when you pass it.