Speech-to-TextReal-time API

Real-time transcription

Learn about real-time transcription with low latency and high accuracy for all 60+ languages.

Overview

Soniox Speech-to-Text AI lets you transcribe audio in real time with low latency and high accuracy in over 60 languages. This is ideal for use cases like live captions, voice assistants, streaming analytics, and conversational AI.

Real-time transcription is provided through our WebSocket API, which streams results back to you as the audio is processed.

Need to translate live speech as it's spoken? Add a translation block to the same request — see Real-time translation.


How processing works

As audio is streamed into the API, Soniox returns a continuous stream of tokens — small units of text such as subwords, words, or spaces.

Each token carries a status flag (is_final) that tells you whether the token is provisional or confirmed:

  • Non-final token (is_final: false) → Provisional text. Appears instantly but may change, disappear, or be replaced as more audio arrives.
  • Final token (is_final: true) → Confirmed text. Once marked final, it will never change in future responses.

This means you get text right away (non-final for instant feedback), followed by the confirmed version (final for stable output).

Non-final tokens may appear multiple times and change slightly until they stabilize into a final token. Final tokens are sent only once and never repeated.

Example token evolution

Here’s how "How are you doing?" might arrive over time:

Initial guess (non-final):

{"tokens": [{"text": "How",    "is_final": false},
            {"text": "'re",    "is_final": false}]}

Refined guess (non-final):

{"tokens": [{"text": "How",    "is_final": false},
            {"text": " ",      "is_final": false},
            {"text": "are",    "is_final": false}]}

Mixed output (final + non-final):

{"tokens": [{"text": "How",    "is_final": true},
            {"text": " ",      "is_final": true},
            {"text": "are",    "is_final": false},
            {"text": " ",      "is_final": false},
            {"text": "you",    "is_final": false}]}

Mixed output (final + non-final):

{"tokens": [{"text": "are",    "is_final": true},
            {"text": " ",      "is_final": true},
            {"text": "you",    "is_final": true},
            {"text": " ",      "is_final": false},
            {"text": "do",     "is_final": false},
            {"text": "ing",    "is_final": false},
            {"text": "?",      "is_final": false}]}

Confirmed tokens (final):

{"tokens": [{"text": " ",      "is_final": true},
            {"text": "do",     "is_final": true},
            {"text": "ing",    "is_final": true},
            {"text": "?",      "is_final": true}]}

Bottom line: The model may start with a shorthand guess like “How’re”, then refine it into “How are you”, and finally extend it into “How are you doing?”. Non-final tokens update instantly, while final tokens never change once confirmed.


Audio progress tracking

Each response also tells you how much audio has been processed:

  • final_audio_proc_ms — audio processed into final tokens.
  • total_audio_proc_ms — audio processed into final + non-final tokens.

Example:

{
  "final_audio_proc_ms": 4800,
  "total_audio_proc_ms": 5250
}

This means:

  • Audio up to 4.8s has been processed and finalized (final tokens).
  • Audio up to 5.25s has been processed in total (final + non-final tokens).

Getting final tokens sooner

There are two ways to obtain final tokens more quickly:

  1. Endpoint detection — the model can detect when a speaker has stopped talking and finalize tokens immediately.
  2. Manual finalization — you can send a "type": "finalize" message over the WebSocket to force all pending tokens to finalize.

Audio formats

Soniox supports both auto-detected formats (no configuration required) and raw audio formats (manual configuration required).

Auto-detected formats

Soniox can automatically detect common container formats from stream headers. No configuration needed — just set:

{
  "audio_format": "auto"
}

Supported auto formats:

aac, aiff, amr, asf, flac, mp3, ogg, wav, webm

Raw audio formats

For raw audio streams without headers, you must provide:

  • audio_format → encoding type.
  • sample_rate → sample rate in Hz.
  • num_channels → number of channels (e.g. 1 (mono) or 2 (stereo)).

Supported encodings:

  • PCM (signed): pcm_s8, pcm_s16, pcm_s24, pcm_s32 (le/be).
  • PCM (unsigned): pcm_u8, pcm_u16, pcm_u24, pcm_u32 (le/be).
  • Float PCM: pcm_f32, pcm_f64 (le/be).
  • Companded: mulaw, alaw.

Example: raw PCM (16-bit, 16kHz, mono)

{
  "audio_format": "pcm_s16le",
  "sample_rate": 16000,
  "num_channels": 1
}

Code example

Prerequisite: Complete the steps in Get started.

See on GitHub: soniox_sdk_realtime.py.

import os
import argparse
from typing import Optional

from soniox import SonioxClient
from soniox.types import (
    RealtimeSTTConfig,
    StructuredContext,
    TranslationConfig,
    StructuredContextGeneralItem,
    StructuredContextTranslationTerm,
)
from soniox.utils import render_tokens, start_audio_thread, throttle_audio


