Realtime Speech To Speech
Stream microphone audio into a realtime translation session and receive transcripts, translated text, and translated audio.
WSS
Bidirectional WebSocket endpoint for real-time speech translation. This endpoint is served by
Sessions run in one of two modes, selected with the
See the Realtime Speech Translation tutorial for the microphone quickstart, the full event list, and configuration.
The sections below document the underlying wire protocol for reference (for example, if you are building a client in a language without an SDK).
The initial
If both the request header and
How it behaves by default:
The server answers with
See Detecting the source language
in the SDK tutorial.
realtime-api-server at wss://realtime.camb.ai/v1/realtime, separate from the /apis/live-tts/ws and /streaming-transcription/listen WebSocket endpoints.
mode query parameter or the mode field of the first session.update. fast (the default) accepts audio almost immediately and translates with the lowest latency, but supports fewer languages. slow supports the full language list and translates more accurately, at the cost of a 30s+ cold boot before the session is ready.
mode replaced a model parameter that took engine codenames. Both are still accepted — iris resolves to fast, and lilac/violet/orchid to slow — so existing integrations keep working while you migrate.
Authenticate with the x-api-key WebSocket request header. If your client cannot set WebSocket headers, send credentials in the first session.update event instead.
Quickstart
Use the SDK (Python or TypeScript) — it handles the session lifecycle (including thesession.starting cold-boot wait), surfaces the server’s binary audio frames as typed audio events, and exposes typed events. Input and output audio are PCM16, mono, 24 kHz. The example below streams a WAV file and writes the translated speech to another WAV.
Integration in 4 steps
1
Open the realtime socket
Connect to
wss://realtime.camb.ai/v1/realtime. This endpoint is not under the client.camb.ai/apis namespace used by the other WebSocket API references.2
Send session.update as the first message
The first WebSocket message must be a JSON The server responds with
session.update event. The server waits up to 60 seconds for it.session.created, then session.updated.3
Stream input audio
Send microphone audio as base64-encoded bytes in Each decoded audio payload can be up to 256 KiB.
input_audio_buffer.append. Only text WebSocket messages are parsed as realtime events.4
Read translated output
Listen for transcript, translated text, and translated audio events.
response.text.delta values are additive for the current response, and response.audio.delta contains base64-encoded synthesized audio bytes.Authentication
Prefer the WebSocket request header:session.update event can also carry credentials:
auth object are present, the request header credential is used.
Reference
The AsyncAPI spec above documents every client and server event. Quick lookup:Session configuration
Voice selection
By default, translated speech is synthesized with a built-in voice for the target language. To synthesize the translation with one of your own cloned voices, include avoice object in the session configuration:
The voice must belong to your account — stock/built-in voice IDs are rejected with an
error event. Omitting voice (or sending { "type": "default" }) uses the built-in voice. The resolved selection is echoed back in session.created.
If you use the SDK, pass voice_id (Python) or voiceId (TypeScript) to realtime.connect() and it builds this voice object for you:
Automatic source language detection
Setsource_language to auto and the server works out which language is being spoken, and keeps
listening for changes. It reports the language with session.source_language_changed, and labels
every completed transcript with the language it was identified as:
- First detection. The language is set after the first full sentence.
- Switching. Once set, the language switches after about two sentences, or 4 seconds of speech, in the new language. A single word, a name, or one sentence in another language doesn’t switch it, and an utterance in the current language cancels a pending switch. Speech counts as each sentence ends, so a speaker who keeps talking without a pause still switches it.
- Utterances that can’t be identified. Very short or ambiguous utterances (“ok”, a number) have
no
languageand don’t count towards a switch. - Same language as the target. Speech already in the target language passes through as text:
its transcript arrives with
passthrough: true, and nothing is translated or spoken for it. - Candidates. Naming the languages you expect in
source_language_candidatesmakes detection faster and more accurate. Without candidates, every language the server can detect is considered. A candidate that can’t be detected automatically is rejected when the session starts, with anerrornaming it.
auto. In fast mode, set a starting target_language, which must be one of
the supported languages; every later target must be one too.
Changing languages during the session
A session started withauto accepts further session.update events that change
source_language, target_language, or both. Nothing else can change once the session is running.
session.updated, and with session.source_language_changed when the
source language changed. A change the session can’t make is answered with an error that has a
code, and the session carries on:
Speaker setup
speaker_setup tells detection who speaks into the session:
-
changing_speakers(default): a remote party, a meeting room, or a broadcast. The language switches as soon as there is enough evidence. -
single_known_speaker: your own microphone. The language switches only after about 4 seconds of speech in the new language, however many sentences that is. And if the session hears its own target language for that long, it doesn’t switch. On speakers rather than headphones, that is usually the other party coming through. Instead the server sends:To accept it, set the source language yourself:{"type": "session.update", "session": {"source_language": "de-de"}}. To reject it, do nothing.
item_id back within about half a second, before the switch applies, so it
doesn’t count towards a switch:
Translating both sides of a call
A session translates one direction. For a conversation, open one session per direction and link them: each session translates into the language the other session detects. Whenever one session sendssession.source_language_changed, send its language to the other session as
target_language. Neither person has to pick a language.
play_to_the_other_party, play_to_you, stream_microphone and stream_call_audio are your own
audio plumbing.
Use single_known_speaker for the microphone session, and wire up
session.source_language_switch_confirmation_requested if your users may be on speakers. A
confirmed switch arrives as session.source_language_changed like any other, so the link passes it
on.
The Python and TypeScript SDKs do this linking for you. connect_session_pair (connectSessionPair
in TypeScript) opens both sessions, sets their speaker setups, and keeps each session’s target
language equal to the other’s source language:
Supported languages
source_language and target_language accept the BCP-47 tags below (case-insensitive). Pick any supported language as the source and any supported language as the target.
Supported realtime languages (14)
Supported realtime languages (14)
Client events
Server events
Limits
Billing
Active sessions are charged in billing windows and finalized on close, failure, or billing stop. If billing stops a session, for example when credits run out, the server sends anerror event with code insufficient_credits (or billing_stopped) and closes the session.
Errors
Errors arrive as anerror event:
When an error ends the session, the server closes the WebSocket right after the
error event. The close frame’s reason is the same code, so a client that missed the event can still tell what happened. The close code is standard:
Errors that end the session:
Errors the session survives. The request is refused and the session carries on:
If the request is refused before the WebSocket opens (an unsupported
mode query parameter), the server answers with HTTP 400 and the same error object as the body.Last modified on October 22, 2026