Skip to main content

Soniox Python SDK

The SDK exposes two clients: SonioxClient (sync) and AsyncSonioxClient (async). Each client supports:

  • STT over REST (client.stt) and realtime WebSocket (client.realtime.stt)
  • TTS over REST (client.tts) and realtime WebSocket (client.realtime.tts)
  • voice cloning (client.voices) - clone a voice from a reference clip and use it in TTS
  • usage and cost (client.usage, client.usage_logs) - daily spend per model, and per-request logs
  • organization administration (client.organization) - balance, limits, usage across projects, and project API keys
  • auth, file uploads, model listing, webhooks, and typed request/response models

Install

pip install soniox
# or if using uv
uv add soniox
export SONIOX_API_KEY=<your-key>

Get your API key from the Soniox Console and inject it once per shell session. Both clients read SONIOX_API_KEY by default, but you can override it per-client if needed.

Every endpoint can come from the environment too, so a client can be pointed at another deployment without touching code. A constructor argument always wins over the variable.

Variable Overrides Default
SONIOX_API_KEY api_key -
SONIOX_API_BASE_URL api_base_url https://api.soniox.com/v1
SONIOX_WS_URL websocket_base_url wss://stt-rt.soniox.com/transcribe-websocket
SONIOX_TTS_API_URL tts_api_base_url https://tts-rt.soniox.com
SONIOX_TTS_WS_URL tts_websocket_base_url wss://tts-rt.soniox.com/tts-websocket

