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.provider left empty, OpenClaw chooses the first configured provider following registry auto-select order. The built-in tts agent 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

ProviderAuthNotes
Azure SpeechAZURE_SPEECH_KEY + AZURE_SPEECH_REGION (also AZURE_SPEECH_API_KEY, SPEECH_KEY, SPEECH_REGION)Native Ogg/Opus voice-note output and telephony.
DeepInfraDEEPINFRA_API_KEYOpenAI-compatible TTS. Defaults to hexgrad/Kokoro-82M.
ElevenLabsELEVENLABS_API_KEY or XI_API_KEYVoice cloning, multilingual, deterministic via seed; streamed for Discord voice playback.
Fish AudioFISH_API_KEY or FISH_AUDIO_API_KEYS2.1 hosted TTS, expressive tags, voice discovery, streaming, and telephony.
Google GeminiGEMINI_API_KEY or GOOGLE_API_KEYGemini API batch TTS; persona-aware via promptTemplate: "audio-profile-v1".
GradiumGRADIUM_API_KEYVoice-note and telephony output.
InworldINWORLD_API_KEYStreaming TTS API. Native Opus voice-note and PCM telephony.
Local CLInoneRuns a configured local TTS command.
MicrosoftnonePublic Edge neural TTS via node-edge-tts. Best-effort, no SLA.
MiniMaxMINIMAX_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.
OpenAIOPENAI_API_KEYAlso used for auto-summary; supports persona instructions.
OpenRouterOPENROUTER_API_KEY (can reuse models.providers.openrouter.apiKey)Default model hexgrad/kokoro-82m.
VolcengineVOLCENGINE_TTS_API_KEY or BYTEPLUS_SEED_SPEECH_API_KEY (legacy AppID/token: VOLCENGINE_TTS_APPID/_TOKEN)BytePlus Seed Speech HTTP API.
VydraVYDRA_API_KEYShared image, video, and speech provider.
xAIXAI_API_KEYxAI batch TTS. Native Opus voice-note is not supported.
Xiaomi MiMoXIAOMI_API_KEYMiMo 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 id edge gets normalized to microsoft, and openclaw doctor --fix migrates stored config; new configurations should stick with microsoft.

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 speech first.

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:

  1. tts
  2. the active agents.entries.*.tts
  3. channel override, provided the channel supports channels.<channel>.tts
  4. account override, provided the channel passes channels.<channel>.accounts.<id>.tts
  5. local /tts preferences for this host
  6. 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:

  1. /tts persona <id> local preference, when present.
  2. tts.persona, when present.
  3. No persona.

Provider selection follows an explicit-first approach:

  1. Direct overrides (CLI, gateway, Talk, allowed TTS directives).
  2. /tts provider <id> local preference.
  3. The active persona's provider.
  4. tts.provider.
  5. Registry auto-select.

For each provider attempt, OpenClaw merges configs in this sequence:

  1. tts.providers.<id>
  2. tts.personas.<persona>.providers.<id>
  3. Trusted request overrides
  4. 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:

PolicyBehavior
preserve-personaDefault. Provider-neutral prompt fields remain available; the provider can use or disregard them.
provider-defaultsPersona is left out of prompt preparation for that attempt; the provider falls back to its neutral defaults while other providers remain candidates.
failThat 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; requires allowProvider: true)
  • speakerVoice / speakerVoiceId (legacy aliases: voice, voiceName, voice_name, google_voice, voiceId)
  • model / google_model
  • stability, similarityBoost, style, speed, useSpeakerBoost
  • vol / 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.text or native command registration must be active.

Behavior notes:

  • /tts on saves the local TTS preference to always; /tts off saves it to off.
  • /tts chat on|off|default applies a session-scoped auto-TTS override for the current chat.
  • /tts persona <id> saves the local persona preference; /tts persona off removes it.
  • /tts latest pulls 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 audio produces 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.
  • limit and summary are kept in local prefs, not the main config.
  • /tts status provides fallback diagnostics for the latest attempt: Fallback: <primary> -> <used>, Attempts: ..., and per-attempt detail (provider:outcome(reasonCode) latency).
  • /status displays 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 fieldEffect
