# Soniox MCP Server — 25 tools via DADL

The Soniox DADL turns Soniox's API into an MCP server that Claude, GPT or any MCP-compatible agent can consume directly. One YAML file declares all 25 tools — transcription, voice, file, speech, audio, usage, and more — and ToolMesh serves them at runtime. No Python boilerplate, no per-endpoint code, no separate MCP server process.

Below: the endpoint coverage matrix, a two-block ToolMesh setup, the full tool reference grouped by Soniox feature area.

**Source:** [Soniox Speech AI API](https://soniox.com/docs/api-reference)

**Updated:** 2026-08-23

**Tags:** ai, cloud, automation, crud, file-management, user-management, real-time, translation, auth:bearer

## Which Soniox endpoints are covered?

**93%** (25 of ~27 endpoints).

**Focus:** Soniox Speech AI REST API v1: transcriptions (async speech-to-text, speaker diarization, language identification, speech translation), voices (voice cloning, per model readiness, recompute), files (audio upload, storage quotas, counts), speech synthesis (audio formats, sample rates, cloned voices), usage (cost logs, daily summary, concurrency limits), models (transcription models, synthesis models), temporary API keys.

**Missing:** The two WebSocket APIs — real-time speech-to-text (wss://stt-rt.soniox.com/transcribe-websocket) and real-time text-to-speech (wss://tts-rt.soniox.com/tts-websocket). Both are streaming protocols that DADL cannot describe; create_temporary_api_key here mints the short-lived keys those clients authenticate with.


*Last reviewed: 2026-08-23*

## How do you configure the Soniox DADL?

1. Create a Soniox account at https://console.soniox.com (free credit is granted on signup; no credit card needed to start).
2. Pick or create a PROJECT. Data residency and API keys are per project — if you need EU or JP processing, set the region when creating the project (contact support@soniox.com to have regions enabled).
3. Open the API keys section of the Console and create a new API key. Copy it immediately — the full key is shown only once.
4. Store it in the ToolMesh credential store under the name 'soniox_api_key' (env var CREDENTIAL_SONIOX_API_KEY for the embedded store).
5. Verify: curl -H "Authorization: Bearer $SONIOX_API_KEY" https://api.soniox.com/v1/models — a 200 with a models array means the key works, 401 unauthenticated means it does not.
6. For a non-US project, set url in backends.yaml to https://api.eu.soniox.com or https://api.jp.soniox.com AND edit the absolute path of the generate_speech tool in this file to the matching tts-rt.<region>.soniox.com host. A key from one region does not work against another.

**Environment variable:** `CREDENTIAL_SONIOX_API_KEY`

[Authentication docs](https://soniox.com/docs/api-reference)

*Soniox API keys have no scopes — one key grants full access to its project, so treat it as a secret and never ship it to a client. For browser or mobile clients use create_temporary_api_key instead (usage_type transcribe_websocket or tts_rt, max 3600s lifetime). Billing is usage-based per audio minute and token; the free signup credit is enough to exercise every tool in this file. Quotas that will bite in production: 1000 stored files, 10 GB storage, 100 pending and 2000 total transcriptions — delete what you have fetched, nothing expires by itself.
*

## How do you install the Soniox MCP server with ToolMesh?

Add to your `backends.yaml`:

```yaml
- name: soniox
  transport: rest
  dadl: soniox.dadl
  url: "https://api.soniox.com"

```

Set the credential:

```
CREDENTIAL_SONIOX_API_KEY=your-token-here
```

## What 25 tools does the Soniox DADL expose?

- **GET** `list_files` — List uploaded audio files in the project, newest first. Returns {files: [{id, filename, size, created_at, client_reference_id}], next_page_cursor}. size is in bytes. next_page_cursor is null on the last page — pass it back as cursor to continue. The project holds at most 1000 files / 10 GB by default; use get_files_count to check headroom before uploading.

- **POST** `upload_file` — Upload an audio file for later transcription. The caller passes a URL; ToolMesh fetches the bytes and posts them as the multipart 'file' field. Returns the created file {id, filename, size, created_at, client_reference_id} — pass that id as file_id to create_transcription. Format is auto-detected (aac, aiff, amr, asf, flac, mp3, ogg, wav, webm, m4a, mp4 and more); no encoding parameters exist. Audio longer than 300 minutes is rejected, and that cap cannot be raised. Fails with 429 limit_exceeded when the 1000-file or 10 GB project cap would be exceeded — delete unused files first. Files are NEVER auto-deleted. Prefer create_transcription with a public audio_url when the audio is already reachable on the web: that skips storage entirely.

- **GET** `get_files_count` — Count stored files, split by origin: {playground, public_api, total}. total counts against the 1000-file project cap regardless of origin (files created in the web Playground count too). Cheap — use it before a bulk upload instead of paging through list_files.

- **GET** `get_file` — Get metadata for one uploaded file: {id, filename, size, created_at, client_reference_id}. There is no download endpoint — Soniox does not serve the audio back. Returns 404 file_not_found if the id is unknown, already deleted, or belongs to a different project.

- **DELETE** `delete_file` — Permanently delete an uploaded file. Irreversible — Soniox keeps no copy and there is no download endpoint to recover the audio from. Returns 204 with no body on success, 404 file_not_found otherwise. Deleting the file does NOT delete transcriptions made from it, and an already-completed transcript stays readable. Do this as soon as the transcript is fetched: storage caps are per project and nothing expires on its own.

- **GET** `list_transcriptions` — List transcription jobs, newest first. Returns {transcriptions: [{id, status, created_at, model, filename, file_id, audio_url, audio_duration_ms, language_hints, enable_speaker_diarization, enable_language_identification, error_type, error_message, webhook_status_code, client_reference_id}], next_page_cursor}. status is queued | processing | completed | error. The webhook URL and secret fields are dropped from this projection; call get_transcription for the full object. Every entry — including failed ones — counts against the 2000 transcription cap.

- **POST** `create_transcription` — Submit recorded audio for asynchronous transcription. Returns the job immediately with status "queued" — NO text. Poll get_transcription until status is "completed" (then call get_transcription_transcript) or "error", or set webhook_url to be notified instead. Audio source: exactly one of audio_url (a public http/https URL Soniox fetches itself) or file_id (from upload_file). Passing both is a 400. model is required — use "stt-async-v5" for recorded audio (v4 is an alias for it); call list_models for the authoritative list. language_hints is an array of 2-letter codes (["en","de"]); omit it to auto-detect. language_hints_strict makes the model trust those hints harder. enable_speaker_diarization adds a speaker label to every token; enable_language_identification adds a language label. translation is an object, either {"type":"one_way","target_language":"de"} or {"type":"two_way","language_a":"en","language_b":"es"} — with it the transcript carries both original and translated tokens, tagged by translation_status. context accepts a plain string, or the structured form {"general":[{"key":"Domain","value":"medicine"}],"text":"...", "terms":["Soniox"],"translation_terms":[{"source":"cloud","target":"Cloud"}]} to bias the model toward names and jargon. webhook_url plus optional webhook_auth_header_name / webhook_auth_header_value replace polling. Fails with 429 limit_exceeded at 100 pending or 2000 total transcriptions, and with 400 model_not_available for an unknown model.

- **GET** `get_transcriptions_count` — Count transcriptions by origin: {playground, public_api, total}. total is the number that counts against the 2000-transcription project cap (queued + processing + completed + error, from any origin). Check this before bulk-submitting; at the cap create_transcription returns 429 limit_exceeded until old jobs are deleted.

- **GET** `get_transcription` — Get one transcription job with its current status. Returns {id, status, created_at, model, audio_url, file_id, filename, language_hints, enable_speaker_diarization, enable_language_identification, audio_duration_ms, error_type, error_message, webhook_url, webhook_auth_header_name, webhook_auth_header_value (masked), webhook_status_code, client_reference_id}. status is queued | processing | completed | error. This is the polling endpoint: repeat until status leaves queued/processing. audio_duration_ms is only set once processing has begun. On "error" read error_type (machine-readable, e.g. invalid_audio_file, file_download_failed, transcription_output_too_long) rather than error_message. Failed jobs are never retried automatically.

- **GET** `get_transcription_transcript` — Get the full transcript of a COMPLETED transcription. Returns {id, text, tokens} where text is the whole transcript as one string and tokens is the per-token detail: {text, start_ms, end_ms, confidence, speaker, language, is_audio_event, translation_status}. The last four keys are ALWAYS PRESENT but are null unless the corresponding feature was enabled on create_transcription — so test for null, not for absence. translation_status is null with translation off and "original" or "translation" with it on. is_audio_event is null in practice: audio-event detection is not settable through this API (see the domain notes). Timestamps are MILLISECONDS from the start of the audio. Token text keeps its own leading whitespace, so joining tokens in order rebuilds text exactly. WARNING: this response is unbounded — an hour of diarized audio is many thousands of tokens. Use the get_transcript_text composite when you only need the words, or get_transcript_segments for speaker-attributed passages; call this raw tool only when you truly need per-token timings or confidences. Only valid while status is "completed" — otherwise 409 transcription_invalid_state, or 404 transcription_not_found.

- **DELETE** `delete_transcription` — Permanently delete a transcription and its transcript. Irreversible — fetch the transcript first, Soniox keeps no copy. Returns 204 with no body. A job that is currently processing cannot be deleted (409 transcription_invalid_state); wait for completed or error. Deleting is the only way back under the 2000-transcription cap, and it does NOT delete the source file — call delete_file for that.

- **GET** `list_voices` — List the project's cloned voices. Returns {voices: [{id, name, filename, created_at, models: [{model, status, error_type, error_message}]}], next_page_cursor}. The models array is the important part: a voice is only usable with a model whose status is "ready" (other values: not_computed, processing, failed). Built-in voices such as "Adrian" are NOT listed here — they come from list_tts_models.

- **POST** `create_voice` — Clone a voice from one reference audio clip. The caller passes a URL; ToolMesh fetches the bytes and posts them as the multipart 'file' field, alongside the voice name. Returns the voice with a per-model readiness array; preparation is asynchronous, so poll get_voice until the model you want shows status "ready" before using the voice id in generate_speech. name must be 1..128 chars and unique in the project (409 voice_name_conflict otherwise). An over-long or unusable clip surfaces as status "failed" with error_type voice_audio_too_long or voice_invalid_audio on that model entry, not as an HTTP error. Only clone voices you have permission to clone.

- **GET** `get_voices_count` — Number of cloned voices in the project: {total}. Unlike the file and transcription counters this is not split by origin.

- **GET** `get_voice` — Get one cloned voice: {id, name, filename, created_at, models}. models holds per-model readiness {model, status, error_type, error_message} with status not_computed | processing | ready | failed. This is the endpoint to poll after create_voice and after recompute_voice. Voice preparation failures (clip too long, unusable audio) appear HERE as a failed model entry — the create call itself will have returned 201.

- **DELETE** `delete_voice` — Permanently delete a cloned voice and all its computed embeddings. Irreversible; the reference clip is not recoverable and re-cloning needs the original audio again. Returns 204 with no body, or 404 voice_not_found. Any generate_speech call still referencing the id then fails with voice_not_found.

- **POST** `recompute_voice` — Prepare an existing cloned voice for models it is not ready for yet — the fix after Soniox ships a new TTS model and an older voice shows status "not_computed" for it. Pass model to target one model, or omit it to prepare the voice for every model it is missing. Models the voice is already prepared for are left untouched, so this is safe to call repeatedly. Returns the voice; preparation is asynchronous, so poll get_voice until the target model reports "ready".

- **POST** `generate_speech` — Synthesize speech from text and return it as a downloadable audio file. The API answers with raw audio bytes, so ToolMesh stores them in its File Broker and returns {url, content_type, size_bytes, expires} with a download URL valid for 24 hours — that URL is unauthenticated while it lives, so treat generated speech as sensitive. The returned content_type is only best-effort (mp3 comes back as audio/mpeg, wav as application/octet-stream); rely on the audio_format you requested, not on that field. Required: model (e.g. tts-rt-v2; see list_tts_models), language (the 2-letter code of the INPUT text), voice (a built-in name such as "Adrian", or the UUID of a cloned voice from list_voices), audio_format, and text. audio_format is one of wav, mp3, aac, opus, flac, pcm_s16le, pcm_s16be, pcm_f32le, pcm_mulaw, pcm_alaw. sample_rate (Hz) is one of 8000, 16000, 24000, 44100, 48000; bitrate (bits/s) one of 32000, 64000, 96000, 128000, 192000, 256000, 320000 — both optional and only meaningful for the formats that carry them. speed runs 0.7..1.3 (1.0 = normal) and only on models with supports_speed_adjustment. reduce_silence shortens inter-word pauses and only works on models with supports_silence_reduction — enabling it elsewhere is a 400 invalid_request. A cloned voice must be "ready" for this model or the call fails with 409 voice_not_prepared / 503 voice_not_ready — check get_voice first. The two "bad voice" errors mean different things (verified live): an unknown built-in NAME is 400 invalid_request "Invalid voice 'X' for model 'Y'" — usually a typo, or a v2-only voice used on v1, so re-check list_tts_models. An unknown or deleted UUID is 404 voice_not_found — the cloned voice is gone and has to be recreated. This endpoint is synchronous: long text means a long request.

- **GET** `list_models` — List the speech-to-text models this account can use. Returns {models: [{id, name, aliased_model_id, transcription_mode, context_version, language_codes, translation_targets, two_way_translation_pairs, one_way_translation, two_way_translation, supports_language_hints_strict, supports_max_endpoint_delay, supports_endpoint_sensitivity, supports_endpoint_latency_adjustment, endpoint_latency_adjustment_max_level}]}. transcription_mode is "async" (recorded audio, for create_transcription) or "real_time" (WebSocket only). aliased_model_id is non-null for aliases such as stt-async-v4, which points at stt-async-v5. language_codes is projected down to the 2-letter codes; one_way_translation containing "all_languages" means any listed language may be a translation target. Call this instead of hardcoding model ids — the catalog changes.

- **GET** `list_tts_models` — List the text-to-speech models and their BUILT-IN voices. Returns {models: [{id, name, aliased_model_id, language_codes, voices: [{id, description, gender}], supports_timestamps, supports_speed_adjustment, speed_min, speed_max, supports_silence_reduction}]}. The voices array is where built-in voice names such as "Adrian" come from — cloned voices live in list_voices instead. Check supports_speed_adjustment / supports_silence_reduction here before passing speed or reduce_silence to generate_speech, or the call is rejected. language_codes is projected down to the 2-letter codes.

- **POST** `create_temporary_api_key` — Mint a short-lived API key so a browser or mobile client can open a Soniox WebSocket stream directly, without ever seeing the long-lived key. Returns {api_key, expires_at} — the key is returned in FULL, so hand it straight to the intended client and never log it. usage_type locks the key to one service and is required: "transcribe_websocket" (real-time speech-to-text) or "tts_rt" (text-to-speech, WebSocket and REST). expires_in_seconds is required and capped at 3600 — it bounds how long NEW streams may be opened, not how long an already-open stream lives. Set single_use to true when the client needs exactly one session, and max_session_duration_seconds (1..18000) to cap how long a single stream may stay open. Always call this from your backend with the real key; a temporary key is the only credential that belongs in untrusted code.

- **GET** `list_usage_logs` — Per-request usage and cost entries for the project, filtered by request END time. Returns {usage_logs: [{uuid, request_scope, client_reference_id, model, start_time, end_time, input_text_tokens, input_audio_tokens, input_audio_duration_ms, output_text_tokens, output_audio_tokens, output_audio_duration_ms, cost_usd, input_cost_usd, input_text_cost_usd, input_audio_cost_usd, output_cost_usd, output_text_cost_usd, output_audio_cost_usd}], next_page_cursor}. All cost fields are DECIMAL STRINGS ("0.0081000000"), not numbers. start_time and end_time are required ISO 8601 UTC timestamps; the window must be at most 31 days and start_time may not be more than 91 days ago. sort is end_time_asc (default) or end_time_desc — pass the same sort value alongside cursor when paging. client_reference_id is how you attribute cost back to your own jobs or tenants. This can be a very large result set; narrow the window rather than paging blindly, and prefer get_usage_summary for totals.

- **GET** `get_usage_summary` — Daily cost and activity for the project, aggregated per whole UTC day and broken down per model. Returns {total, models: [...]} where each entry is {model (null for the total), days: ["2026-08-01", ...], total_cost_usd, total_input_cost_usd, total_output_cost_usd, total_duration_cost_usd, cost_usd[], input_cost_usd[], output_cost_usd[], duration_cost_usd[], total_num_requests, total_input_text_tokens, total_input_audio_tokens, total_input_audio_duration_ms, total_output_text_tokens, total_output_audio_tokens, total_output_audio_duration_ms, total_duration_ms, num_requests[], and the matching per-day arrays}. The per-day arrays are POSITIONALLY aligned with days — index i of cost_usd is the cost on days[i]. Cost values are decimal strings, not numbers. The window is half-open [start_time, end_time): a day is included when the window covers any part of it, so an end_time exactly at midnight excludes that day. At most 366 UTC days per call. Cheaper than list_usage_logs for "what did this cost" questions.

- **GET** `get_concurrency_limits` — Current concurrent stream counts and the configured caps, for both the project and its organization. Returns {project: {current: {...}, limits: {...}}, organization: {current: {...}, limits: {...}}}, where each inner object carries transcribe_concurrent, tts_concurrent and voice_agent_concurrent. A limits value of null means "no project-level cap set — the organization limit applies", so always compare against the organization numbers too. Region-scoped: it reports the region of the host being called. Check this when streams start failing with max_concurrent_streams_reached, since the organization cap can bind even when the project looks idle.

- **GET** `list_concurrent_streams_history` — Historical concurrent stream counts per aggregation period. Returns {kind, entries: [{period_start, period_sec, sample_min, sample_max, sample_sum, sample_count, total_count}]} ordered by period_start ascending, with NO gaps — idle periods are returned with every field 0. sample_max is the peak concurrency and stays exact when minutes roll up into hours or days; sample_min is always 0 by construction, so use sample_max for headroom questions. sample_sum / sample_count is the average while streams were active, sample_sum / total_count the average across the whole period. All four parameters are required: start_time and end_time as ISO 8601 UTC (half-open, filtered on period_start), period_sec as 60, 3600 or 86400 (the period also caps how long the window may be), and kind as "stt" (speech-to-text WebSocket sessions) or "tts" (text-to-speech WebSocket streams and REST requests). Region-scoped.


## What composite workflows does the Soniox DADL provide?

- **FN** `get_transcript_text` — Get just the words of a completed transcription — {id, text, character_count, token_count} — without pulling the full token array into context. This is the cheap default for "what was said"; use get_transcript_segments when you need speakers, and the raw get_transcription_transcript only for per-token timings or confidences.

- **FN** `get_transcript_segments` — Fetch a completed transcript and merge its sub-word tokens into readable segments, collapsing thousands of tokens into a handful of passages. Returns {id, group_by, segment_count, segments: [{group, translation_status, start_ms, end_ms, text, token_count}]}. group_by selects what starts a new segment: "speaker" (default — requires enable_speaker_diarization), "language" (requires enable_language_identification) or "translation_status". A gap longer than max_gap_ms also splits a segment, and a change of translation_status ALWAYS does — original and translated tokens share the same timestamps, so they must never be merged into one passage. Set the translation_status parameter to "original" or "translation" to drop one side of a translated transcript entirely. Costs exactly one API call.

- **FN** `get_transcription_result` — Status-and-transcript in one call: read a transcription's status and, if it is already "completed", return the transcript text with it. Returns {id, status, ready, model, filename, audio_duration_ms, text, token_count} when finished, and {id, status, ready: false, error_type, error_message, text: null} while queued/processing or on error. This is the tool to call in a polling loop — it saves a round trip on the iteration that finally succeeds. Set include_tokens to also return the raw token array (large). It does NOT wait or sleep: call it again from the agent side, and space the calls out rather than busy-looping.

- **FN** `cleanup_transcriptions` — Delete finished transcriptions to get back under the 2000-transcription project cap. DRY RUN BY DEFAULT — it reports what it would delete and changes nothing until dry_run is explicitly false. Considers only finished jobs ("completed" and "error"); queued and processing jobs are never touched and cannot be deleted anyway. status narrows that to one value, keep_newest protects the N most recent matches, and at most 40 deletions happen per call (the composite sandbox caps API calls) — run it again for more. Returns {dry_run, matched, would_delete, ids} in dry run, and {dry_run, matched, deleted_count, deleted, failed} when armed. Deletion is irreversible and does NOT remove the underlying files.


## Which DADLs are related to Soniox?

- [Billomat](https://www.dadl.ai/d/billomat/) — Billomat REST API — German cloud invoicing & accounting: invoices, estimates, credit notes, recurring invoices, delivery notes, order confirmations, dunning reminders, clients, contacts, articles, payments, expenses, taxes
- [Xen Orchestra](https://www.dadl.ai/d/xen-orchestra/) — Xen Orchestra REST API (XO 6.4+, current through 6.7) -- complete coverage: VMs (incl. PATCH update + snapshot revert), VM controllers, hosts (incl. full power/lifecycle actions + maintenance mode), pools (incl. add_host), storage (SR create/delete, VDI incl. PATCH update, VBD), networks (VIF incl. PATCH update + PIF/PBD), VM/VDI snapshots, VM templates, hardware (PCI/PGPU/SM), tasks, backups (jobs/logs/repositories incl. health + benchmark/restore), schedules, messages, alarms, events (SSE), RBAC v2 (users/groups/acl-roles/acl-privileges incl. assignment introspection), proxies, servers, dashboards, auth tokens, SDN traffic rules (add/delete/update), health check
- [Graylog](https://www.dadl.ai/d/graylog/) — Graylog REST API -- log search (Views/Search + legacy universal), streams, pipelines, inputs, alerts, events, dashboards, users, roles, sidecars, index management, and cluster administration. Targets Graylog 6.x.
- [Mastodon](https://www.dadl.ai/d/mastodon/) — Mastodon REST API — statuses, timelines, accounts, notifications, search, media, lists, polls, conversations, trends, filters, and instance info. Base URL must be set to the target Mastodon instance (e.g. https://mastodon.social).
- [Mempool](https://www.dadl.ai/d/mempool/) — mempool.space — Bitcoin block explorer, mempool visualizer, fee estimator, Lightning Network explorer, and transaction accelerator
- [NetBox](https://www.dadl.ai/d/netbox/) — NetBox DCIM/IPAM API -- full v4 coverage: sites, racks, devices, modules, interfaces, cables, power, IPAM (prefixes, IPs, VLANs, VRFs, route-targets, VLAN translation), virtualization, circuits (including virtual circuits), tenants, contacts, VPN (IKE/IPSec/L2VPN), wireless, extras (webhooks, event-rules, scripts, config-templates, bookmarks, notifications), users/permissions/tokens, and core data sources & jobs

---

**Canonical URL:** https://www.dadl.ai/d/soniox/
**Raw DADL:** https://github.com/DunkelCloud/dadl-registry/blob/main/soniox.dadl