Avoid Python 3.13.6 - it has a regression in ssl that hangs realtime STT/TTS (CPython issue #137583). Use any other 3.10-3.13.x.

Quick run (STT + TTS, REST + realtime)

  1. REST STT transcription: transcribe a local file end-to-end in one call. Full example: examples/soniox_client/api_example.py.
from soniox import SonioxClient

client = SonioxClient()
transcript = client.stt.transcribe_and_wait_with_tokens(
    file="path/to/audio.mp3",                # local file
    # audio_url="https://example.com/audio.mp3",  # or remote URL
    delete_after=True,                        # auto-cleanup file + transcription
)
print(transcript.text)
client.close()
  1. REST TTS generation: convert text to an audio file.
from soniox import SonioxClient
from soniox.utils import output_file_for_audio_format

client = SonioxClient()
output_file = output_file_for_audio_format("wav", "tts_sync_output")
written = client.tts.generate_to_file(
    output_file,
    text="Hello from Soniox Python SDK Text-to-Speech.",
    model="tts-rt-v2",
    language="en",
    voice="Adrian",
    audio_format="wav",
)
print(f"Wrote {written} bytes to {output_file.resolve()}")
client.close()

Run the full example at examples/soniox_client/tts_api_example.py or async version at examples/async_soniox_client/tts_api_example.py.

The voice above is a built-in (shared) voice name. To browse a model's shared voices, filtered by gender, age, accent, use case or style:

for v in client.voices.list_all_shared("tts-rt-v2", gender="female", style=["calm"]):
    print(v.id, v.accent, v.description)

Use client.voices.list_shared(...) for a single page with an explicit cursor.

You can also clone a voice from a reference audio clip and pass the returned voice id as voice:

voice = client.voices.create("reference.wav", name="my-cloned-voice")
# Cloning runs asynchronously; poll client.voices.get(voice.id) until a model
# reports status "ready", then synthesize with voice=voice.id.

See the voice cloning guide for details.

Output settings that are not identity - speed, reduce_silence, sample rate, bitrate - go on CreateTtsConfig:

from soniox.types import CreateTtsConfig

client.tts.generate(
    text="Hello.",
    voice="Adrian",
    model="tts-rt-v2",
    language="en",
    config=CreateTtsConfig(speed=1.1, reduce_silence=True),
)

reduce_silence shortens the pauses between words and is only accepted by models whose supports_silence_reduction is true in client.tts_models.list().

  1. Realtime STT streaming: open client.realtime.stt.connect, call session.send_byte_chunk or session.send_bytes, then iterate session.receive_events() to render tokens:
from soniox import SonioxClient
from soniox.types import RealtimeSTTConfig, Token
from soniox.utils import render_tokens, throttle_audio, start_audio_thread

DEMO_FILE = "path_to_your_audio_file"

client = SonioxClient()
config = RealtimeSTTConfig(model="stt-rt-v6", audio_format="mp3")
final_tokens: list[Token] = []
non_final_tokens: list[Token] = []

def realtime():
    with client.realtime.stt.connect(config=config) as session:
        start_audio_thread(session, throttle_audio(DEMO_FILE, delay_seconds=0.1))
        for event in session.receive_events():
            for token in event.tokens:
                if token.is_final:
                    final_tokens.append(token)
                else:
                    non_final_tokens.append(token)
            print(render_tokens(final_tokens, non_final_tokens))
            non_final_tokens.clear()

realtime()
client.close()

See examples/soniox_client/realtime_example.py for the full flow.

In a voice agent, tell Soniox what your agent is saying so those same words are recognized when the user says them back, and so the agent's own voice bleeding into the microphone is not transcribed as the user. Set enable_agent_text_input on the config - the model must report supports_agent_text_input in client.models.list() - and send the text with session.send_agent_text():

config = RealtimeSTTConfig(
    model="stt-rt-v6",
    audio_format="pcm_s16le",
    sample_rate=24000,
    num_channels=1,
    enable_agent_text_input=True,
)

LEAD_SEC = 0.7  # send each piece 700ms before the caller hears it


def send_due(session, characters, start_times, sent, position_sec):
    """Send the characters the caller is about to hear, and nothing beyond them."""
    horizon = position_sec + LEAD_SEC
    due = sent
    while due < len(characters) and start_times[due] <= horizon:
        due += 1
    if due > sent:
        session.send_agent_text("".join(characters[sent:due]))  # mid-turn by default
    return due


# After each chunk of caller audio. Keep the returned cursor, or every piece is
# sent again on the next call.
sent = send_due(session, characters, start_times, sent, position_sec)

Agent text carries no timestamps: Soniox places each piece where it sits among the audio frames. So follow the playback - not the LLM, which writes the reply far faster than it is spoken, and not the TTS, which generates audio faster than real time. Call send_due() right after each chunk of caller audio, with position_sec as the smaller of the wall clock since playback began and the audio the speaker was actually given, so the text cannot run ahead of a slow TTS.

Realtime TTS supplies the alignment with return_timestamps: grow characters and start_times from each event's timestamps - the character times run from the start of the stream, so they already say when the caller hears each one.

Close every turn with end_of_segment=True, on the last piece of text or on its own. A barge-in is the same call: stop the playback, send what was already heard but not sent, close the turn, and drop the rest of the reply - the caller never heard it.

Two threads share characters, start_times and sent - one fills them from the Text-to-Speech, the other sends - so they need a lock, and the turn has to close exactly once. The mic example below has that part written out.

Send only what the agent says out loud, and clean the reply before it reaches the Text-to-Speech - then the timestamps describe text that is already clean. Soniox TTS drops emoji and collapses whitespace, but it passes markdown, SSML and expression tags like [laughs] through untouched, so anything your model writes that is not meant to be spoken has to be stripped by you on the way in.

Echo cancellation is the other half and it is not the SDK's: it belongs to the platform. Use a headset with hardware AEC, or on Linux pactl load-module module-echo-cancel and record from the source it creates. Agent text covers what cancellation cannot - it is never perfect, and the user will often repeat the agent's words back.

Run the full example at examples/soniox_client/agent_text_example.py or async version at examples/async_soniox_client/agent_text_example.py.

  1. Realtime TTS streaming: send text chunks and write audio to a file as it arrives.
from uuid import uuid4

from soniox import SonioxClient
from soniox.types import RealtimeTTSConfig
from soniox.utils import output_file_for_audio_format

client = SonioxClient()
config = RealtimeTTSConfig(
    stream_id=f"sync-{uuid4()}",
    model="tts-rt-v2",
    language="en",
    voice="Adrian",
    audio_format="wav",
)

output_file = output_file_for_audio_format("wav", "tts_realtime_output")
bytes_written = 0
with client.realtime.tts.connect(config=config) as session, output_file.open("wb") as f:
    session.send_text_chunks(
        ["Hello from realtime TTS. ", "This is the final chunk."],
        text_end=True,
    )
    for chunk in session.receive_audio_chunks():
        f.write(chunk)
        bytes_written += len(chunk)

print(f"Wrote {bytes_written} bytes to {output_file.resolve()}")

Run the full example at examples/soniox_client/tts_realtime_example.py or async version at examples/async_soniox_client/tts_realtime_example.py.

Usage and cost

client.usage.summary() returns daily cost and activity for the project, rolled up per model and across all models. start_time is inclusive, end_time exclusive, both ISO 8601 UTC, and the window may span at most 366 days.

summary = client.usage.summary(
    start_time="2026-04-01T00:00:00Z",
    end_time="2026-05-01T00:00:00Z",
)
print(summary.total.total_cost_usd)
for entry in summary.models:
    print(entry.model, entry.total_cost_usd)

Each entry carries a days list plus per-day lists (cost_usd, num_requests, ...) aligned to it by index. For per-request records instead, use client.usage_logs.list() / list_all().

client.concurrency_limits covers concurrency: get() for current counts and configured limits, history() for past peaks aggregated per minute (60), hour (3600), or day (86400).

history = client.concurrency_limits.history(
    "2026-04-28T09:00:00Z",
    "2026-04-28T10:00:00Z",
    period_sec=60,
    kind="tts",
)
peak = max(entry.sample_max for entry in history.entries)

Organization administration

client.organization covers the whole organization rather than one project, so every call needs an organization API key. It goes in the same Authorization header, so pass it as api_key; a project key gets a SonioxPermissionDeniedError.

client = SonioxClient(api_key=organization_api_key)

balance = client.organization.balance()
print(balance.available_balance_usd, balance.balance_exhausted)

monthly = client.organization.monthly_usage("2026-04")      # defaults to this month
for project in monthly.projects:
    print(project.name, project.usage_usd)

usage_summary(start_time, end_time) returns the same per-day entries as client.usage.summary(), split per project instead of per model. get(), limits() and projects() cover identity, per-region quotas and the project list. Amounts are decimal strings everywhere, never floats.

API keys of a project are managed with list_project_api_keys(), create_project_api_key() and delete_project_api_key(). The create call is the only place the secret appears - it is in key on the response and is not retrievable afterwards.

created = client.organization.create_project_api_key(
    project_id,
    name="ci",
    permissions=["stt_async:write", "files:write"],
)
print(created.key)  # store it now; you cannot read it back

Run the full example at examples/soniox_client/organization_example.py or async version at examples/async_soniox_client/organization_example.py.

Repository layout

  • src/soniox/ – sdk code (clients, http namespaces, real-time/session helpers, types, utils).
  • examples/soniox_client & examples/async_soniox_client – runnable STT and TTS examples for sync and async clients.
  • docs/ – markdown reference (async_client.md, realtime_client.md, types.md, utils.md, errors.md) generated by scripts/generate_docs.py.
  • assets/ – sample audio referenced by the examples.

Development

uv install --with dev

This pulls in ruff, pyright, pytest, etc., so you can lint, type-check, test, and regenerate docs locally.

Docs

source .venv/bin/activate
python3 scripts/generate_docs.py

Docs are output to /docs directory.

Resources

Metadata

Release files for soniox 2.11.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for soniox 2.11.0
File Size Uploaded Provenance Actions
soniox-2.11.0.tar.gz 1.2 MB Without provenance Details for soniox-2.11.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for soniox 2.11.0
File Interpreter ABI Platform Actions
soniox-2.11.0-py3-none-any.whl Python 3 none any Details for soniox-2.11.0-py3-none-any.whl

Total release size: 1.3 MB

Release files / soniox-2.11.0.tar.gz

Download URL soniox-2.11.0.tar.gz
Size 1.2 MB
Tags Source
SHA-256 checksum
How to use checksums
37ef5be9c675ed5d995887ec707243c7875d32a55ec4d417d76f48e5023bf492
BLAKE2b-256 checksum
How to use checksums
ed79a976d3ece3795ee568b469648d85e1e885914f84850f4887e6454281d55d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.9

Release files / soniox-2.11.0-py3-none-any.whl

Download URL soniox-2.11.0-py3-none-any.whl
Size 83.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
467402187661609faa0ae66ea01562c6e3fd5510640106d07b3c482cc4fe1826
BLAKE2b-256 checksum
How to use checksums
bafb2bab0894ae1d33969cb16152ddeeed008bd0fe2abcd6f6876ba91342148e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.9

Security for soniox 2.11.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

2.11.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page