openrouter-tts

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

OpenRouter Text-to-Speech

OpenRouter 文本转语音

Synthesize speech via
POST /api/v1/audio/speech
using
curl
. The endpoint is OpenAI-compatible, so the OpenAI SDKs work by pointing them at
https://openrouter.ai/api/v1
. Requires
OPENROUTER_API_KEY
(get one at https://openrouter.ai/keys). If unset, stop and ask.
通过
curl
调用
POST /api/v1/audio/speech
接口合成语音。该接口与OpenAI兼容,因此只需将OpenAI SDK的请求地址指向
https://openrouter.ai/api/v1
即可使用。需要
OPENROUTER_API_KEY
(可在https://openrouter.ai/keys获取)。如果未设置该密钥,请停止操作并询问用户。

One call, raw bytes back

单次调用,返回原始字节

The response body is the audio bytes — write them to a file with the extension matching
response_format
. It is not JSON; error responses are, so only try to parse JSON when the status is non-200.
Two response headers are worth keeping:
  • Content-Type
    audio/mpeg
    for mp3; for pcm it includes the sample rate and channel count, e.g.
    audio/pcm;rate=24000;channels=1
    . Parse these parameters if you need to wrap the raw bytes into a WAV container.
  • X-Generation-Id
    — the generation ID (format
    gen-tts-<timestamp>-<suffix>
    ), useful for tracking, debugging, and cost lookups.
响应体为音频字节——请将其写入扩展名为
response_format
指定格式的文件。响应不是JSON格式;只有错误响应是JSON,因此仅当状态码非200时才尝试解析JSON。
有两个响应头值得关注:
  • Content-Type
    ——mp3格式对应
    audio/mpeg
    ;pcm格式会包含采样率和声道数,例如
    audio/pcm;rate=24000;channels=1
    。如果需要将原始字节封装为WAV容器,请解析这些参数。
  • X-Generation-Id
    ——生成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
supported_voices
array with the exact voice IDs that provider accepts.
bash
undefined
通过输出模态过滤模型接口,列出语音模型。每个模型都带有一个
supported_voices
数组,包含提供商接受的精确语音ID。
bash
undefined

Models + voices in one shot

一次性获取模型与语音

curl -sS "https://openrouter.ai/api/v1/models?output_modalities=speech"
| 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}'

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[]'

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[]'

语音由提供商命名:OpenAI使用短名称(如`alloy`、`nova`),Voxtral采用语言+角色+情绪的编码方式(如`en_paul_happy`),Kokoro则以语言/性别作为前缀(如`af_bella` = 美国女性Bella)。

Parameters

参数

FieldRequiredNotes
model
yesTTS model slug (e.g.
openai/gpt-4o-mini-tts-2025-12-15
,
mistralai/voxtral-mini-tts-2603
).
input
yesThe text to synthesize.
voice
yesVoice identifier. Look up the exact set for your model in
supported_voices
on the models endpoint (see the discovery section above). Voices are provider-namespaced — e.g.
alloy
is an OpenAI voice and will not work on Voxtral or Kokoro.
response_format
no
mp3
or
pcm
. Default is
pcm
. Set this explicitly — the default is usually not what a user wants to save.
speed
noPlayback multiplier (e.g.
1.25
). Honored by OpenAI TTS. Other providers may accept and ignore it, or reject unknown fields — check the provider's behavior if it matters.
provider
noProvider passthrough — see below.
字段是否必填说明
model
TTS模型标识(例如
openai/gpt-4o-mini-tts-2025-12-15
mistralai/voxtral-mini-tts-2603
)。
input
需要合成的文本内容。
voice
语音标识符。请通过模型接口的
supported_voices
字段查询对应模型支持的语音集合(见上文发现部分)。语音由提供商命名——例如
alloy
是OpenAI的语音,无法在Voxtral或Kokoro中使用。
response_format
可选值为
mp3
pcm
,默认值为
pcm
请显式设置此参数——默认值通常不是用户想要保存的格式。
speed
播放速度倍数(例如
1.25
)。OpenAI TTS支持此参数。其他提供商可能接受并忽略该参数,或拒绝未知字段——如果对此有要求,请查看提供商的具体说明。
provider
提供商透传参数——详见下文。

Picking a format

格式选择

  • mp3
    (
    audio/mpeg
    ) — compressed, ready to play in any audio app. Default choice for files the user will listen to or share.
  • pcm
    (
    audio/pcm;rate=<rate>;channels=<n>
    ) — uncompressed raw samples. The response
    Content-Type
    carries the sample rate and channel count (e.g.
    rate=24000;channels=1
    for 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.
Match the file extension to the format — saving pcm bytes as
.mp3
produces a file no player will open, and this is the most common cause of "empty/corrupted audio" reports.
  • mp3
    audio/mpeg
    )——压缩格式,可在任意音频应用中播放。是用户收听或分享文件的默认选择。
  • pcm
    audio/pcm;rate=<rate>;channels=<n>
    )——未压缩的原始采样数据。响应的
    Content-Type
    会包含采样率和声道数(例如OpenAI TTS的
    rate=24000;channels=1
    ),如果需要将字节封装为WAV容器,需用到这些参数。适合低延迟的实时流处理管道,但无法直接播放。仅当用户明确需要原始音频或要传入流系统时选择此格式。
