Skip to main content
The streaming WebSocket transcribes audio while you send it. You stream raw audio as binary frames, and the server sends text back as it is recognized. Use it for voice agents, live captions, phone calls, and dictation.

Endpoint

For the message schemas, see the ASR WebSocket reference. For complete Python and Node.js programs, see the quickstart.

Authenticating

Send your API key in the chariotai-api-key header on the WebSocket handshake. If your client cannot set handshake headers, pass it as the api_key query parameter instead:
Prefer the header where your client allows it, and keep the key on your server. If authentication fails, the server sends an error event and closes with code 1008.

How a session works

1

Connect

Open the WebSocket with your options in the query string. The server replies with session.started. Wait for it before sending audio.
2

Stream audio

Send audio as binary frames as you capture it. transcript.delta events arrive with new text as it is recognized.
3

Finish

Send input.done after your last frame. The server transcribes the remaining audio, sends transcript.done with the complete transcript, and closes with code 1000.
One connection is one session. Settings are fixed for the session. To change the language, mode, style, or audio format, open a new connection.

Connection parameters

Pass these as query parameters on the connection URL. All are optional.
string
default:"pcm_16000"
Encoding and sample rate of the audio you send, always mono. One of pcm_8000, pcm_16000, pcm_22050, pcm_24000, pcm_32000, pcm_44100, pcm_48000 (signed 16-bit little-endian PCM at that rate), ulaw_8000 (G.711 mu-law), or alaw_8000 (G.711 A-law). Declare the rate you actually send. See Audio formats.
string
Language hint for the session: en, hi, bn, gu, kn, ml, mr, or, pa, sa, ta, or te. Omit it, or pass none, for automatic language detection. It does not translate. See Languages.
string
default:"balanced"
balanced prioritizes responsiveness, for interactive applications. high_accuracy allows more context before committing text, which increases latency and usually gives more accurate transcripts. See Modes.
string
default:"codemix"
How mixed-language speech is written. codemix keeps borrowed words, usually English, in their own script. native writes everything in the script of the spoken language. See Styles.
string
default:"chariot-1-asr-realtime"
The speech-to-text model. chariot-1-asr-realtime is the default and only value.
number
default:"60"
Seconds the server waits for your next message, audio or JSON, before closing the session as idle. Must be greater than 0 and at most 300. A value outside that range rejects the connection.
string
Your API key, for clients that cannot set the chariotai-api-key header.

Client to server

Audio (binary frames)

Send audio as binary WebSocket frames in the declared audio_format. Each frame is raw samples, not a file: no WAV header, no JSON, no base64. Any frame size works, and a frame does not have to end on a sample boundary. Frames of about 100 ms are a good default, for example 3,200 bytes at pcm_16000. See chunk size.

keepalive

Keeps the session open while you have no audio to send, for example while a microphone is muted. It resets the idle timer. The server does not reply.

input.done

Signals that no more audio is coming. The server transcribes everything it has received, sends any remaining transcript.delta events and then transcript.done, and closes with 1000. Audio sent after input.done is not transcribed.
All JSON messages are text frames. A text frame that is not valid JSON, is not a JSON object, or has an unknown type gets an error event without a code, and the session continues.

Server to client

The server sends JSON text frames only.
event
The session is ready for audio. Sent once, first. Echoes the settings in effect.
event
New text, as it is recognized. Each delta adds to the text before it, so append text to what you have shown so far. Spaces are included in text, so concatenate deltas without adding separators.
event
The complete transcript, sent once after input.done. audio_seconds is the duration of the audio you sent. The connection then closes with 1000.
Treat text here as the final result, and use it in place of the deltas you accumulated.
event
The session will reach max_duration_seconds soon. Sent once, about a minute before the limit.
event
Something went wrong. When the event has a code, the error is terminal: the server closes the connection with that same close code. Without a code, the error concerns a single message and the session continues.

Keeping a session open

The server closes a session as idle when it receives no message for idle_timeout seconds (60 by default). Every audio frame and every JSON message resets the timer. An idle close sends an error event with code 1008 and closes with 1008. If you pause the audio, send {"type": "keepalive"} more often than the idle timeout, for example every 10 to 20 seconds. Sessions that end on an idle timeout do not get a transcript.done. The text recognized up to that point arrived in transcript.delta events and is saved in history.
Text for your most recent speech arrives only after more audio follows it, and high_accuracy holds back more of it than balanced. With a live source, keep streaming through pauses, silence included, so that text arrives during the pause. While you send only keepalive, it waits until audio resumes or you send input.done. When you have no more audio, send input.done to get the final text.

Session length

A session can run for up to max_duration_seconds, announced in session.started (currently 10,800 seconds, three hours). About a minute before the limit, the server sends session.expiring. At the limit it sends an error event with code 4413 and closes with 4413, without a transcript.done. To transcribe a stream longer than the limit, open a new session when session.expiring arrives, move your audio to it, and send input.done on the old session to collect its final transcript.

Concurrency

Each open session uses one speech-to-text concurrency slot for its entire life, including pauses, until the connection closes. The number of slots depends on your plan. When every slot is in use, a new session gets an error event and closes with 4429. See Rate limits & concurrency. Close sessions you no longer need, and send input.done rather than leaving a finished session open until it times out.

Billing

Streaming costs 1.5 credits per second of audio you send, at any sample rate or encoding. Time with the connection open but no audio sent, such as keepalive-only pauses, is not billed. Silence you send as audio is billed like any other audio. Credits are charged periodically while the session runs and settled when it closes. A session’s total is the audio duration multiplied by 1.5, rounded up to a whole credit. To open a session, your workspace needs a small minimum balance (currently 30 credits). Otherwise the server sends an error event with code 4402 and closes. If your balance runs out during a session, the server sends an error event with code 4402 and closes, without a transcript.done. See Credits & pricing.

Close codes

The error event’s message explains the cause. Branch on the code, not on the message text. When a session fails to start, nothing is billed.

Best practices

  • Declare the right format. Audio sent at a different rate than audio_format declares is transcribed as the wrong speech, without an error.
  • Wait for session.started before sending audio, and read max_duration_seconds from it.
  • Send about 100 ms per frame from live sources, as you capture it.
  • Always end with input.done to get transcript.done. Closing the socket yourself skips it.
  • Use transcript.done as the final text. Deltas are for live display.
  • Handle 1011 by reading the error message: if it names an option to change, reconnect with that value; otherwise reconnect after a short delay. On 4429, wait for another session to finish, or retry with backoff.
  • Set language when you know it. Omit it for mixed or unknown speakers.

Example: keepalive during pauses

This Python fragment sends audio from a queue and falls back to keepalive messages while the queue is empty, for example while a microphone is muted:
Python