Skip to main content
Realtime voice is in beta and access is gated per workspace.
A realtime session is a voice call between your client and a provider’s realtime model, relayed by Phaseo so it can be metered and billed. The relay forwards the provider’s own events unchanged and adds three of its own: session.end, session.warning and session.ended.

Start a session

Create the session from your server:
The response includes client_secret, connect.url, max_duration_seconds and idle_timeout_seconds (always null for API sessions). Open a WebSocket to connect.url on api.phaseo.app with the WebSocket subprotocols statsync-realtime and rtsec.<client_secret>. The relay selects statsync-realtime; browsers and most WebSocket libraries fail the handshake if you offer only rtsec.<client_secret>, because no protocol is selected. The relay sends relay.connected once the provider is ready, then accepts microphone audio as {"type": "client.audio", "audio": "<base64 PCM16>"}: 24 kHz for OpenAI, xAI and ElevenLabs, 16 kHz for Google and Mistral. Phaseo holds $5 of credit when the session starts and extends the hold as the call runs. The TypeScript and Python SDKs do all of this for you: client.realtime.connect() creates the session and opens the socket, and session.end() returns session.ended. The other SDKs have the same small client; each package README has an example. The C++ SDK has no realtime client.

Google Live connections

Keep streaming microphone audio for the whole call, including silence. Google closes a connection that receives no audio for about 150 seconds (close code 1008), and the call then ends with provider_closed. The relay sends a provider_closing warning with threshold: "input_idle" after 2 minutes without audio. Silence between turns is not part of any turn, so it isn’t billed. Google also replaces each connection after about 10 minutes. The relay moves the call to a new connection with Google’s session resumption, keeps your socket open and sends {"type": "relay.provider_reconnected", "reconnects": 1} when the switch is done. Audio you send during the switch (usually under a second) is held and passed on. The resumed connection reprocesses the conversation so far, and Google bills that again as input tokens on the next turn, so a long call costs a little more per turn after each switch. All of it is included in usage and the bill. The relay only warns with provider_closing if it can’t resume, and the call then ends when Google closes the connection.

GPT Live sessions

GPT Live (openai/gpt-live-1) handles the voice and hands reasoning to a Responses backend model. Create it with POST /v1/live/sessions:
voice is one of alloy, ash, ballad, beacon, bossa, cedar, cinder, coral, delta, echo, gleam, marin, meridian, quartz, ripple, sage, shimmer, stone, tempo, verse, vesper or willow. backend_model is openai/gpt-5.6-luna (default) or openai/gpt-5.6-terra. Connect and stream 24 kHz audio the same way as other sessions. Function tools run on your client. The backend’s call arrives as a response.event whose event.item.type is function_call. Its name and arguments come from the model, and what the caller says can steer them, so treat them as untrusted input:
  1. Run only tools you declared, looked up by name, and refuse any other name.
  2. Parse arguments as JSON and validate it against the tool’s parameters schema before using it.
  3. Check that the signed-in user is allowed to do what the call asks, and confirm with them before anything that changes data, spends money or contacts someone.
Then run the tool, send the result and ask for the follow-up reply. If a call fails these checks, send an output that says why instead of running it:
The relay accepts a function_call_output only for a call the backend made, once per call and up to 64 KB, and response.create only after at least one output. Anything else gets {"type": "relay.client_event_rejected", "reason": "..."} and the call continues. Phaseo never runs function tools itself. A GPT Live call is billed per second of voice ($0.05 per minute, from GPT Live’s own usage.seconds) plus the backend model’s tokens for every response, including the replies after tool results. GPT Live expires a session that receives no audio for about 30 seconds, which ends the call with provider_idle_timeout. Keep streaming microphone audio, silence included.

ElevenLabs Realtime sessions

ElevenLabs Realtime comes in two models: eleven-labs/eleven-realtime-v1-mini, the faster and cheaper default, and eleven-labs/eleven-realtime-v1-max, which reasons before it answers and suits complex instructions and multi-step tool use. Both use the same create request and relay as OpenAI, with 24 kHz audio. Phaseo connects to ElevenLabs with its own key; your client only ever sees the relay. ElevenLabs bills a call for every second it’s connected, plus the language-model tokens it uses, and it doesn’t report usage during the call. The relay meters connected time while the call runs, so holds and the overdraft cap work as for other providers. When the call ends, Phaseo reads ElevenLabs’ bill for the conversation and charges the larger of that and its own list-price total. That usually takes a few seconds. If it takes longer, session.ended arrives with billing_status: "pending" and a final_bill request, and the bill is settled as soon as ElevenLabs reports it, normally within a few minutes. Your holds stay in place until then. If ElevenLabs hasn’t reported the bill after 24 hours, the session moves to billing review rather than being charged for connected time alone.

Streaming transcription

Mistral’s Voxtral Realtime models transcribe live audio as it arrives: mistral/voxtral-mini-transcribe-realtime-3 and mistral/voxtral-mini-transcribe-realtime. They don’t speak back, so create the session without voice or instructions:
Connect the same way and stream 16 kHz PCM16 as client.audio. Mistral’s events are forwarded unchanged: session.created, then transcription.text.delta with each new piece of text, and transcription.language and transcription.segment where Mistral sends them. To change the transcription settings, send {"type": "session.update", "session": {...}} with any of language, target_streaming_delay_ms (80 to 1,200, or 2,400), context_bias, diarize and turn_detection. Other fields, including the audio format, are rejected with relay.client_event_rejected. Settings are fixed once audio starts, so send session.update before the first client.audio; later updates are rejected the same way. The older mistral/voxtral-mini-transcribe-realtime accepts only target_streaming_delay_ms. When you send session.end, the relay ends Mistral’s audio stream and waits up to 10 seconds for transcription.done, which carries the full transcript, before it settles and sends session.ended. Use the audio you have already sent; nothing is discarded. Streaming transcription is billed per minute of audio you send ($0.008 per minute for Voxtral Mini Transcribe Realtime 3), measured by the relay. Mistral’s own count in transcription.done is the minimum charged. For a recording you already have, use audio transcriptions instead.

End a session

Send session.end when the caller hangs up:
The relay then:
  1. stops accepting audio;
  2. discards microphone audio the provider has not committed to a turn;
  3. cancels the reply in progress, or lets it finish, and waits up to 10 seconds for its usage;
  4. for GPT Live, sends the provider session.close and waits for session.closed;
  5. settles the bill;
  6. sends session.ended;
  7. closes the socket with code 1000.
Closing the socket without session.end also ends the call and bills it the same way, but your client won’t receive session.ended.

session.ended

The relay sends session.ended on every ending: one you requested, one the provider started and one the gateway started. It’s sent while your socket is still open, just before the relay closes it.
If settlement is still being retried, billing_status is pending and cost shows the running estimate. If the session needs a billing review, it’s unresolved. In both cases final_bill gives the request to fetch the final cost once it’s settled:

Reason codes

New codes may be added, so treat an unknown reason like gateway_error.

session.warning

Warnings tell you what’s coming without ending the call. Each code is sent at most once per threshold.
remaining_usd is what the call can still spend before it ends, including the overdraft.

Limits

API sessions are never ended for being idle, and spend limits only warn. A call ends on its own only when one of these happens: Session limits count from when the relay connects, not from when the session was created.
Last modified on October 29, 2026