请确保文件扩展名与格式匹配——将pcm字节保存为
.mp3
会生成无法播放的文件,这是"音频为空/损坏"报告的最常见原因。

Provider-specific options

提供商专属选项

Provider passthrough goes under
provider.options.<slug>
and is only forwarded when that provider handles the request. The most useful one is OpenAI's
instructions
, which steers tone, accent, pacing, or emotion without retraining:
json
{
  "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).
提供商透传参数需放在
provider.options.<slug>
下,仅当请求由该提供商处理时才会被转发。最实用的是OpenAI的
instructions
参数,无需重新训练即可调整语气、口音、语速或情绪:
json
{
  "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
/audio/speech
, 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.
python
undefined
由于该接口镜像了OpenAI的
/audio/speech
接口,因此只需更换基础URL即可使用OpenAI SDK。当用户已在Python/TypeScript项目中开发,且不想使用shell命令时,推荐此方式。
python
undefined

Python — 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):
  1. Split the text at sentence or paragraph boundaries — never mid-word.
  2. Synthesize each chunk with the same
    model
    +
    voice
    so prosody stays consistent.
  3. Concatenate the resulting audio files. For mp3,
    ffmpeg -i "concat:part1.mp3|part2.mp3" -c copy out.mp3
    works for simple cases; for mixed bitrates or tight seams, re-encode via
    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模型有单请求字符限制(通常为几千字符),且按输入字符数计费,因此拆分文本不会产生额外费用。对于长文本(章节、文章、脚本):
  1. 在句子或段落边界拆分文本——切勿在单词中间拆分。
  2. 使用相同的
    model
    voice
    合成每个片段,以保持语调一致。
  3. 拼接生成的音频文件。对于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
Content-Type
in the response headers:
audio/pcm
saved as
.mp3
will not play. Either re-request with
response_format: "mp3"
or save with the matching extension.
400 with
"Model X does not exist"
— the slug is wrong. Use the full dated slug from the models endpoint (
openai/gpt-4o-mini-tts-2025-12-15
, not
gpt-4o-mini-tts
).
400 with a
ZodError
— a required field is missing or the wrong type. The body looks like
{"success":false,"error":{"name":"ZodError","message":"[...]"}}
— the nested
message
JSON string names the bad path (e.g.
"path":["voice"]
).
speed
has no effect
— the provider probably doesn't support it. OpenAI TTS honors it; for other providers, check upstream docs before relying on it.
音频文件"为空"或无法播放——几乎都是格式/扩展名不匹配导致的。检查响应头中的
Content-Type
:将
audio/pcm
格式保存为
.mp3
将无法播放。请重新请求并设置
response_format: "mp3"
,或使用匹配的扩展名保存。
400错误提示
"Model X does not exist"
——模型标识错误。请使用模型接口中的完整带日期标识(如
openai/gpt-4o-mini-tts-2025-12-15
,而非
gpt-4o-mini-tts
)。
400错误提示
ZodError
——缺少必填字段或字段类型错误。响应体格式为
{"success":false,"error":{"name":"ZodError","message":"[...]"}}
——嵌套的
message
会指出错误字段(例如
"path":["voice"]
)。
speed
参数无效果
——提供商可能不支持该参数。OpenAI TTS支持此参数;对于其他提供商,请在依赖该参数前查看上游文档。

References

参考资料