Text-to-Speech for Outbound Replies: Providers, Personas, and Setup
Learn how OpenClaw converts outgoing replies to audio across 14 providers, including native voice messages and PCM/Ulaw streams. Set up TTS with API keys, config, and slash commands.
Read this when
- Enabling text-to-speech for replies
- Configuring a TTS provider, fallback chain, or persona
- Using /tts commands or directives
OpenClaw turns outgoing replies into audio through 14 speech providers: Feishu, Matrix, Telegram, and WhatsApp get native voice messages; other platforms receive audio attachments; telephony and Talk use PCM/Ulaw streams.
On the speech-output side of Talk's stt-tts mode, TTS handles the
synthesis, and talk.speak routes through this same pipeline. When Talk
sessions run inside a provider that supports native realtime, that
provider does the speech generation itself; transcription sessions never
produce a synthesized assistant voice.
Quick start
Pick a provider
For hosted options, OpenAI and ElevenLabs give the most dependable results. Microsoft and Local CLI need no API key. The complete provider list lives in the provider matrix.
Set the API key
Set the environment variable for your provider (for instance OPENAI_API_KEY,
ELEVENLABS_API_KEY). Microsoft and Local CLI skip the key requirement.
Enable in config
Configure tts.auto: "always" and tts.provider:
{
tts: {
auto: "always",
provider: "elevenlabs",
},
}
Try it in chat
Current status appears via /tts status. A single audio reply is sent
with /tts audio Hello from OpenClaw.
Note
Auto-TTS starts off. With
tts.providerleft empty, OpenClaw chooses the first configured provider following registry auto-select order. The built-inttsagent tool only acts on explicit intent: normal chat remains text unless the user requests audio, uses/tts, or turns on Auto-TTS/directive speech.
Supported providers
| Provider | Auth | Notes |
|---|---|---|
| Azure Speech | AZURE_SPEECH_KEY + AZURE_SPEECH_REGION (also AZURE_SPEECH_API_KEY, SPEECH_KEY, SPEECH_REGION) | Native Ogg/Opus voice-note output and telephony. |
| DeepInfra | DEEPINFRA_API_KEY | OpenAI-compatible TTS. Defaults to hexgrad/Kokoro-82M. |
| ElevenLabs | ELEVENLABS_API_KEY or XI_API_KEY | Voice cloning, multilingual, deterministic via seed; streamed for Discord voice playback. |
| Fish Audio | FISH_API_KEY or FISH_AUDIO_API_KEY | S2.1 hosted TTS, expressive tags, voice discovery, streaming, and telephony. |
| Google Gemini | GEMINI_API_KEY or GOOGLE_API_KEY | Gemini API batch TTS; persona-aware via promptTemplate: "audio-profile-v1". |
| Gradium | GRADIUM_API_KEY | Voice-note and telephony output. |
| Inworld | INWORLD_API_KEY | Streaming TTS API. Native Opus voice-note and PCM telephony. |
| Local CLI | none | Runs a configured local TTS command. |
| Microsoft | none | Public Edge neural TTS via node-edge-tts. Best-effort, no SLA. |
| MiniMax | MINIMAX_API_KEY (or Token Plan: MINIMAX_OAUTH_TOKEN, MINIMAX_CODE_PLAN_KEY, MINIMAX_CODING_API_KEY) | T2A v2 API. Defaults to speech-2.8-hd. |
| OpenAI | OPENAI_API_KEY | Also used for auto-summary; supports persona instructions. |
| OpenRouter | OPENROUTER_API_KEY (can reuse models.providers.openrouter.apiKey) | Default model hexgrad/kokoro-82m. |
| Volcengine | VOLCENGINE_TTS_API_KEY or BYTEPLUS_SEED_SPEECH_API_KEY (legacy AppID/token: VOLCENGINE_TTS_APPID/_TOKEN) | BytePlus Seed Speech HTTP API. |
| Vydra | VYDRA_API_KEY | Shared image, video, and speech provider. |
| xAI | XAI_API_KEY | xAI batch TTS. Native Opus voice-note is not supported. |
| Xiaomi MiMo | XIAOMI_API_KEY | MiMo TTS through Xiaomi chat completions. |
When several providers are set up, the chosen one takes priority and the rest
serve as backups. Auto-summary relies on summaryModel (or
agents.defaults.model.primary), so that provider needs authentication too if you leave
summaries active.
Warning
The Microsoft provider that ships with OpenClaw relies on the online neural TTS service from Microsoft Edge, accessed through
node-edge-tts. Since this is a public web service with no published SLA or quota, treat it as best-effort. The older provider idedgegets normalized tomicrosoft, andopenclaw doctor --fixmigrates stored config; new configurations should stick withmicrosoft.
Configuration
TTS settings are stored under tts inside ~/.openclaw/openclaw.json. Choose a preset and modify the provider section. The speakerVoice and speakerVoiceId fields shown here are the standard ones; each provider's own voice, voiceId, and voiceName field names remain valid as legacy aliases.
Azure Speech
{
tts: {
auto: "always",
provider: "azure-speech",
providers: {
"azure-speech": {
apiKey: "${AZURE_SPEECH_KEY}",
region: "eastus",
speakerVoice: "en-US-JennyNeural",
lang: "en-US",
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
voiceNoteOutputFormat: "ogg-24khz-16bit-mono-opus",
},
},
},
}
ElevenLabs
{
tts: {
auto: "always",
provider: "elevenlabs",
providers: {
elevenlabs: {
apiKey: "${ELEVENLABS_API_KEY}",
model: "eleven_multilingual_v2",
speakerVoiceId: "EXAVITQu4vr4xnSDxMaL",
},
},
},
}
Fish Audio
{
tts: {
auto: "tagged",
provider: "fish-audio",
providers: {
"fish-audio": {
apiKey: "${FISH_API_KEY}",
model: "s2.1-pro",
speakerVoiceId: "802e3bc2b27e49c2995d23ef70e6ac89",
latency: "balanced",
},
},
},
}
Google Gemini
{
tts: {
auto: "always",
provider: "google",
providers: {
google: {
apiKey: "${GEMINI_API_KEY}",
model: "gemini-3.1-flash-tts-preview",
speakerVoice: "Kore",
// Optional natural-language style prompts:
// audioProfile: "Speak in a calm, podcast-host tone.",
// speakerName: "Alex",
},
},
},
}
Gradium
{
tts: {
auto: "always",
provider: "gradium",
providers: {
gradium: {
apiKey: "${GRADIUM_API_KEY}",
speakerVoiceId: "YTpq7expH9539ERJ",
},
},
},
}
Inworld
{
tts: {
auto: "always",
provider: "inworld",
providers: {
inworld: {
apiKey: "${INWORLD_API_KEY}",
modelId: "inworld-tts-1.5-max",
speakerVoiceId: "Sarah",
temperature: 0.7,
},
},
},
}
Local CLI
{
tts: {
auto: "always",
provider: "tts-local-cli",
providers: {
"tts-local-cli": {
command: "say",
args: ["-o", "{{OutputPath}}", "{{Text}}"],
outputFormat: "wav",
timeoutMs: 120000,
},
},
},
}
Microsoft (no key)
{
tts: {
auto: "always",
provider: "microsoft",
providers: {
microsoft: {
enabled: true,
speakerVoice: "en-US-MichelleNeural",
lang: "en-US",
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
rate: "+0%",
pitch: "+0%",
},
},
},
}
MiniMax
{
tts: {
auto: "always",
provider: "minimax",
providers: {
minimax: {
apiKey: "${MINIMAX_API_KEY}",
model: "speech-2.8-hd",
speakerVoiceId: "English_expressive_narrator",
speed: 1.0,
vol: 1.0,
pitch: 0,
},
},
},
}
OpenAI + ElevenLabs
{
tts: {
auto: "always",
provider: "openai",
summaryModel: "openai/gpt-4.1-mini",
modelOverrides: { enabled: true },
providers: {
openai: {
apiKey: "${OPENAI_API_KEY}",
model: "gpt-4o-mini-tts",
speakerVoice: "alloy",
},
elevenlabs: {
apiKey: "${ELEVENLABS_API_KEY}",
model: "eleven_multilingual_v2",
speakerVoiceId: "EXAVITQu4vr4xnSDxMaL",
voiceSettings: { stability: 0.5, similarityBoost: 0.75, style: 0.0, useSpeakerBoost: true, speed: 1.0 },
applyTextNormalization: "auto",
languageCode: "en",
},
},
},
}
OpenRouter
{
tts: {
auto: "always",
provider: "openrouter",
providers: {
openrouter: {
apiKey: "${OPENROUTER_API_KEY}",
model: "hexgrad/kokoro-82m",
speakerVoice: "af_alloy",
responseFormat: "mp3",
},
},
},
}
Volcengine
{
tts: {
auto: "always",
provider: "volcengine",
providers: {
volcengine: {
apiKey: "${VOLCENGINE_TTS_API_KEY}",
resourceId: "seed-tts-1.0",
speakerVoice: "en_female_anna_mars_bigtts",
},
},
},
}
xAI
{
tts: {
auto: "always",
provider: "xai",
providers: {
xai: {
apiKey: "${XAI_API_KEY}",
speakerVoiceId: "eve",
language: "en",
responseFormat: "mp3",
},
},
},
}
Xiaomi MiMo
{
tts: {
auto: "always",
provider: "xiaomi",
providers: {
xiaomi: {
apiKey: "${XIAOMI_API_KEY}",
model: "mimo-v2.5-tts",
speakerVoice: "mimo_default",
format: "mp3",
},
},
},
}
For Xiaomi mimo-v2.5-tts-voicedesign, leave out speakerVoice and assign style to the voice-design prompt. OpenClaw passes that prompt as the TTS user message and skips sending audio.voice for the voicedesign model.
Local Speech Swift and speech-core
Speech Swift and speech-core offer local speech inference on macOS, Linux, and Windows. When Speech Swift and OpenClaw share the same Mac, go with the OpenAI-compatible HTTP provider. For direct executable integration on any supported platform, use Local CLI.
Install ffmpeg if a channel needs OpenClaw to turn WAV output into Opus or raw PCM.
macOS HTTP
Warning
Speech Swift v0.0.23 or newer is required for this HTTP setup. If Homebrew installed an older version, run
brew update && brew upgrade speechfirst.
Launch Speech Swift's local server:
brew install speech
speech-server --port 8080
Direct the OpenAI speech provider to its loopback endpoint. Set responseFormat to wav because the local endpoint produces no compressed audio:
{
tts: {
auto: "always",
provider: "openai",
providers: {
openai: {
apiKey: "local",
baseUrl: "http://127.0.0.1:8080/v1",
model: "tts-1",
speakerVoice: "alloy",
responseFormat: "wav",
},
},
},
}
tts-1 picks Kokoro. Other local engines are chosen via Speech Swift registry aliases like qwen3-tts, cosyvoice, and voxcpm2. The placeholder API key is mandatory in OpenClaw's provider config but the loopback server never checks it.
macOS CLI
The Homebrew speech executable can write straight to OpenClaw's per-invocation output path:
{
tts: {
auto: "always",
provider: "tts-local-cli",
providers: {
"tts-local-cli": {
command: "speech",
args: ["speak", "{{Text}}", "--output", "{{OutputPath}}"],
outputFormat: "wav",
timeoutMs: 120000,
},
},
},
}
Linux CLI
Install a speech-core Linux release package, fetch the ONNX model set once, and test synthesis before launching OpenClaw:
speech download-models
speech speak "Hello from OpenClaw" hello.wav
Then set up the packaged Kokoro command:
{
tts: {
auto: "always",
provider: "tts-local-cli",
providers: {
"tts-local-cli": {
command: "speech",
args: ["speak", "{{Text}}", "{{OutputPath}}"],
outputFormat: "wav",
timeoutMs: 120000,
},
},
},
}
Check the speech-core Linux CLI reference for release packages and model-directory settings.
Windows CLI
Grab the speech-core Windows release, unpack it, and install the ONNX models once:
$Version = "0.0.11"
$Url = "https://github.com/soniqo/speech-core/releases/download/v$Version/speech-$Version-windows-x64.zip"
Invoke-WebRequest $Url -OutFile speech.zip
Expand-Archive speech.zip
Set-Location "speech\speech-$Version-windows-x64\bin"
Set-ExecutionPolicy -Scope Process Bypass
.\speech_download_models.ps1
Then aim Local CLI at the packaged Kokoro executable:
{
tts: {
auto: "always",
provider: "tts-local-cli",
providers: {
"tts-local-cli": {
command: "C:\\path\\to\\speech-0.0.11-windows-x64\\bin\\speech_synthesize.exe",
args: ["{{OutputPath}}", "{{Text}}", "en"],
outputFormat: "wav",
timeoutMs: 120000,
},
},
},
}
See the speech-core Windows CLI reference for the packaged server, model cache, and standalone command syntax.
Per-agent voice overrides
Use agents.entries.*.tts when a single agent needs a different provider, voice, model, persona, or auto-TTS mode. The agent block deep-merges over tts, so provider credentials can remain in the global provider config:
{
tts: {
auto: "always",
provider: "elevenlabs",
providers: {
elevenlabs: { apiKey: "${ELEVENLABS_API_KEY}", model: "eleven_multilingual_v2" },
},
},
agents: {
list: [
{
id: "reader",
tts: {
providers: {
elevenlabs: { speakerVoiceId: "EXAVITQu4vr4xnSDxMaL" },
},
},
},
],
},
}
To lock in a per-agent persona, set agents.entries.*.tts.persona alongside provider config; it overrides the global tts.persona only for that agent.
Precedence for automatic replies, /tts audio, /tts status, and the
tts agent tool is resolved in this order:
tts- the active
agents.entries.*.tts - channel override, provided the channel supports
channels.<channel>.tts - account override, provided the channel passes
channels.<channel>.accounts.<id>.tts - local
/ttspreferences for this host - inline
[[tts:...]]directives when model overrides are enabled
Overrides at the channel and account levels follow the same format as tts
and merge deeply over the layers beneath them. This lets shared provider
credentials live in tts while a channel or bot account adjusts only the
speaker voice, model, persona, or auto mode:
{
tts: {
provider: "openai",
providers: {
openai: { apiKey: "${OPENAI_API_KEY}", model: "gpt-4o-mini-tts" },
},
},
channels: {
feishu: {
accounts: {
english: {
tts: {
providers: {
openai: { speakerVoice: "shimmer" },
},
},
},
},
},
},
}
Personas
A persona defines a fixed spoken identity that applies predictably across providers. It may favor a particular provider, specify provider-neutral prompt intent, and carry provider-specific bindings for voices, models, prompt templates, seeds, and voice settings.
Minimal persona
{
tts: {
auto: "always",
persona: "narrator",
personas: {
narrator: {
label: "Narrator",
provider: "elevenlabs",
providers: {
elevenlabs: {
speakerVoiceId: "EXAVITQu4vr4xnSDxMaL",
modelId: "eleven_multilingual_v2",
},
},
},
},
},
}
Full persona (provider-specific shaping)
{
tts: {
auto: "always",
persona: "alfred",
personas: {
alfred: {
label: "Alfred",
description: "Dry, warm British butler narrator.",
provider: "google",
fallbackPolicy: "preserve-persona",
providers: {
google: {
model: "gemini-3.1-flash-tts-preview",
speakerVoice: "Algieba",
promptTemplate: "audio-profile-v1",
},
openai: { model: "gpt-4o-mini-tts", speakerVoice: "cedar" },
elevenlabs: {
speakerVoiceId: "voice_id",
modelId: "eleven_multilingual_v2",
seed: 42,
voiceSettings: {
stability: 0.65,
similarityBoost: 0.8,
style: 0.25,
useSpeakerBoost: true,
speed: 0.95,
},
},
},
},
},
},
}
Persona resolution
Selection of the active persona is deterministic:
/tts persona <id>local preference, when present.tts.persona, when present.- No persona.
Provider selection follows an explicit-first approach:
- Direct overrides (CLI, gateway, Talk, allowed TTS directives).
/tts provider <id>local preference.- The active persona's
provider. tts.provider.- Registry auto-select.
For each provider attempt, OpenClaw merges configs in this sequence:
tts.providers.<id>tts.personas.<persona>.providers.<id>- Trusted request overrides
- Allowed model-emitted TTS directive overrides
Custom persona shaping
The provider-neutral personas.<id>.prompt.* config is no longer supported. Doctor
removes those fields and directs you to the speech-provider seam. Built-in
provider settings belong under personas.<id>.providers.<provider> (for example, Google
personaPrompt or OpenAI instructions). For custom behavior, write a
speech provider plugin with prepareSynthesis(ctx) and return adjusted text,
provider config, or overrides before synthesize() executes. This keeps expressive
prompt construction in provider code, where request semantics are known.
Fallback policy
fallbackPolicy governs what happens when a persona has no binding for the
provider being attempted:
| Policy | Behavior |
|---|---|
preserve-persona | Default. Provider-neutral prompt fields remain available; the provider can use or disregard them. |
provider-defaults | Persona is left out of prompt preparation for that attempt; the provider falls back to its neutral defaults while other providers remain candidates. |
fail | That provider attempt is skipped with reasonCode: "not_configured" and personaBinding: "missing". Fallback providers are still attempted. |
The entire TTS request fails only when every attempted provider is skipped or fails.
Provider selection for a Talk session is scoped to that session. A Talk client
should take provider ids, model ids, voice ids, and locales from talk.catalog and
send them through the Talk session or handoff request. Opening a voice session
should not alter tts or global Talk provider defaults.
Model-driven directives
By default, the assistant can emit [[tts:...]] directives to override
voice, model, or speed for a single reply, plus an optional
[[tts:text]]...[[/tts:text]] block for expressive cues meant for audio only:
Here you go.
[[tts:speakerVoiceId=pMsXgVXv3BLzUgSXRplE model=eleven_v3 speed=1.1]]
[[tts:text]](laughs) Read the song once more.[[/tts:text]]
When tts.auto is "tagged", directives are required to trigger
audio. Streaming block delivery removes directives from visible text before the
channel receives it, even when they span adjacent blocks.
provider=... is ignored unless modelOverrides.allowProvider: true. When a
reply declares provider=..., the remaining keys in that directive are parsed
only by that provider; unsupported keys are stripped and surfaced as TTS
directive warnings.
Directive keys available:
provider(registered provider id; requiresallowProvider: true)speakerVoice/speakerVoiceId(legacy aliases:voice,voiceName,voice_name,google_voice,voiceId)model/google_modelstability,similarityBoost,style,speed,useSpeakerBoostvol/volume(MiniMax volume,(0, 10])pitch(MiniMax integer pitch, −12 to 12; fractional values are truncated)emotion(Volcengine emotion tag)applyTextNormalization(auto|on|off)languageCode(ISO 639-1)seed
Turn off all model overrides:
{ messages: { tts: { modelOverrides: { enabled: false } } } }
Let users switch providers while other settings stay adjustable:
{ messages: { tts: { modelOverrides: { enabled: true, allowProvider: true, allowSeed: false } } } }
Slash commands
A single command, /tts, handles this. On Discord, OpenClaw also sets up /voice because
/tts is already a built-in Discord command; text /tts ... continues to function.
/tts off | on | status
/tts chat on | off | default
/tts latest
/tts provider <id>
/tts persona <id> | off
/tts limit <chars>
/tts summary off
/tts audio <text>
Note
Only authorized senders (subject to allowlist/owner rules) can run these commands, and either
commands.textor native command registration must be active.
Behavior notes:
/tts onsaves the local TTS preference toalways;/tts offsaves it tooff./tts chat on|off|defaultapplies a session-scoped auto-TTS override for the current chat./tts persona <id>saves the local persona preference;/tts persona offremoves it./tts latestpulls the most recent assistant reply from the current session transcript and delivers it as audio once. It keeps only a hash of that reply on the session entry to prevent duplicate voice sends./tts audioproduces a one-off audio reply (it does not turn TTS on)./tts limit <chars>accepts 100, 4096 (4096 is the Telegram caption/message max); values outside that range are rejected.limitandsummaryare kept in local prefs, not the main config./tts statusprovides fallback diagnostics for the latest attempt:Fallback: <primary> -> <used>,Attempts: ..., and per-attempt detail (provider:outcome(reasonCode) latency)./statusdisplays the current TTS mode along with the configured provider, model, voice, and sanitized custom endpoint metadata when TTS is enabled.
Per-user preferences
Slash commands write local overrides to the TTS preferences path. The default is
~/.openclaw/settings/tts.json; override it with OPENCLAW_TTS_PREFS. Doctor
moves the retired global tts.prefsPath value into shared machine state.
Advanced multi-agent setups may still set agents.entries.<id>.tts.prefsPath
when agents intentionally use separate preference stores.
| Stored field | Effect |
|---|---|
auto | Per-host automatic TTS override (always, off, …) |
provider | Per-host primary provider override |
persona | Per-host persona override |
maxLength | Summary/truncation limit (default 1500 characters, /tts limit range 100, 4096) |
summarize | Summary enable switch (default true) |
These values supersede the effective configuration coming from tts and the active
agents.entries.*.tts block for that host.
Output formats
Voice delivery depends on what each channel advertises. Channel plugins declare
whether voice-style TTS should request a native voice-note target from providers or
stick with standard audio-file synthesis, and whether the channel converts
non-native output before transmission.
Telegram additionally advertises captioned final TTS. When tts.mode: "final" is active and
Auto-TTS is set to always (or an eligible inbound mode), streamed text is buffered
until synthesis completes and then delivered as the voice-note caption. Text that
exceeds Telegram's caption limit trails the voice note as a separate text message. If
synthesis or a verified pre-send delivery step fails, OpenClaw falls back to sending the
visible text. tagged mode retains its usual streaming behavior, and text
inside a [[tts:text]] block stays audio-only.
Once synthesis finishes, OpenClaw saves batch TTS output in the media store under
tool-speech-synthesis. The reply references that persistent media path rather than a
provider temporary file, and routine media maintenance removes expired output.
Local CLI providers may still use {{OutputPath}} as scratch space before
OpenClaw imports the completed bytes. For inline-player formats and limits, see Media playback.
| Target | Format |
|---|---|
| Feishu / Matrix / Telegram / WhatsApp | Voice-note replies favor Opus (opus_48000_64 from ElevenLabs, opus from OpenAI). 48 kHz / 64 kbps balances clarity and size. |
| Other channels | MP3 (mp3_44100_128 from ElevenLabs, mp3 from OpenAI). 44.1 kHz / 128 kbps is the default balance for speech. |
| Talk / telephony | Provider-native PCM (Inworld 22050 Hz, Google 24 kHz), or ulaw_8000 from Gradium for telephony. |
Notes per provider:
- Feishu / WhatsApp transcoding: when a voice-note reply lands as MP3/WebM/WAV/M4A or another likely audio file, the channel plugin transcodes it to 48 kHz Ogg/Opus with
ffmpeg(libopus, 64 kbps) before sending the native voice message. WhatsApp sends the result through the Baileysaudiopayload withptt: trueandaudio/ogg; codecs=opus. On transcode failure: Feishu catches the error and falls back to sending the original file as a plain attachment; WhatsApp has no fallback, so the send itself fails rather than posting an incompatible PTT payload. - MiniMax: MP3 (
speech-2.8-hdmodel, 32 kHz sample rate) for normal audio attachments; transcoded to 48 kHz Opus withffmpegfor channel-advertised voice-note targets. - Xiaomi MiMo: MP3 by default, or WAV when configured; transcoded to 48 kHz Opus with
ffmpegfor channel-advertised voice-note targets. - Local CLI: uses the configured
outputFormat. Voice-note targets are converted to Ogg/Opus and telephony output is converted to raw 16 kHz mono PCM withffmpeg. - Google Gemini: returns raw 24 kHz PCM. OpenClaw wraps it as WAV for audio attachments, transcodes it to 48 kHz Opus for voice-note targets, and returns PCM directly for Talk/telephony.
- Gradium: WAV for audio attachments, Opus for voice-note targets, and
ulaw_8000at 8 kHz for telephony. - Inworld: MP3 for normal audio attachments, native
OGG_OPUSfor voice-note targets, and rawPCMat 22050 Hz for Talk/telephony. - xAI: MP3 by default; audio-file synthesis may use
mp3,wav,pcm,mulaw, oralawfor both buffered and streaming output. Voice-note targets use MP3 for streaming and buffered fallback because xAI'spcm,mulaw, andalawoutputs are headerless raw audio. Buffered synthesis uses xAI's batch REST/v1/ttsendpoint;textToSpeechStreamuses nativewss://api.x.ai/v1/tts. This is not the realtime voice contract. Native Opus voice-note format is not supported. - Microsoft: uses
microsoft.outputFormat(defaultaudio-24khz-48kbitrate-mono-mp3).- The bundled transport accepts an
outputFormat, but not all formats are available from the service. - Output format values follow Microsoft Speech output formats (including Ogg/WebM Opus).
- Telegram
sendVoiceaccepts OGG/MP3/M4A; use OpenAI/ElevenLabs if you need guaranteed Opus voice messages. - If the configured Microsoft output format fails, OpenClaw retries with MP3.
- When no explicit voice override is set and the default English voice is used, OpenClaw auto-switches to a Chinese neural voice (
zh-CN-XiaoxiaoNeural,zh-CNlocale) if the reply text is CJK-dominant.
- The bundled transport accepts an
OpenAI and ElevenLabs pick output formats per channel as listed above. An
explicit OpenAI responseFormat overrides that selection; a format that is not
voice-note compatible may be delivered as an audio file or transcoded by a
channel that supports conversion.
Auto-TTS behavior
When tts.auto is turned on, OpenClaw behaves as follows:
- If the response already carries structured media, TTS is skipped.
- Extremely brief responses, those under 10 characters, are not synthesized.
- When summarization is active, lengthy replies get condensed via
summaryModel(oragents.defaults.model.primary) before synthesis. - The generated audio file is appended to the response.
- In
mode: "final", audio is dispatched only after the streamed text finishes. For channels lacking captioned-final support, an audio-only supplement is provided; Telegram places text within its caption limit on the voice note and sends any remaining overflow as separate follow-up text. Produced media undergoes the same channel media normalization applied to standard reply attachments.
When the reply goes past maxLength, OpenClaw never drops audio entirely:
- Summary on (the default) with an available summary model: the text is condensed to roughly
maxLengthcharacters, and that summary is then synthesized. - Summary off, summarization failing, or no API key for the summary model: the text is cut down to
maxLengthcharacters and the truncated version is synthesized.
Reply -> TTS enabled?
no -> send text
yes -> has media / short?
yes -> send text
no -> length > limit?
no -> TTS -> attach audio
yes -> summary enabled and available?
no -> truncate -> TTS -> attach audio
yes -> summarize -> TTS -> attach audio
Field reference
Top-level tts.*
-
auto(off" | "always" | "inbound" | "tagged), Auto-TTS mode. Withinbound, audio is generated only after an inbound voice message arrives; withtagged, synthesis happens only when the reply carries[[tts:...]]directives or a[[tts:text]]block. -
enabled(boolean), Legacy toggle.openclaw doctor --fixmigrates this setting toauto. -
mode(final" | "all, default: final),"all"adds tool and block replies on top of final ones. -
provider(string), Identifier for the speech provider. If left unset, OpenClaw picks the first configured provider following registry auto-select order. The legacyprovider: "edge"is converted to"microsoft"byopenclaw doctor --fix. -
persona(string), Active persona id sourced frompersonas. This is normalized to lowercase. -
true, " type="object"> Stable spoken identity. Its fields arelabel,description,provider,fallbackPolicy,prompt, andproviders.<provider>. Refer to Personas for details. -
summaryModel(string), Low-cost model used for auto-summary, defaulting toagents.defaults.model.primary. It acceptsprovider/modelor a configured model alias. -
modelOverrides(object), Lets the model emit TTS directives.enabledcomes with a default oftrue;allowProviderdefaults tofalse. -
true, " type="object"> Provider-specific settings indexed by speech provider id. Legacy direct blocks (tts.openai,.elevenlabs,.microsoft,.edge) are rewritten byopenclaw doctor --fix; onlytts.providers.<id>is committed. -
maxTextLength(number, default: 4096), Upper limit on TTS input characters. Exceeding it causes/tts audio,tts.convert, andtts.speakto fail. -
timeoutMs(number, default: 30000), Timeout for requests, measured in milliseconds. A per-calltimeoutMs(agent tool, gateway) takes precedence when present; otherwise an explicitly settts.timeoutMsoverrides any plugin-defined provider default.
Provider apiKey fields may be raw strings or SecretRefs. On a cold Gateway startup, a TTS SecretRef that is unavailable flags the built-in TTS capability as configured-unavailable rather than halting the Gateway. In that case tts.speak returns UNAVAILABLE with reason SECRET_SURFACE_UNAVAILABLE, and no provider request goes out. Status and doctor report the degraded TTS owner along with its config paths. The explicit refs stay in the runtime snapshot, so environment or profile credentials cannot silently switch to a different account. Reloads and config-write preflight enforce the owner-aware degradation policy: an unchanged eligible TTS owner may keep its last-known-good credentials as stale, while a new or changed failure becomes cold without blocking healthy owners. Structurally invalid refs and resolved values still cause startup failure or rejection of the update.
Azure Speech
-
apiKey(string), Set through the environment variableAZURE_SPEECH_KEY,AZURE_SPEECH_API_KEY, orSPEECH_KEY. -
region(string), Specifies the Azure Speech region, for exampleeastus. The environment variablesAZURE_SPEECH_REGIONorSPEECH_REGIONcan supply this value. -
endpoint(string), An optional override for the Azure Speech endpoint, also known by the aliasbaseUrl. -
speakerVoice(string), The ShortName of the Azure voice, defaulting toen-US-JennyNeural. The legacy aliasvoiceis also accepted. -
lang(string), Language code used in SSML, withen-USas the default. -
outputFormat(string), The AzureX-Microsoft-OutputFormatapplied to standard audio output, defaulting toaudio-24khz-48kbitrate-mono-mp3. -
voiceNoteOutputFormat(string), The AzureX-Microsoft-OutputFormatdesignated for voice-note output, defaulting toogg-24khz-16bit-mono-opus.
ElevenLabs
-
apiKey(string), If not provided, this falls back toELEVENLABS_API_KEYorXI_API_KEY. -
model(string), Identifies the model, defaulting toeleven_multilingual_v2. The older identifierseleven_turbo_v2_5andeleven_turbo_v2are converted to the correspondingflashmodel. -
speakerVoiceId(string), The voice id for ElevenLabs, defaulting topMsXgVXv3BLzUgSXRplE. The legacy aliasvoiceIdworks as well. -
voiceSettings(object), Containsstability,similarityBoost, andstyle, each a0..1with defaults0.5/0.75/0, plususeSpeakerBoost(atrue|false, defaulttrue) andspeed(a0.5..2.0, default1.0). -
applyTextNormalization(auto" | "on" | "off), Controls how text normalization is handled. -
languageCode(string), A two-letter ISO 639-1 code, such asenorde. -
seed(number), An integer0..4294967295used to aim for consistent output. -
baseUrl(string), Lets you change the base URL for the ElevenLabs API.
Google Gemini
-
apiKey(string), When absent,GEMINI_API_KEYorGOOGLE_API_KEYis used as the fallback. If it is not set, TTS may reusemodels.providers.google.apiKeybefore checking the environment variables. -
model(string), The Gemini TTS model to use. Falls back togemini-3.1-flash-tts-previewif not specified. -
speakerVoice(string), Name of the prebuilt Gemini voice. Defaults toKore. Older names:voiceName,voice. -
audioProfile(string), A style prompt written in natural language, inserted ahead of the spoken text. -
speakerName(string), When your prompt references a specific speaker, this optional label is placed before the spoken text. -
promptTemplate(audio-profile-v1), Assignaudio-profile-v1to have active persona prompt fields wrapped in a deterministic Gemini TTS prompt layout. -
personaPrompt(string), Additional Google-specific persona prompt text, appended to the Director's Notes within the template. -
baseUrl(string), The only value allowed ishttps://generativelanguage.googleapis.com.Gradium
-
apiKey(string), Set through the environment variableGRADIUM_API_KEY. -
baseUrl(string), HTTPS URL for the Gradium API, hosted onapi.gradium.ai. Defaults tohttps://api.gradium.ai. -
speakerVoiceId(string), Defaults to Emma (YTpq7expH9539ERJ). Former alias:voiceId.Inworld
Inworld primary
-
apiKey(string), Configured via the environment variableINWORLD_API_KEY. -
baseUrl(string), Defaults tohttps://api.inworld.ai. -
modelId(string), Defaults toinworld-tts-1.5-max. Other options:inworld-tts-1.5-mini,inworld-tts-1-max,inworld-tts-1. -
speakerVoiceId(string), Defaults toSarah. Former alias:voiceId. -
temperature(number), Sampling temperature, set to0..2(0 is not allowed).Local CLI (tts-local-cli)
-
command(string), The local executable or command string used for CLI-based TTS. -
args(string[]), Arguments passed to the command. Placeholders{{Text}},{{OutputPath}},{{OutputDir}}, and{{OutputBase}}are supported. -
outputFormat(mp3" | "opus" | "wav), The output format expected from the CLI. Defaults tomp3for audio attachments. -
timeoutMs(number), Timeout for the command, measured in milliseconds. Defaults to120000. -
cwd(string), Working directory for the command, if needed. -
env(true), Optional environment variable overrides for the command.Output from the command's stdout, along with any generated or converted audio, cannot exceed 50 MiB. Stderr diagnostics are capped at 1 MiB. If either threshold is crossed, OpenClaw kills the command and marks synthesis as failed.
Microsoft (no API key)
-
enabled(boolean, default: true), Permits the use of Microsoft speech services. -
speakerVoice(string), Name of the Microsoft neural voice, for exampleen-US-MichelleNeural. Former alias:voice. When the default English voice is active and the reply text is mostly CJK, OpenClaw automatically switches tozh-CN-XiaoxiaoNeural. -
lang(string), Language code, such asen-US. -
outputFormat(string), Specifies the Microsoft output format. The default isaudio-24khz-48kbitrate-mono-mp3. Certain formats are unavailable with the built-in Edge-based transport.-
rate / pitch / volume(string), Percent-encoded strings, for example+10%or-5%. -
saveSubtitles(boolean), Generates JSON subtitle files next to the audio output. -
proxy(string), Proxy address used for Microsoft speech calls. -
timeoutMs(number), Overrides the request timeout, measured in milliseconds. -
edge.*(object), Deprecated alias. Executeopenclaw doctor --fixto migrate stored configuration toproviders.microsoft.
MiniMax
-
apiKey(string), UsesMINIMAX_API_KEYas a fallback. Token Plan authentication throughMINIMAX_OAUTH_TOKEN,MINIMAX_CODE_PLAN_KEY, orMINIMAX_CODING_API_KEY. -
baseUrl(string), Defaults tohttps://api.minimax.io. Environment variable:MINIMAX_API_HOST. -
model(string), Defaults tospeech-2.8-hd. Environment variable:MINIMAX_TTS_MODEL. -
speakerVoiceId(string), Defaults toEnglish_expressive_narrator. Environment variable:MINIMAX_TTS_VOICE_ID. Deprecated alias:voiceId. -
speed(number),0.5..2.0. Defaults to1.0. -
vol(number),(0, 10]. Defaults to1.0. -
pitch(number), Must be an integer-12..12. Defaults to0. Fractional parts get cut off before the request is sent.
OpenAI
-
apiKey(string), Falls back toOPENAI_API_KEY. -
model(string), Identifier for the OpenAI TTS model. Defaults togpt-4o-mini-tts. -
speakerVoice(string), Voice selection, such asalloyorcedar. Defaults tocoral. Deprecated alias:voice. -
instructions(string), Explicit value for the OpenAIinstructionsfield. If set, persona prompt fields are not mapped automatically. -
responseFormat(mp3" | "opus" | "wav), Explicit response format. When omitted, OpenClaw picks Opus for voice-note targets and MP3 in all other cases. Usewavwith compatible local endpoints that skip compressed audio encoding. -
extraBody / extra_body(true), ">Additional JSON fields merged into/audio/speechrequest bodies after the generated OpenAI TTS fields. Suitable for OpenAI-compatible services like Kokoro that need provider-specific keys such aslang; unsafe prototype keys get ignored. -
baseUrl(string), Overrides the OpenAI TTS endpoint. Lookup order: config, thenOPENAI_TTS_BASE_URL, thenhttps://api.openai.com/v1. Non-default values mark the endpoint as OpenAI-compatible, so custom model and voice names are allowed, andspeedno longer enforces its0.25..4.0range check.
OpenRouter
-
-
apiKey(string), Set via environment variableOPENROUTER_API_KEY.models.providers.openrouter.apiKeymay be reused. -
baseUrl(string), Falls back tohttps://openrouter.ai/api/v1. The olderhttps://openrouter.ai/v1form gets normalized. -
model(string), Defaults tohexgrad/kokoro-82m. Also known asmodelId. -
speakerVoice(string), Defaults toaf_alloy. Older names:voiceandvoiceId. -
responseFormat(mp3" | "pcm), Defaults tomp3. -
speed(number), Speed override passed straight to the provider.Volcengine (BytePlus Seed Speech)
-
apiKey(string), Read fromVOLCENGINE_TTS_API_KEYorBYTEPLUS_SEED_SPEECH_API_KEY. -
resourceId(string), Defaults toseed-tts-1.0. Environment variable:VOLCENGINE_TTS_RESOURCE_ID. Pickseed-tts-2.0if your project carries a TTS 2.0 entitlement. -
appKey(string), Header carrying the app key. Defaults toaGjiRDfUWi. Environment variable:VOLCENGINE_TTS_APP_KEY. -
baseUrl(string), Point the Seed Speech TTS HTTP endpoint elsewhere. Environment variable:VOLCENGINE_TTS_BASE_URL. -
speakerVoice(string), Which voice type to use. Defaults toen_female_anna_mars_bigtts. Environment variable:VOLCENGINE_TTS_VOICE. Older alias:voice. -
speedRatio(number), Speed ratio native to the provider,0.2..3. -
emotion(string), Emotion tag native to the provider. -
appId / token / cluster(string), Fields from the legacy Volcengine Speech Console. Environment variables:VOLCENGINE_TTS_APPID,VOLCENGINE_TTS_TOKEN,VOLCENGINE_TTS_CLUSTER(defaults tovolcano_tts).xAI
-
apiKey(string), Read from environment variableXAI_API_KEY. -
baseUrl(string), Defaults tohttps://api.x.ai/v1. Environment variable:XAI_BASE_URL. -
speakerVoiceId(string), Defaults toeve. When authentication is on,openclaw infer tts voices --provider xaipulls the current built-in catalog; without it, offline fallbacks are listed:ara,eve,leo,rex, andsal. Custom voice IDs from the account are passed along even if they do not appear in the built-in list. Older alias:voiceId. -
language(string), A BCP-47 language code orauto. Defaults toen. -
responseFormat(mp3" | "wav" | "pcm" | "mulaw" | "alaw), Falls back tomp3. -
speed(number), Provider-specific speed adjustment,0.7..1.5.Xiaomi MiMo
-
apiKey(string), Environment variable:XIAOMI_API_KEY. -
baseUrl(string), Falls back tohttps://api.xiaomimimo.com/v1. Environment variable:XIAOMI_BASE_URL. -
model(string), Falls back tomimo-v2.5-tts. Environment variable:XIAOMI_TTS_MODEL. Additionally acceptsmimo-v2.5-tts-voicedesign. -
speakerVoice(string), Falls back tomimo_defaultfor models using preset voices. Environment variable:XIAOMI_TTS_VOICE. Older alias:voice. Excluded from requests formimo-v2.5-tts-voicedesign. -
format(mp3" | "wav), Falls back tomp3. Environment variable:XIAOMI_TTS_FORMAT. -
style(string), Optional style instruction in natural language, transmitted as the user message; not read aloud. Formimo-v2.5-tts-voicedesign, this acts as the voice-design prompt; if left out, OpenClaw provides a default.
Agent tool
The tts tool changes text into speech and returns an audio attachment for reply delivery. On Feishu, Matrix, Telegram, and WhatsApp, the audio arrives as a voice message instead of a file attachment. When ffmpeg is present, Feishu and WhatsApp can transcode non-Opus TTS output along this route.
WhatsApp sends audio via Baileys as a PTT voice note (audio with ptt: true) and transmits visible text apart from PTT audio, since clients do not reliably show captions on voice notes.
The tool accepts optional channel and timeoutMs fields; timeoutMs is a per-call provider request timeout measured in milliseconds. Per-call values take precedence over tts.timeoutMs; configured TTS timeouts override any provider default set by a plugin.
Gateway RPC
| Method | Purpose |
|---|---|
tts.status | Retrieve current TTS state and last attempt. |
tts.enable | Set local auto preference to always. |
tts.disable | Set local auto preference to off. |
tts.convert | One-off text to audio. |
tts.setProvider | Set local provider preference. |
tts.personas | Show configured personas and the active one. |
tts.setPersona | Set local persona preference. |
tts.providers | Show configured providers and status. |
Service links
- Azure Speech provider
- Azure Speech REST text-to-speech
- ElevenLabs Authentication
- ElevenLabs Text to Speech
- Gradium
- Inworld TTS API
- Microsoft Speech output formats
- MiniMax T2A v2 API
- node-edge-tts
- OpenAI Audio API reference
- OpenAI text-to-speech guide
- speech-core
- Speech Swift
- Volcengine TTS HTTP API
- xAI text to speech
- Xiaomi MiMo speech synthesis