openrouter-tts
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOpenRouter Text-to-Speech
OpenRouter 文本转语音
Synthesize speech via using . The endpoint is OpenAI-compatible, so the OpenAI SDKs work by pointing them at . Requires (get one at https://openrouter.ai/keys). If unset, stop and ask.
POST /api/v1/audio/speechcurlhttps://openrouter.ai/api/v1OPENROUTER_API_KEY通过调用接口合成语音。该接口与OpenAI兼容,因此只需将OpenAI SDK的请求地址指向即可使用。需要(可在https://openrouter.ai/keys获取)。如果未设置该密钥,请停止操作并询问用户。
curlPOST /api/v1/audio/speechhttps://openrouter.ai/api/v1OPENROUTER_API_KEYOne call, raw bytes back
单次调用,返回原始字节
The response body is the audio bytes — write them to a file with the extension matching . It is not JSON; error responses are, so only try to parse JSON when the status is non-200.
response_formatTwo response headers are worth keeping:
- —
Content-Typefor mp3; for pcm it includes the sample rate and channel count, e.g.audio/mpeg. Parse these parameters if you need to wrap the raw bytes into a WAV container.audio/pcm;rate=24000;channels=1 - — the generation ID (format
X-Generation-Id), useful for tracking, debugging, and cost lookups.gen-tts-<timestamp>-<suffix>
响应体为音频字节——请将其写入扩展名为指定格式的文件。响应不是JSON格式;只有错误响应是JSON,因此仅当状态码非200时才尝试解析JSON。
response_format有两个响应头值得关注:
- ——mp3格式对应
Content-Type;pcm格式会包含采样率和声道数,例如audio/mpeg。如果需要将原始字节封装为WAV容器,请解析这些参数。audio/pcm;rate=24000;channels=1 - ——生成ID(格式为
X-Generation-Id),可用于跟踪、调试和成本查询。gen-tts-<timestamp>-<suffix>
Drop-in workflow
即插即用工作流
bash
#!/usr/bin/env bash
set -euo pipefail
MODEL="openai/gpt-4o-mini-tts-2025-12-15"
VOICE="alloy"
FORMAT="mp3" # mp3 or pcm
INPUT="Hello! This is a text-to-speech test."
OUTPUT="speech-$(date +%Y%m%d-%H%M%S).${FORMAT}"
HEADERS=$(mktemp)
payload=$(jq -n --arg model "$MODEL" --arg input "$INPUT" \
--arg voice "$VOICE" --arg fmt "$FORMAT" \
'{model: $model, input: $input, voice: $voice, response_format: $fmt}')
http_code=$(curl -sS -X POST https://openrouter.ai/api/v1/audio/speech \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-D "$HEADERS" \
--output "$OUTPUT" \
-w '%{http_code}' \
-d "$payload")
if [[ "$http_code" != "200" ]]; then
echo "TTS failed (HTTP $http_code):" >&2
cat "$OUTPUT" >&2 # error body is JSON, not audio
rm -f "$OUTPUT" "$HEADERS"
exit 1
fi
gen_id=$(grep -i '^x-generation-id:' "$HEADERS" | awk '{print $2}' | tr -d '\r')
rm -f "$HEADERS"
echo "Saved $(realpath "$OUTPUT") (generation_id=${gen_id:-unknown})"bash
#!/usr/bin/env bash
set -euo pipefail
MODEL="openai/gpt-4o-mini-tts-2025-12-15"
VOICE="alloy"
FORMAT="mp3" # mp3 or pcm
INPUT="Hello! This is a text-to-speech test."
OUTPUT="speech-$(date +%Y%m%d-%H%M%S).${FORMAT}"
HEADERS=$(mktemp)
payload=$(jq -n --arg model "$MODEL" --arg input "$INPUT" \
--arg voice "$VOICE" --arg fmt "$FORMAT" \
'{model: $model, input: $input, voice: $voice, response_format: $fmt}')
http_code=$(curl -sS -X POST https://openrouter.ai/api/v1/audio/speech \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-D "$HEADERS" \
--output "$OUTPUT" \
-w '%{http_code}' \
-d "$payload")
if [[ "$http_code" != "200" ]]; then
echo "TTS failed (HTTP $http_code):" >&2
cat "$OUTPUT" >&2 # error body is JSON, not audio
rm -f "$OUTPUT" "$HEADERS"
exit 1
fi
gen_id=$(grep -i '^x-generation-id:' "$HEADERS" | awk '{print $2}' | tr -d '\r')
rm -f "$HEADERS"
echo "Saved $(realpath "$OUTPUT") (generation_id=${gen_id:-unknown})"Discovering TTS models and voices
发现TTS模型与语音
Filter the models endpoint by output modality to list speech models. Each model carries a array with the exact voice IDs that provider accepts.
supported_voicesbash
undefined通过输出模态过滤模型接口,列出语音模型。每个模型都带有一个数组,包含提供商接受的精确语音ID。
supported_voicesbash
undefinedModels + voices in one shot
一次性获取模型与语音
curl -sS "https://openrouter.ai/api/v1/models?output_modalities=speech"
| jq '.data[] | {id, name, supported_voices, pricing}'
| jq '.data[] | {id, name, supported_voices, pricing}'
curl -sS "https://openrouter.ai/api/v1/models?output_modalities=speech"
| jq '.data[] | {id, name, supported_voices, pricing}'
| jq '.data[] | {id, name, supported_voices, pricing}'
Just the voices for a specific model
获取特定模型的语音
curl -sS "https://openrouter.ai/api/v1/models?output_modalities=speech"
| jq -r '.data[] | select(.id=="openai/gpt-4o-mini-tts-2025-12-15") | .supported_voices[]'
| jq -r '.data[] | select(.id=="openai/gpt-4o-mini-tts-2025-12-15") | .supported_voices[]'
Voices are provider-namespaced: OpenAI uses short names (`alloy`, `nova`), Voxtral encodes language + persona + emotion (`en_paul_happy`), Kokoro prefixes with language/gender (`af_bella` = American female Bella).curl -sS "https://openrouter.ai/api/v1/models?output_modalities=speech"
| jq -r '.data[] | select(.id=="openai/gpt-4o-mini-tts-2025-12-15") | .supported_voices[]'
| jq -r '.data[] | select(.id=="openai/gpt-4o-mini-tts-2025-12-15") | .supported_voices[]'
语音由提供商命名:OpenAI使用短名称(如`alloy`、`nova`),Voxtral采用语言+角色+情绪的编码方式(如`en_paul_happy`),Kokoro则以语言/性别作为前缀(如`af_bella` = 美国女性Bella)。Parameters
参数
| Field | Required | Notes |
|---|---|---|
| yes | TTS model slug (e.g. |
| yes | The text to synthesize. |
| yes | Voice identifier. Look up the exact set for your model in |
| no | |
| no | Playback multiplier (e.g. |
| no | Provider passthrough — see below. |
| 字段 | 是否必填 | 说明 |
|---|---|---|
| 是 | TTS模型标识(例如 |
| 是 | 需要合成的文本内容。 |
| 是 | 语音标识符。请通过模型接口的 |
| 否 | 可选值为 |
| 否 | 播放速度倍数(例如 |
| 否 | 提供商透传参数——详见下文。 |
Picking a format
格式选择
- (
mp3) — compressed, ready to play in any audio app. Default choice for files the user will listen to or share.audio/mpeg - (
pcm) — uncompressed raw samples. The responseaudio/pcm;rate=<rate>;channels=<n>carries the sample rate and channel count (e.g.Content-Typefor OpenAI TTS), which you'll need if you wrap the bytes into a WAV container. Lower latency for real-time streaming pipelines, but not directly playable on its own. Pick this only when the user explicitly wants raw audio or is piping into a streaming system.rate=24000;channels=1
Match the file extension to the format — saving pcm bytes as produces a file no player will open, and this is the most common cause of "empty/corrupted audio" reports.
.mp3- (
mp3)——压缩格式,可在任意音频应用中播放。是用户收听或分享文件的默认选择。audio/mpeg - (
pcm)——未压缩的原始采样数据。响应的audio/pcm;rate=<rate>;channels=<n>会包含采样率和声道数(例如OpenAI TTS的Content-Type),如果需要将字节封装为WAV容器,需用到这些参数。适合低延迟的实时流处理管道,但无法直接播放。仅当用户明确需要原始音频或要传入流系统时选择此格式。rate=24000;channels=1
请确保文件扩展名与格式匹配——将pcm字节保存为会生成无法播放的文件,这是"音频为空/损坏"报告的最常见原因。
.mp3Provider-specific options
提供商专属选项
Provider passthrough goes under and is only forwarded when that provider handles the request. The most useful one is OpenAI's , which steers tone, accent, pacing, or emotion without retraining:
provider.options.<slug>instructionsjson
{
"model": "openai/gpt-4o-mini-tts-2025-12-15",
"input": "Welcome to the show.",
"voice": "alloy",
"response_format": "mp3",
"provider": {
"options": {
"openai": {
"instructions": "Speak in a warm, friendly tone with a slow pace."
}
}
}
}For other providers, check each provider's upstream docs for available passthrough keys — naming conventions vary (camelCase for OpenAI/Google, snake_case for most others).
提供商透传参数需放在下,仅当请求由该提供商处理时才会被转发。最实用的是OpenAI的参数,无需重新训练即可调整语气、口音、语速或情绪:
provider.options.<slug>instructionsjson
{
"model": "openai/gpt-4o-mini-tts-2025-12-15",
"input": "Welcome to the show.",
"voice": "alloy",
"response_format": "mp3",
"provider": {
"options": {
"openai": {
"instructions": "Speak in a warm, friendly tone with a slow pace."
}
}
}
}对于其他提供商,请查看各提供商的上游文档以了解可用的透传参数——命名规则各不相同(OpenAI/Google使用驼峰式,大多数其他提供商使用蛇形命名)。
OpenAI SDK compatibility
OpenAI SDK兼容性
Because the endpoint mirrors OpenAI's , both OpenAI SDKs work by swapping the base URL. Prefer this when the user is already in a Python/TypeScript project and doesn't want to shell out.
/audio/speechpython
undefined由于该接口镜像了OpenAI的接口,因此只需更换基础URL即可使用OpenAI SDK。当用户已在Python/TypeScript项目中开发,且不想使用shell命令时,推荐此方式。
/audio/speechpython
undefinedPython — streaming write to file
Python — 流式写入文件
import os
from openai import OpenAI
client = OpenAI(base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"])
with client.audio.speech.with_streaming_response.create(
model="openai/gpt-4o-mini-tts-2025-12-15",
input="The quick brown fox jumps over the lazy dog.",
voice="nova",
response_format="mp3",
) as response:
response.stream_to_file("output.mp3")
```typescript
// TypeScript — collect bytes, write once
import OpenAI from "openai";
import fs from "fs";
const client = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY!,
});
const response = await client.audio.speech.create({
model: "openai/gpt-4o-mini-tts-2025-12-15",
input: "The quick brown fox jumps over the lazy dog.",
voice: "nova",
response_format: "mp3",
});
await fs.promises.writeFile(
"output.mp3",
Buffer.from(await response.arrayBuffer()),
);import os
from openai import OpenAI
client = OpenAI(base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"])
with client.audio.speech.with_streaming_response.create(
model="openai/gpt-4o-mini-tts-2025-12-15",
input="The quick brown fox jumps over the lazy dog.",
voice="nova",
response_format="mp3",
) as response:
response.stream_to_file("output.mp3")
```typescript
// TypeScript — 收集字节后一次性写入
import OpenAI from "openai";
import fs from "fs";
const client = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY!,
});
const response = await client.audio.speech.create({
model: "openai/gpt-4o-mini-tts-2025-12-15",
input: "The quick brown fox jumps over the lazy dog.",
voice: "nova",
response_format: "mp3",
});
await fs.promises.writeFile(
"output.mp3",
Buffer.from(await response.arrayBuffer()),
);Long inputs
长文本输入
TTS models have per-request character limits (usually a few thousand characters) and are priced per character of input, so there's no penalty to splitting. For anything long (chapters, articles, scripts):
- Split the text at sentence or paragraph boundaries — never mid-word.
- Synthesize each chunk with the same +
modelso prosody stays consistent.voice - Concatenate the resulting audio files. For mp3, works for simple cases; for mixed bitrates or tight seams, re-encode via
ffmpeg -i "concat:part1.mp3|part2.mp3" -c copy out.mp3.ffmpeg -f concat -safe 0 -i list.txt output.mp3
Splitting also improves time-to-first-audio if you stream chunks to a user as they're generated.
TTS模型有单请求字符限制(通常为几千字符),且按输入字符数计费,因此拆分文本不会产生额外费用。对于长文本(章节、文章、脚本):
- 在句子或段落边界拆分文本——切勿在单词中间拆分。
- 使用相同的和
model合成每个片段,以保持语调一致。voice - 拼接生成的音频文件。对于mp3格式,简单场景下可使用;对于比特率不一致或需要无缝拼接的情况,可通过
ffmpeg -i "concat:part1.mp3|part2.mp3" -c copy out.mp3重新编码。ffmpeg -f concat -safe 0 -i list.txt output.mp3
拆分文本还能在生成片段时流式传输给用户,缩短首段音频的生成时间。
Troubleshooting
故障排查
"Empty" or unplayable audio file — almost always a format/extension mismatch. Check in the response headers: saved as will not play. Either re-request with or save with the matching extension.
Content-Typeaudio/pcm.mp3response_format: "mp3"400 with — the slug is wrong. Use the full dated slug from the models endpoint (, not ).
"Model X does not exist"openai/gpt-4o-mini-tts-2025-12-15gpt-4o-mini-tts400 with a — a required field is missing or the wrong type. The body looks like — the nested JSON string names the bad path (e.g. ).
ZodError{"success":false,"error":{"name":"ZodError","message":"[...]"}}message"path":["voice"]speed音频文件"为空"或无法播放——几乎都是格式/扩展名不匹配导致的。检查响应头中的:将格式保存为将无法播放。请重新请求并设置,或使用匹配的扩展名保存。
Content-Typeaudio/pcm.mp3response_format: "mp3"400错误提示——模型标识错误。请使用模型接口中的完整带日期标识(如,而非)。
"Model X does not exist"openai/gpt-4o-mini-tts-2025-12-15gpt-4o-mini-tts400错误提示——缺少必填字段或字段类型错误。响应体格式为——嵌套的会指出错误字段(例如)。
ZodError{"success":false,"error":{"name":"ZodError","message":"[...]"}}message"path":["voice"]speed