Create a managed realtime session
POST /v1/realtime/sessions creates a LiveKit room and returns a scoped
participant token. Authenticate with a secret API key and provide either a
saved agent_id or an inline config, but not both.
prompt, voice, product, tools,
primary_language, additional_languages, background_track, telephony,
placeholders, outbound, and call_events. prompt is required.
Unknown keys are rejected with a validation error rather than silently
ignored.
201 Created with the room credentials:
Endpoint
The realtime endpoint shares its host with the REST API — the same
api.thunderphone.com base URL and API key work for both.Connect and authenticate
Authenticate the WebSocket upgrade with your secret API key:session.created. Configure
the call with session.update; the server confirms the accepted settings
with session.updated.
Configure the session
Send the GA nested session shape before streaming audio. The firstsession.update starts the call. When instructions is omitted or empty,
ThunderPhone uses You are a helpful voice assistant.
Session fields
session.config accepts only voice, product, tools,
primary_language, additional_languages, background_track, telephony,
placeholders, outbound, and call_events. The call prompt comes from
session.instructions; there is no separate prompt key over the WebSocket. Unknown keys are rejected with a
validation error rather than silently ignored. Set telephony to false for
the same wideband or browser-style input tuning selected by
session.audio.input.noise_reduction.type set to near_field.
All audio is mono. PCM audio is signed 16-bit little-endian at 16000 or
24000 Hz; G.711 (audio/pcmu μ-law, audio/pcma A-law) is 8000 Hz, matching
telephony trunks — no client-side transcoding needed. Input and output formats
are configured independently, but neither can change after the call starts.
After the call starts, agent configuration is immutable: instructions,
voice, tools, product, and other session.config fields cannot change.
Bridge-local transport options such as binary_output remain changeable.
For compatibility, flat instructions, voice, input_audio_format, and
output_audio_format fields are also accepted on session.update. New
integrations should use the nested shape above.
Inline sessions can define custom functions under session.config.tools. The
function schema is available to the agent; your WebSocket client remains
responsible for executing the function and returning its output.
Inline sessions are client-steered
With inline configuration, ThunderPhone adds no automatic conversation behaviors. The agent never speaks first, there are no silence check-ins or timeouts, and by default nothing is spoken while a response is being prepared. If you want progress speech during slow responses — for example while the agent waits on one of your tool results — setplaceholders in the session
config to a list of short phrases in your own words (e.g.
{"placeholders": ["One moment.", "Still checking."]}). They are spoken in
the session voice, cycling only when a response takes long enough to need
them.
If the caller goes quiet, the session remains open until your client prompts
the agent or hangs up. Silence-behavior config keys are not accepted and are
rejected with a validation error.
To have the agent open the call, configure the session, append a system or user
message, and explicitly request one response:
session.created is sent as soon as the WebSocket connects. session.updated
is sent only after the configuration has been accepted and the session fully
provisioned, so it is the signal that the call is live; a rejected
configuration produces an error event instead, and the session stays usable
for a corrected retry. You do not need to wait, though: message items and
response.create sent early are buffered and delivered in order once the
agent is ready, so the opening sequence above is safe to send immediately
after session.update.
Sessions created with agent_id instead retain the behaviors configured on the
saved agent, including its greeting order and silence check-ins.
Use a saved agent
To use an existing agent, connect with its id:session.update with instructions
is not required. Set its wire audio before minting with the optional
input_audio_format, output_audio_format, input_rate, and output_rate
query parameters. Formats accept audio/pcmu, audio/pcma, g711_ulaw,
g711_alaw, or pcm16; PCM rates accept 16000 or 24000 (G.711 is always
8000 Hz). For example:
Client events
Send client events as JSON text frames unless otherwise noted.Append audio
Send text guidance
Message items are appended to the conversation and never trigger a response by themselves. Useuser for a completed caller utterance and system, with
developer accepted as an alias, for silent guidance. A response begins either
from server turn detection on audio or from an explicit response.create.
Message items are acknowledged with conversation.item.added followed by
conversation.item.done; both echo the client-provided item.id, or a
generated id when it is absent. Text is not converted into caller audio.
"role": "system" in the same message shape.
Assistant-role message injection is unsupported.
Request a response
Sendresponse.create to trigger exactly one response using the conversation
so far. If a response is already in progress, the server emits an error event
with code conversation_already_has_active_response and does not start another.
After handling a custom function call, the client must send a
function_call_output item with the received call_id. The output can be a
JSON string or object. The default deadline is 30 seconds. If the deadline
expires, the conversation continues with an error result so the agent can
recover. Every function call names one of your declared tools and expects a
function_call_output; platform actions arrive as call.* events instead
(see below) and take no output.
Server events
Server events are JSON text frames. Each JSON event includes a uniqueevent_id.
Conformance notes
ThunderPhone supports PCM at 16000 Hz as an extension. The OpenAI GA schema pinsaudio/pcm to 24000 Hz, so strict validators of session.created and
session.updated should also allow rate 16000.
The normal audio response sequence is:
Function calls
Function calls arrive asfunction_call output items and always name one of
the tools your client declared. Read the function name and call_id from
response.output_item.added, accumulate argument deltas, and act after
response.function_call_arguments.done. Platform actions are never function
calls — they arrive as call events.
Call capabilities
Every session’s agent can end the call and can deliberately stay silent for a turn while the far side is still talking — fundamental phone-call behaviors, always on. Sessions withoutbound: true additionally wait out hold queues,
and — with call_events enabled — press keypad digits to navigate menus.
Transfers also require call_events: the agent decides to transfer, and your
client executes it on your telephony stack from the call.transfer
instruction. Capabilities that instruct your client are only granted when the
event channel exists to carry the instruction; there is nothing else to
configure or declare.
A set of function names is reserved for the platform and rejected in your
tools with a validation error: end_call, transfer_call,
send_keypad_input, no_response, wait_on_hold, search_knowledge_base,
play_sound, speak_uninterruptible.
Call events
Platform call actions and state changes are delivered as dedicated events in thecall.* namespace — a ThunderPhone extension to the realtime event set.
They are an explicit opt-in: set call_events: true in the session config to
receive them. They are notifications: nothing is sent in reply, and there is
no call_id or output. Handle every type below in your event loop — clients
ported from other realtime APIs often switch on known event types and would
otherwise drop them.
Outbound calls
Setoutbound: true in the session config when the session is an outbound
call — the agent dialed out, so the far side may be an automated menu, a hold
queue, voicemail, or a screener rather than a person. This enables the
engine’s outbound call handling: the agent recognizes and navigates automated
systems reliably, responds immediately when a human picks up so they are not
met with dead air, and applies cost-saving measures that keep the price down
while the call is working through menus, waiting on hold, or handling
voicemail. It is off by default; sessions without it treat the far side as a
human caller. Menu navigation and hold handling need no further
configuration — steer them through your instructions:
Using the official OpenAI SDK
Existing OpenAI Realtime integrations work by changing only the WebSocket base URL and the API key. Pass the base ending at/v1 — the SDK appends
/realtime itself, mirroring wss://api.openai.com/v1:
model argument is echoed back but does not change behavior; the agent
configuration comes from the session instructions or a saved agent.
Minimal Python client
Installwebsockets, set THUNDERPHONE_API_KEY, and provide an uncompressed
mono pcm16 WAV file sampled at 16000 Hz:
Protocol notes
- Turn detection uses server-side voice activity detection and is always on. Clients do not need to commit or clear the input audio buffer.
- Transcript events are emitted as per-utterance segments, not token-by-token
deltas. Use the
completedanddoneevents as final text. - Unknown or unsupported client event types produce an
errorevent without closing the WebSocket. The client can correct the request and continue.