# Realtime Real-time speech-to-text transcription over WebSocket. See the [Realtime guide](/speech-to-text/realtime/) for concepts. ``` wss://api.reson8.dev/v1/speech-to-text/realtime ``` ## Request ### Headers | Header | Value | |------------------------|-----------------------------------------------| | Authorization | `ApiKey ` or `Bearer ` | | Sec-WebSocket-Protocol | `bearer, ` | See [Authentication](/authentication/) for which header to use in different situations. ### Query Parameters | Parameter | Type | Default | Description | |----------------------|---------|-----------|-----------------------------------------------------------| | `encoding` | string | `auto` | Audio encoding: `auto` for detected container formats, `pcm_s16le` for raw PCM, or `mulaw` / `alaw` for raw G.711 telephony audio. See [Audio Formats](/speech-to-text/features/audio-formats/) | | `sample_rate` | number | `16000` | Sample rate in Hz (only used depending on encoding) | | `channels` | number | `1` | Number of audio channels, 1-10 (only used depending on encoding) | | `language` | string | | Language to transcribe. Recommended for best quality. A comma-separated list (e.g. `nl,en`) constrains per-utterance auto-detection to those candidates. When omitted, the server auto-detects each utterance independently. See [Languages](/speech-to-text/features/languages/) for supported codes | | `phrases` | string | | Comma-separated phrases to bias transcription toward, up to 250. See [Custom Models](/speech-to-text/features/custom-models/) | | `custom_model_id` | string | | ID of a [custom model](/api/custom-model/create/) to bias transcription. Overrides the model configured on the API client | | `bias_strength` | number | `0.45` | Strength of contextual biasing. Must be a non-negative number | | `include_timestamps` | boolean | `false` | Include `start_ms` and `duration_ms` on transcripts and words | | `include_words` | boolean | `false` | Include word-level detail on transcripts | | `include_language` | boolean | `false` | Include the detected `language` on transcripts (empty on interim) | | `include_confidence` | boolean | `false` | Include `confidence` on words | | `include_interim` | boolean | `false` | Include interim (partial) results and the `is_final` flag | | `diarize` | boolean | `false` | Enable [speaker diarization](/speech-to-text/features/diarization/). Adds `speaker_id` to each transcript | | `max_speakers` | number | | Maximum number of distinct speakers (1-4). When omitted, the count is determined automatically. Only used when `diarize=true` | | `patterns` | string | | Comma-separated regex-style patterns for short alphanumeric tokens (order codes, licence plates) to recover. Only set when the token is likely present; cannot be combined with `phrases` or a custom model - see [Patterns](/speech-to-text/features/patterns/) | | `filler_mode` | string | `natural` | Controls filler words in final transcripts: `clean` removes them, `natural` lets the model decide, and `verbatim` preserves them. Interim transcripts are unaffected | ### Example ```python import asyncio import json import websockets async def transcribe(): url = "wss://api.reson8.dev/v1/speech-to-text/realtime" headers = {"Authorization": "ApiKey "} async with websockets.connect(url, additional_headers=headers) as ws: async def send_audio(): try: with open("recording.wav", "rb") as f: while chunk := f.read(8192): await ws.send(chunk) await ws.send(json.dumps({"type": "flush_request"})) except Exception: await ws.close() raise sender = asyncio.create_task(send_audio()) async for message in ws: event = json.loads(message) print(event) if event["type"] == "flush_confirmation": break await sender asyncio.run(transcribe()) ``` ```javascript const token = ""; const url = "wss://api.reson8.dev/v1/speech-to-text/realtime"; // Passes token via Sec-WebSocket-Protocol header const ws = new WebSocket(url, ["bearer", token]); ws.onopen = async () => { const audio = await (await fetch("/recording.wav")).arrayBuffer(); for (let i = 0; i < audio.byteLength; i += 8192) { ws.send(audio.slice(i, i + 8192)); } ws.send(JSON.stringify({ type: "flush_request" })); }; ws.onmessage = (event) => { const message = JSON.parse(event.data); console.log(message); if (message.type === "flush_confirmation") ws.close(); }; ``` ## Sending Messages ### Audio Binary WebSocket frame containing audio data. ### Flush Request Force the server to finalize any buffered audio and return a transcript. ```json { "type": "flush_request", "id": "abc123" } ``` | Field | Type | Required | Description | |-------|--------|----------|-------------| | `id` | string | No | Optional identifier, returned in the corresponding flush confirmation | ## Receiving Messages ### Transcript Returned when speech is recognized. By default, only final results are returned. When `include_interim=true`, interim (partial) results are also returned - these may change as more audio is processed. ```json { "type": "transcript", "text": "the patient presented with chest pain" } ``` With `diarize=true`, each transcript carries the `speaker_id` of its dominant speaker. ```json { "type": "transcript", "text": "where does it hurt", "speaker_id": 0 } { "type": "transcript", "text": "my chest, mostly", "speaker_id": 1 } ``` ```json { "type": "transcript", "text": "the patient presented with chest pain", "language": "en", "is_final": true, "start_ms": 1200, "duration_ms": 2400, "speaker_id": 0, "words": [ { "text": "the", "start_ms": 1200, "duration_ms": 200, "confidence": 0.990 }, { "text": "patient", "start_ms": 1410, "duration_ms": 450, "confidence": 0.980 }, { "text": "presented", "start_ms": 1880, "duration_ms": 500, "confidence": 0.970 }, { "text": "with", "start_ms": 2400, "duration_ms": 200, "confidence": 0.990 }, { "text": "chest", "start_ms": 2620, "duration_ms": 350, "confidence": 0.960 }, { "text": "pain", "start_ms": 3000, "duration_ms": 600, "confidence": 0.970 } ] } ``` | Field | Type | Included | Description | |---------------|---------|--------------------------------|-----------------------------------------------| | `text` | string | Always | The recognized text | | `language` | string | When `include_language=true` | The detected language code (empty on interim) | | `is_final` | boolean | When `include_interim=true` | `true` for final, `false` for interim | | `start_ms` | number | When `include_timestamps=true` | Start time in milliseconds | | `duration_ms` | number | When `include_timestamps=true` | Duration in milliseconds | | `speaker_id` | number | When `diarize=true` | Speaker label for this transcript (integer, 0-indexed). See [Diarization](/speech-to-text/features/diarization/) | | `words` | array | When `include_words=true` | Word-level detail | Each word contains: | Field | Type | Included | Description | |---------------|--------|--------------------------------|----------------------------| | `text` | string | Always | The recognized word | | `start_ms` | number | When `include_timestamps=true` | Start time in milliseconds | | `duration_ms` | number | When `include_timestamps=true` | Duration in milliseconds | | `confidence` | number | When `include_confidence=true` | Probability in `(0, 1]` | ### Flush Confirmation Sent after a flush request has been processed. The `id` field is always present; it is `null` if no identifier was provided in the request. ```json { "type": "flush_confirmation", "id": "abc123" } ``` | Field | Type | Included | Description | |-------|--------|----------|-------------| | `id` | string | Always | The identifier from the corresponding flush request, or `null` if none was provided | ## Errors Rejected WebSocket upgrades return no body; where available, the reason is in the `X-Error-Message` response header. | Status | Description | |-----------------------|--------------------------------------------------------------| | 400 Bad Request | Invalid query parameter, unknown `custom_model_id`, or `patterns` combined with a custom model | | 401 Unauthorized | Missing or invalid credentials | | 402 Payment Required | Credit limit exceeded - see [Limits](/limits/) | | 429 Too Many Requests | Concurrent connection limit exceeded - see [Limits](/limits/) | ### During the Session If an unrecoverable error occurs after the connection is established, the server closes the WebSocket without sending an error message. Treat an unexpected close as an error and reconnect with a fresh audio stream. Malformed or unrecognized client messages are ignored; the session continues.