autoPer-host automatic TTS override (always, off, …)
providerPer-host primary provider override
personaPer-host persona override
maxLengthSummary/truncation limit (default 1500 characters, /tts limit range 100, 4096)
summarizeSummary 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.

TargetFormat
Feishu / Matrix / Telegram / WhatsAppVoice-note replies favor Opus (opus_48000_64 from ElevenLabs, opus from OpenAI). 48 kHz / 64 kbps balances clarity and size.
Other channelsMP3 (mp3_44100_128 from ElevenLabs, mp3 from OpenAI). 44.1 kHz / 128 kbps is the default balance for speech.
Talk / telephonyProvider-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 Baileys audio payload with ptt: true and audio/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-hd model, 32 kHz sample rate) for normal audio attachments; transcoded to 48 kHz Opus with ffmpeg for channel-advertised voice-note targets.
  • Xiaomi MiMo: MP3 by default, or WAV when configured; transcoded to 48 kHz Opus with ffmpeg for 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 with ffmpeg.
  • 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_8000 at 8 kHz for telephony.
  • Inworld: MP3 for normal audio attachments, native OGG_OPUS for voice-note targets, and raw PCM at 22050 Hz for Talk/telephony.
  • xAI: MP3 by default; audio-file synthesis may use mp3, wav, pcm, mulaw, or alaw for both buffered and streaming output. Voice-note targets use MP3 for streaming and buffered fallback because xAI's pcm, mulaw, and alaw outputs are headerless raw audio. Buffered synthesis uses xAI's batch REST /v1/tts endpoint; textToSpeechStream uses native wss://api.x.ai/v1/tts. This is not the realtime voice contract. Native Opus voice-note format is not supported.
  • Microsoft: uses microsoft.outputFormat (default audio-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 sendVoice accepts 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-CN locale) if the reply text is CJK-dominant.

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 (or agents.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 maxLength characters, and that summary is then synthesized.
  • Summary off, summarization failing, or no API key for the summary model: the text is cut down to maxLength characters 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. With inbound, audio is generated only after an inbound voice message arrives; with tagged, synthesis happens only when the reply carries [[tts:...]] directives or a [[tts:text]] block.

  • enabled (boolean), Legacy toggle. openclaw doctor --fix migrates this setting to auto.

  • 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 legacy provider: "edge" is converted to "microsoft" by openclaw doctor --fix.

  • persona (string), Active persona id sourced from personas. This is normalized to lowercase.

  • true, " type="object"> Stable spoken identity. Its fields are label, description, provider, fallbackPolicy, prompt, and providers.<provider>. Refer to Personas for details.

  • summaryModel (string), Low-cost model used for auto-summary, defaulting to agents.defaults.model.primary. It accepts provider/model or a configured model alias.

  • modelOverrides (object), Lets the model emit TTS directives. enabled comes with a default of true; allowProvider defaults to false.

  • true, " type="object"> Provider-specific settings indexed by speech provider id. Legacy direct blocks (tts.openai, .elevenlabs, .microsoft, .edge) are rewritten by openclaw doctor --fix; only tts.providers.<id> is committed.

  • maxTextLength (number, default: 4096), Upper limit on TTS input characters. Exceeding it causes /tts audio, tts.convert, and tts.speak to fail.

  • timeoutMs (number, default: 30000), Timeout for requests, measured in milliseconds. A per-call timeoutMs (agent tool, gateway) takes precedence when present; otherwise an explicitly set tts.timeoutMs overrides 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 variable AZURE_SPEECH_KEY, AZURE_SPEECH_API_KEY, or SPEECH_KEY.

  • region (string), Specifies the Azure Speech region, for example eastus. The environment variables AZURE_SPEECH_REGION or SPEECH_REGION can supply this value.

  • endpoint (string), An optional override for the Azure Speech endpoint, also known by the alias baseUrl.

  • speakerVoice (string), The ShortName of the Azure voice, defaulting to en-US-JennyNeural. The legacy alias voice is also accepted.

  • lang (string), Language code used in SSML, with en-US as the default.

  • outputFormat (string), The Azure X-Microsoft-OutputFormat applied to standard audio output, defaulting to audio-24khz-48kbitrate-mono-mp3.

  • voiceNoteOutputFormat (string), The Azure X-Microsoft-OutputFormat designated for voice-note output, defaulting to ogg-24khz-16bit-mono-opus.

ElevenLabs

  • apiKey (string), If not provided, this falls back to ELEVENLABS_API_KEY or XI_API_KEY.

  • model (string), Identifies the model, defaulting to eleven_multilingual_v2. The older identifiers eleven_turbo_v2_5 and eleven_turbo_v2 are converted to the corresponding flash model.

  • speakerVoiceId (string), The voice id for ElevenLabs, defaulting to pMsXgVXv3BLzUgSXRplE. The legacy alias voiceId works as well.

  • voiceSettings (object), Contains stability, similarityBoost, and style, each a 0..1 with defaults 0.5/0.75/0, plus useSpeakerBoost (a true|false, default true) and speed (a 0.5..2.0, default 1.0).

  • applyTextNormalization (auto" | "on" | "off), Controls how text normalization is handled.

  • languageCode (string), A two-letter ISO 639-1 code, such as en or de.

  • seed (number), An integer 0..4294967295 used 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_KEY or GOOGLE_API_KEY is used as the fallback. If it is not set, TTS may reuse models.providers.google.apiKey before checking the environment variables.

  • model (string), The Gemini TTS model to use. Falls back to gemini-3.1-flash-tts-preview if not specified.

  • speakerVoice (string), Name of the prebuilt Gemini voice. Defaults to Kore. 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), Assign audio-profile-v1 to 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 is https://generativelanguage.googleapis.com.

    Gradium

  • apiKey (string), Set through the environment variable GRADIUM_API_KEY.

  • baseUrl (string), HTTPS URL for the Gradium API, hosted on api.gradium.ai. Defaults to https://api.gradium.ai.

  • speakerVoiceId (string), Defaults to Emma (YTpq7expH9539ERJ). Former alias: voiceId.

    Inworld

    Inworld primary

  • apiKey (string), Configured via the environment variable INWORLD_API_KEY.

  • baseUrl (string), Defaults to https://api.inworld.ai.

  • modelId (string), Defaults to inworld-tts-1.5-max. Other options: inworld-tts-1.5-mini, inworld-tts-1-max, inworld-tts-1.

  • speakerVoiceId (string), Defaults to Sarah. Former alias: voiceId.

  • temperature (number), Sampling temperature, set to 0..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 to mp3 for audio attachments.

  • timeoutMs (number), Timeout for the command, measured in milliseconds. Defaults to 120000.

  • 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 example en-US-MichelleNeural. Former alias: voice. When the default English voice is active and the reply text is mostly CJK, OpenClaw automatically switches to zh-CN-XiaoxiaoNeural.

  • lang (string), Language code, such as en-US.

  • outputFormat (string), Specifies the Microsoft output format. The default is audio-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. Execute openclaw doctor --fix to migrate stored configuration to providers.microsoft.

    MiniMax

    • apiKey (string), Uses MINIMAX_API_KEY as a fallback. Token Plan authentication through MINIMAX_OAUTH_TOKEN, MINIMAX_CODE_PLAN_KEY, or MINIMAX_CODING_API_KEY.

    • baseUrl (string), Defaults to https://api.minimax.io. Environment variable: MINIMAX_API_HOST.

    • model (string), Defaults to speech-2.8-hd. Environment variable: MINIMAX_TTS_MODEL.

    • speakerVoiceId (string), Defaults to English_expressive_narrator. Environment variable: MINIMAX_TTS_VOICE_ID. Deprecated alias: voiceId.

    • speed (number), 0.5..2.0. Defaults to 1.0.

    • vol (number), (0, 10]. Defaults to 1.0.

    • pitch (number), Must be an integer -12..12. Defaults to 0. Fractional parts get cut off before the request is sent.

    OpenAI

    • apiKey (string), Falls back to OPENAI_API_KEY.

    • model (string), Identifier for the OpenAI TTS model. Defaults to gpt-4o-mini-tts.

    • speakerVoice (string), Voice selection, such as alloy or cedar. Defaults to coral. Deprecated alias: voice.

    • instructions (string), Explicit value for the OpenAI instructions field. 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. Use wav with compatible local endpoints that skip compressed audio encoding.

    • extraBody / extra_body (true), ">Additional JSON fields merged into /audio/speech request bodies after the generated OpenAI TTS fields. Suitable for OpenAI-compatible services like Kokoro that need provider-specific keys such as lang; unsafe prototype keys get ignored.

    • baseUrl (string), Overrides the OpenAI TTS endpoint. Lookup order: config, then OPENAI_TTS_BASE_URL, then https://api.openai.com/v1. Non-default values mark the endpoint as OpenAI-compatible, so custom model and voice names are allowed, and speed no longer enforces its 0.25..4.0 range check.

    OpenRouter

  • apiKey (string), Set via environment variable OPENROUTER_API_KEY. models.providers.openrouter.apiKey may be reused.

  • baseUrl (string), Falls back to https://openrouter.ai/api/v1. The older https://openrouter.ai/v1 form gets normalized.

  • model (string), Defaults to hexgrad/kokoro-82m. Also known as modelId.

  • speakerVoice (string), Defaults to af_alloy. Older names: voice and voiceId.

  • responseFormat (mp3" | "pcm), Defaults to mp3.

  • speed (number), Speed override passed straight to the provider.

    Volcengine (BytePlus Seed Speech)

  • apiKey (string), Read from VOLCENGINE_TTS_API_KEY or BYTEPLUS_SEED_SPEECH_API_KEY.

  • resourceId (string), Defaults to seed-tts-1.0. Environment variable: VOLCENGINE_TTS_RESOURCE_ID. Pick seed-tts-2.0 if your project carries a TTS 2.0 entitlement.

  • appKey (string), Header carrying the app key. Defaults to aGjiRDfUWi. 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 to en_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 to volcano_tts).

    xAI

  • apiKey (string), Read from environment variable XAI_API_KEY.

  • baseUrl (string), Defaults to https://api.x.ai/v1. Environment variable: XAI_BASE_URL.

  • speakerVoiceId (string), Defaults to eve. When authentication is on, openclaw infer tts voices --provider xai pulls the current built-in catalog; without it, offline fallbacks are listed: ara, eve, leo, rex, and sal. 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 or auto. Defaults to en.

  • responseFormat (mp3" | "wav" | "pcm" | "mulaw" | "alaw), Falls back to mp3.

  • speed (number), Provider-specific speed adjustment, 0.7..1.5.

    Xiaomi MiMo

  • apiKey (string), Environment variable: XIAOMI_API_KEY.

  • baseUrl (string), Falls back to https://api.xiaomimimo.com/v1. Environment variable: XIAOMI_BASE_URL.

  • model (string), Falls back to mimo-v2.5-tts. Environment variable: XIAOMI_TTS_MODEL. Additionally accepts mimo-v2.5-tts-voicedesign.

  • speakerVoice (string), Falls back to mimo_default for models using preset voices. Environment variable: XIAOMI_TTS_VOICE. Older alias: voice. Excluded from requests for mimo-v2.5-tts-voicedesign.

  • format (mp3" | "wav), Falls back to mp3. Environment variable: XIAOMI_TTS_FORMAT.

  • style (string), Optional style instruction in natural language, transmitted as the user message; not read aloud. For mimo-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

MethodPurpose
tts.statusRetrieve current TTS state and last attempt.
tts.enableSet local auto preference to always.
tts.disableSet local auto preference to off.
tts.convertOne-off text to audio.
tts.setProviderSet local provider preference.
tts.personasShow configured personas and the active one.
tts.setPersonaSet local persona preference.
tts.providersShow configured providers and status.
5,754 words · updated Aug 8, 2026