Realtime transcription

Stream audio over a WebSocket and get transcripts back while the speaker is still talking.

The batch endpoint waits for a whole file. This one takes audio as it arrives and emits a transcript for each segment as soon as the speaker pauses — the same recogniser, a different rhythm.

Why this lives under /api, not /v1
The gateway won't carry this path: an Upgrade to /v1/realtime/transcribe is refused by LiteLLM, which owns /v1/realtime for its own OpenAI-realtime bridge and does not know this service's protocol. Verified both ways — the transcription service itself answers 101 Switching Protocols to the identical handshake. So live transcription is served same-origin, one level up, where the console proxies it through to the backend with the same key you already have.
WS/api/realtime/transcribe
ParameterTypeDescription
auth
Optional
subprotocol | queryThe WebSocket subprotocol bearer.<key> is preferred — a subprotocol never lands in an access log or a Referer header. ?key=… works as a fallback for a client that can't set one, at the cost of the key sitting in the URL.
binary frame
Optional
bytesRaw float32 mono PCM at 16kHz, or any container ffmpeg reads. Send audio in small chunks as it's captured, not the whole file at once.
{"type":"config"}
Optional
text frameSets model and language before you send audio; answered with {"type":"ready",…}.
{"type":"commit"}
Optional
text frameFlush whatever is buffered right now, without waiting for silence.
{"type":"done"}
Optional
text frameEnd the session: flush anything left, then close after sending {"type":"done"} back.
A live session
# websocat is the practical "curl for WebSockets" -- there is no
# raw curl incantation that speaks this framing. Convert the file
# once, then stream the control frames and the raw audio together;
# -B keeps each write on its own frame instead of coalescing them.
ffmpeg -v error -i voice.m4a -ac 1 -ar 16000 -f f32le voice.raw

{ printf '%s\n' '{"type":"config","model":"whisper-large-v3","language":"persian"}'
  cat voice.raw
  printf '%s\n' '{"type":"done"}'
} | websocat -B 1000000 \
    --header "Sec-WebSocket-Protocol: bearer.$LARSA_API_KEY" \
    wss://api.console.larsa.larsima.com/api/realtime/transcribe

# {"type":"ready","model":"whisper-large-v3","language":"persian"}
# {"type":"final","text":"قرارداد اجاره باید به صورت کتبی تنظیم شود.","duration":3.9}
# {"type":"done"}

Segmentation: cut on silence, not on a timer

A segment closes and a final event fires once at least a second of audio has arrived and the trailing 200ms has stayed below a silence threshold for 600ms straight, or once the buffer hits 20 seconds regardless of silence. The silence check looks only at the tail of the buffer, so a pause in the middle of a sentence doesn't end the segment early — only a pause that lasts.

What comes back

Only final events — there is no interim partial transcript while a segment is still filling. Each one carries text and duration (the length of that segment, in seconds — not its position in the overall stream). Like the batch endpoint, there are no word or segment timestamps beyond that.

model accepts the same three values as the batch endpoint — whisper-large-v3 (default), whisper-large-v3-turbo, whisper-fa. There is no fusion here; reconciling four transcripts costs roughly twice the time, which defeats the point of "while they're still talking".
Navigate Open esc Close