def get_config(audio_format: str, translation: Optional[str]) -> RealtimeSTTConfig:
    config = RealtimeSTTConfig(
        # Select the model to use.
        # See: docs.soniox.com/stt/models
        model="stt-rt-v5",
        #
        # Set language hints when possible to significantly improve accuracy.
        # See: docs.soniox.com/stt/concepts/language-hints
        language_hints=["en", "es"],
        #
        # Enable language identification. Each token will include a "language" field.
        # See: docs.soniox.com/stt/concepts/language-identification
        enable_language_identification=True,
        #
        # Enable speaker diarization. Each token will include a "speaker" field.
        # See: docs.soniox.com/stt/concepts/speaker-diarization
        enable_speaker_diarization=True,
        #
        # Set context to help the model understand your domain, recognize important terms,
        # and apply custom vocabulary and translation preferences.
        # See: docs.soniox.com/stt/concepts/context
        context=StructuredContext(
            general=[
                StructuredContextGeneralItem(key="domain", value="Healthcare"),
                StructuredContextGeneralItem(
                    key="topic", value="Diabetes management consultation"
                ),
                StructuredContextGeneralItem(key="doctor", value="Dr. Martha Smith"),
                StructuredContextGeneralItem(key="patient", value="Mr. David Miller"),
                StructuredContextGeneralItem(
                    key="organization", value="St John's Hospital"
                ),
            ],
            text="Mr. David Miller visited his healthcare provider last month for a routine follow-up related to diabetes care. The clinician reviewed his recent test results, noted improved glucose levels, and adjusted his medication schedule accordingly. They also discussed meal planning strategies and scheduled the next check-up for early spring.",
            terms=[
                "Celebrex",
                "Zyrtec",
                "Xanax",
                "Prilosec",
                "Amoxicillin Clavulanate Potassium",
            ],
            translation_terms=[
                StructuredContextTranslationTerm(
                    source="Mr. Smith", target="Sr. Smith"
                ),
                StructuredContextTranslationTerm(
                    source="St John's", target="St John's"
                ),
                StructuredContextTranslationTerm(source="stroke", target="ictus"),
            ],
        ),
        #
        # Use endpointing to detect when the speaker stops.
        # It finalizes all non-final tokens right away, minimizing latency.
        # See: docs.soniox.com/stt/rt/endpoint-detection
        enable_endpoint_detection=True,
    )

    # Audio format.
    # See: docs.soniox.com/stt/rt/real-time-transcription#audio-formats
    if audio_format == "auto":
        # Set to "auto" to let Soniox detect the audio format automatically.
        config.audio_format = "auto"
    elif audio_format == "pcm_s16le":
        # Example of a raw audio format; Soniox supports many others as well.
        config.audio_format = "pcm_s16le"
        config.sample_rate = 16000
        config.num_channels = 1
    else:
        raise ValueError(f"Unsupported audio_format: {audio_format}")

    # Translation options.
    # See: docs.soniox.com/translation/stt-translation#translation-modes
    if translation == "none":
        pass
    elif translation == "one_way":
        # Translates all languages into the target language.
        config.translation = TranslationConfig(
            type="one_way",
            target_language="es",
        )
    elif translation == "two_way":
        # Translates from language_a to language_b and back from language_b to language_a.
        config.translation = TranslationConfig(
            type="two_way",
            language_a="en",
            language_b="es",
        )
    else:
        raise ValueError(f"Unsupported translation: {translation}")

    return config


def run_session(
    client: SonioxClient,
    audio_path: str,
    audio_format: str,
    translation: str,
) -> None:
    config = get_config(audio_format, translation)

    print("Connecting to Soniox...")
    with client.realtime.stt.connect(config=config) as session:
        final_tokens = []

        start_audio_thread(session, throttle_audio(audio_path, delay_seconds=0.1))
        print("Session started.")

        for event in session.receive_events():
            # Error from server.
            # See: https://docs.soniox.com/stt/api-reference/websocket-api#error-response
            if event.error_code:
                print(f"Error: {event.error_code} - {event.error_message}")

            # Parse tokens from current response.
            non_final_tokens = []
            for token in event.tokens:
                if token.is_final:
                    # Final tokens are returned once and should be appended to final_tokens.
                    final_tokens.append(token)
                else:
                    # Non-final tokens update as more audio arrives; reset them on every response.
                    non_final_tokens.append(token)

            # Render tokens.
            print(render_tokens(final_tokens, non_final_tokens))

            # Session finished.
            if event.finished:
                print("Session finished.")


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--audio_path", type=str)
    parser.add_argument("--audio_format", default="auto")
    parser.add_argument("--translation", default="none")
    args = parser.parse_args()

    api_key = os.environ.get("SONIOX_API_KEY")
    if api_key is None:
        raise RuntimeError("Missing SONIOX_API_KEY.")

    client = SonioxClient()

    run_session(client, args.audio_path, args.audio_format, args.translation)


if __name__ == "__main__":
    main()

Terminal
# Transcribe a live audio stream
python soniox_sdk_realtime.py --audio_path ../assets/coffee_shop.mp3

# Transcribe a raw audio live stream
python soniox_sdk_realtime.py --audio_path ../assets/coffee_shop.pcm_s16le --audio_format pcm_s16le

On this page