Live streaming
Stream audio over a WebSocket and receive Arabic transcripts as people speak — interim results while a sentence is in progress, a stable final when it completes, and a turn-boundary event your application can act on. First partials arrive in about 0.7 seconds (median), with word timestamps, per-word confidence, and per-turn sentiment and speaker gender.
Endpoint & authentication
wss://api.munsit.com/api/v1/listen?api_key=YOUR_MUNSIT_API_KEY&encoding=linear16&sample_rate=16000
At least one auth method is required. If several are supplied, the api_key query parameter wins.
| Method | Where | Notes |
|---|---|---|
x-api-key | Header | Preferred for server-side clients. |
api_key | Query parameter | For browser WebSocket clients, which cannot set headers. x-api-key is also accepted as a query parameter. |
Connections are rejected with close code 1008 when authentication fails, when the key's concurrent-session limit is reached, or when the wallet balance is below about 60 seconds of runway — the Error message before the close says which.
Query parameters
All optional. Telephony sources typically use encoding=mulaw&sample_rate=8000; microphone capture typically uses encoding=linear16&sample_rate=16000.
| Parameter | Type | Default | Description |
|---|---|---|---|
encoding | string | linear16 | Encoding of the binary frames you send: linear16 (16-bit PCM), mulaw, or alaw. |
sample_rate | integer | 8000 | Sample rate of your audio: 8000 or 16000. |
channels | integer | 1 | 1 (mono) or 2 (stereo, interleaved). With 2, each channel is transcribed independently — ideal for two-leg call recordings. |
model | string | munsit | ASR model for the session: munsit (Arabic) or munsit-en-ar (mixed Arabic-English code-switching). Routes the stream to the matching engine. Both models share the munsit-2 recognizer generation, so this selects language coverage, not model vintage. |
language | string | ar | Transcription language. ar is the only supported value in v1. |
interim_results | boolean | true | Emit interim (partial) results while a turn is in progress. |
endpointing | integer | 800 | Milliseconds of silence that end a turn. Range 100–5000. Retunable mid-session with Configure. |
smart_turn | boolean | true | Gate end-of-turn on a semantic turn-completion model in addition to silence. A turn always ends after 2× the endpointing silence regardless. |
hotwords | string | — | Comma-separated custom vocabulary (multi-word phrases allowed). Up to 200 entries, each up to 40 characters; URL-encode the value. |
correlation_id | string | — | Your identifier for this session (up to 128 characters), echoed in the opening Metadata event. |
metadata | string | — | Base64-encoded JSON object (up to 2 KB) attached to the session. |
Error with code 4002 and recoverable: false, then the connection closes with code 4002. An invalid mid-session Configure is recoverable instead — the session continues.Sending audio
Send raw audio as binary WebSocket frames in the encoding and sample rate you declared at connect time. No container headers — for linear16, frames are little-endian 16-bit PCM samples, interleaved when channels=2. Frame size is up to you; roughly 20–200 ms of audio per frame works well.
| Rule | Why |
|---|---|
| Keep the connection alive | 12 seconds with neither audio nor a KeepAlive message produces an Error and closes with code 1011. |
| Don't run ahead of real time | Audio may be buffered at most 60 seconds ahead of real time; exceeding it closes with code 4008. Live sources never hit this — pace your sends when streaming from a file. |
Control messages
Controls are JSON text frames, sent on the same socket as your binary audio.
CloseStream and short utterances. CloseStream finalizes the turn in progress. Every event for that turn — Results with is_final: true, then UtteranceEnd, Gender and Sentiment — is sent before the closing Metadata, which is always the last event before the 1000 close.
This holds for utterances shorter than your endpointing window: a 400 ms word closed out with CloseStream still produces a final Results. It does not hold for audio that never opened a turn — under roughly 200 ms of speech, or noise-only input, the voice-activity detector never confirms speech, so the session ends with Metadata (turn_count: 0) and no Results at all. Treat that as “no speech detected” rather than an error.
After sending CloseStream, keep reading until the server closes the socket. Closing it yourself first aborts finalization and loses both the final Results and the billing Metadata.
This is the /listen counterpart to the legacy end_of_stream → finalized flush; the stable-text flag is is_final rather than isFinal, and min_buffer_seconds has no equivalent here.
Server events
Every server message is a JSON text frame with a type field. All events carry session_id — and correlation_id when you set one — so multiplexed clients can attribute events without tracking connections.
A single spoken turn produces this sequence:
SpeechStarted → Results (interim, refreshing) → Results (final) → UtteranceEnd → Gender → Sentiment
Turn detection
Endpointing is the silence the engine waits for before declaring a turn finished, and it's the main latency/accuracy dial you control. You can retune it live, mid-session, with a Configure message — drop to 300 when your agent asks a yes/no question, restore 800 for open-ended answers.
| Setting | Final transcript arrives | Trade-off |
|---|---|---|
800 ms (default) | ~1.2 s after speech ends | Safest turn boundaries for conversational speech. |
500 ms | ~0.9 s (estimated) | Balanced; clears most mid-sentence hesitations. |
300 ms | ~0.8 s | Fastest — best for short commands. On hesitant, conversational speech it can split turns mid-thought, costing ~2 WER points in our benchmarks. |
Smart turn detection (smart_turn, on by default) runs a semantic end-of-turn model on every pause, so a caller who stops mid-sentence to think isn't cut off just because the silence threshold elapsed. A turn still always ends after 2× the endpointing silence. If the model is unavailable server-side, endpointing degrades gracefully to silence-only.
is_final vs speech_final. Treat speech_final as your end-of-turn signal for agent logic, and is_final as “this text will not change”.
speech_final: true— the speaker genuinely finished. This is your trigger to respond.is_final: truewithspeech_final: false— a forced split during long unbroken speech, about every 60 seconds. The text is stable but the speaker is still talking: don't respond, noUtteranceEndfires, and transcription continues under the nextturn_id.
Interim results (is_final: false) refresh continuously and may revise earlier words — render them as provisional text. UtteranceEnd fires only on genuine turn ends and is the cleanest single signal for “caller stopped talking”.
Custom vocabulary
Pass rare terms — customer names, brands, product codes — in the hotwords query parameter, comma-separated and URL-encoded. Multi-word phrases are allowed.
&hotwords=%D8%B9%D8%A8%D8%AF%20%D8%A7%D9%84%D9%82%D8%A7%D8%AF%D8%B1%2C%D8%A3%D8%AF%D9%8A%D8%A8
Short lists of 5–30 genuinely rare terms work best; very long lists dilute the effect. Up to 200 entries, each up to 40 characters. Entries that can't be applied are skipped and reported in dropped_hotwords on the opening Metadata event — check it to confirm every entry landed.
Sentiment & speaker gender
After each final result you receive two enrichment events, with no extra requests.
| Event | Labels | Notes |
|---|---|---|
Gender | male / female | Classified from the turn's audio — measured 99.2% accuracy on Arabic. |
Sentiment | positive / neutral / negative | Derived from the turn's transcript. |
On a two-channel call this gives you live per-party gender and a running sentiment trajectory — useful for routing, analytics and supervisor alerts. For post-call analysis of an existing transcription, use Sentiment analysis instead.
Session health & close codes
Four habits keep a session healthy: send KeepAlive during pauses, pace file streaming to real time, end with CloseStream so the billing event arrives, and handle the close code.
| Code | Meaning | Client handling |
|---|---|---|
1000 | Normal close after CloseStream | Done — billing was reported in the closing Metadata. |
1008 | Policy rejection: auth failed, concurrent-session limit reached, or insufficient wallet balance | Check the preceding Error. Auth: fix the key. Session limit: retry after a session ends. Balance: top up the wallet. |
1011 | Internal error, or 12 s with no audio and no KeepAlive | Reconnect and resume; send KeepAlive during pauses. |
4002 | Invalid connection parameters | Fix the parameters and reconnect. |
4008 | Audio sent more than 60 s ahead of real time | Pace file streaming to real time. |
Billing & limits
Usage is metered as seconds of audio received × number of channels, charged from your wallet in 60-second cycles during the session.
| Limit | Value |
|---|---|
| Concurrent sessions | 5 per API key by default — raised on request. Exceeding it closes the new connection with 1008. |
| Wallet runway to connect | About 60 seconds. Running out mid-session closes the connection with 1008 after an Error. |
| Idle timeout | 12 seconds with neither audio nor KeepAlive (code 1011). |
| Session length | Unlimited while the connection stays active. Unbroken speech is force-segmented about every 60 seconds so results keep flowing. |
The closing Metadata event reports the session total as audio_seconds_billed.
Examples
Streaming a file, and capturing a microphone in the browser.
Legacy endpoint
WS /websocket/speech-to-text is deprecated. The previous streaming endpoint — JSON audio_chunk frames and a cumulative transcript string — remains available for existing integrations but receives no new recognition features: no word timestamps, turn events, hotwords, confidence, sentiment or gender. New integrations should use WS /api/v1/listen. Its message reference is on the WebSocket protocol page.Lifecycle. This endpoint is frozen, not scheduled for removal. It stays available for existing integrations with no functional changes; any change to that would be announced on the Changelog.
If you are still on it: send
{"event":"end_of_stream"} before closing the socket and wait for the finalized event. Closing alone does not flush buffered audio, so short answers are otherwise lost. See Finalizing a stream.Go further
Batch alternatives, and the agent frameworks that wrap this socket for you.
Working with an AI assistant? Every page is available as Markdown: add .md to the URL, or send an Accept: text/markdown header. For the whole documentation in one request, point it at llms-full.txt; the page index is llms.txt. Or use Copy Page, top right.
