OpenRouter 文字转语音 API 五分钟教程:一个端点调用全部 TTS 模型
一句话结论
OpenRouter 的文字转语音走 OpenAI 兼容端点 POST /api/v1/audio/speech:发送文本、模型名和该模型支持的语音标识,即可拿到音频字节流。一个 API key 复用多家供应商的 TTS 模型,请求结构完全一致——换模型只改 model 和 voice 两行,端点、鉴权、响应处理全部不动。

准备工作
在 OpenRouter 设置页创建 API key 并存入环境变量,避免写进源码。macOS 或 Linux 当前终端会话:
export OPENROUTER_API_KEY="your-api-key"
所有示例的 base URL 都是 https://openrouter.ai/api/v1,鉴权用 Authorization Bearer 头。
请求字段速览
端点接受两个必填字段加一个事实必填的语音项:
-
model:选择语音模型
-
input:要转成语音的文本
-
voice:该模型支持的语音。仅当供应商文档写明有默认语音时才可省略,实践中当作必填处理
response_format 和 speed 是可选字段。但显式设置输出格式能让响应更可预期——省略时端点默认返回 PCM,而 Mistral Voxtral Mini TTS 只接受 MP3;speed 只对支持变速的模型生效。
成功请求返回原始音频字节,失败请求返回 JSON。写文件前必须校验响应。
第一个 MP3:cURL 版
用 Mistral Voxtral Mini TTS 和它的 en_paul_neutral 语音,把返回字节直接存进 output.mp3:
curl --silent \
--show-error \
--fail-with-body \
--request POST \
--url https://openrouter.ai/api/v1/audio/speech \
--header "Authorization: Bearer $OPENROUTER_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"model": "mistralai/voxtral-mini-tts-2603",
"input": "OpenRouter turns this text into speech through one API endpoint.",
"voice": "en_paul_neutral",
"response_format": "mp3"
}' \
--dump-header output.headers \
--output output.mp3
几个关键旗标:—fail-with-body 让 cURL 在 4xx/5xx 时以错误退出;因为设了 —output,错误 JSON 体会写进 output.mp3 而不是终端——命令失败时用 cat output.mp3 读错误信息,重试前删掉该文件,别把 JSON 当音频播。—dump-header 存响应头,用于确认 content type 和记录生成 ID。
播放前确认文件存在且有数据:macOS 用 afplay output.mp3,Linux 用 ffplay。
Python 版:校验后再落盘
先装 requests(没有的话):python -m pip install requests。这个例子检查 HTTP 状态并确认返回的是 MP3 数据再保存:
import os
from pathlib import Path
import requests
response = requests.post(
"https://openrouter.ai/api/v1/audio/speech",
headers={
"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}",
"Content-Type": "application/json",
},
json={
"model": "mistralai/voxtral-mini-tts-2603",
"input": (
"OpenRouter turns this text into speech through one API endpoint."
),
"voice": "en_paul_neutral",
"response_format": "mp3",
},
timeout=60,
)
response.raise_for_status()
content_type = response.headers.get("Content-Type", "").split(";")[0]
if content_type != "audio/mpeg":
raise RuntimeError(f"Expected audio/mpeg, received {content_type}")
Path("output.mp3").write_bytes(response.content)
generation_id = response.headers.get("X-Generation-Id")
print(f"Saved output.mp3. Generation ID: {generation_id}")
raise_for_status 在 4xx/5xx 时抛异常,防止把错误体存成音频;content-type 检查确认响应真的是音频再写文件。记录 X-Generation-Id 响应头,追踪请求或找支持时用得上。
OpenAI SDK 流式版
端点遵循 OpenAI Audio Speech API 形状,把 OpenAI 客户端指到 OpenRouter base URL 即可流式落盘:
import os
from pathlib import Path
from openai import OpenAI
client = OpenAI(
api_key=os.environ["OPENROUTER_API_KEY"],
base_url="https://openrouter.ai/api/v1",
)
with client.audio.speech.with_streaming_response.create(
model="mistralai/voxtral-mini-tts-2603",
voice="en_paul_neutral",
input="OpenRouter can stream this response into an audio file.",
response_format="mp3",
) as response:
response.stream_to_file(Path("output.mp3"))
这种写法增量读取响应边存文件;渐进播放需要支持缓冲分块的播放器。
JavaScript 版
JS 用 arrayBuffer 读同一响应,同样先查状态和 content type 再建文件:
import { writeFile } from "node:fs/promises";
const response = await fetch(
"https://openrouter.ai/api/v1/audio/speech",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "mistralai/voxtral-mini-tts-2603",
input: "OpenRouter returns audio bytes to JavaScript.",
voice: "en_paul_neutral",
response_format: "mp3",
}),
},
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const contentType = response.headers.get("content-type")?.split(";")[0];
if (contentType !== "audio/mpeg") {
await response.body?.cancel();
throw new Error(`Expected audio/mpeg, received ${contentType}`);
}
await writeFile("output.mp3", Buffer.from(await response.arrayBuffer()));
console.log(response.headers.get("x-generation-id"));
格式选择:MP3 文件小、标准播放器通吃;PCM 免压缩、兼容的实时流水线里延迟更低。MP3 返回 audio/mpeg,PCM 返回 audio/pcm(可带 rate 和 channels 参数),可用格式取决于所选模型——Mistral Voxtral Mini TTS 只收 MP3,要 PCM 会返回 400。播原始 PCM 需要正确的音频参数,改扩展名为 .mp3 不会转换格式。
换模型换语音:必须成对改
语音标识属于特定模型。同模型内换语音只改一行,例如 Grok Voice TTS 1.0 内置五个语音 eve/ara/rex/sal/leo,从 eve 换 ara 只动 voice 那行。
跨供应商换模型时,model 和 voice 必须一起改——每家供应商的模型 ID 和语音 ID 各自独立,而端点、鉴权、输入和响应检查保持不变:
- "model": "mistralai/voxtral-mini-tts-2603",
- "voice": "en_paul_neutral"
+ "model": "x-ai/grok-voice-tts-1.0",
+ "voice": "eve"
发请求前到所选模型页确认两个值——模型可用性和语音目录会变。查当前 TTS 模型列表:
curl "https://openrouter.ai/api/v1/models?output_modalities=speech"
当前代表模型:
-
mistralai/voxtral-mini-tts-2603:en_paul_neutral,经 OpenRouter 语音端点输出 MP3
-
x-ai/grok-voice-tts-1.0:eve/ara/rex/sal/leo,五语音覆盖 20+ 语言
-
microsoft/mai-voice-2:en-US-Harper:MAI-Voice-2,支持 speed、Azure 风格与风格强度
MAI-Voice-2 接受 Azure 语音名,speed 文档范围 0.5-2.0,还支持 Azure 表达选项,通过 provider.options.azure 传 style 和 styledegree。这些控制是供应商专属的:不支持的供应商会忽略 speed,风格依赖所选语音。截至 2026 年 9 月,目录里没有 OpenAI 语音模型,依赖供应商专属字段前先查当前模型列表。
第一次跑通后先做三件小事:播放生成的 MP3 确认语音自然度、查看响应头里的 X-Generation-Id 并记下来、用 ls -lh 看文件大小是否合理(几秒语音通常几十 KB)。这三步能在接入早期就发现”存了 JSON 当音频”或”空文件”两类最常见事故。
长文本直接整段发给 TTS 模型容易超时或截断,正确做法是按句子或段落边界切分后逐段请求,再按顺序拼接音频。切分点选句号、问号或换行处,避免把一个词从中间切开;拼接工具要与输出格式匹配,MP3 直接二进制追加即可,PCM 则要按采样率处理。
按字符计费意味着成本与文本长度线性相关:同一段文案反复生成会线性烧钱,生成记录里保留模型、语音、格式和字符数,账单对得上。预算敏感的场景先用最短可接受文本测价,再放量。
如果团队已有 OpenAI SDK 代码,迁移成本几乎为零:只改 base_url 和 api_key 两个值,其余请求结构、流式接口、错误处理全部沿用。这也是统一端点设计的直接好处——供应商锁定被降到最低,换语音模型不再重写传输层。
生产化清单
长文本按句子或段落边界切分、逐段请求、用格式感知的工具拼接——首段返回更快、整体更可靠。每一段都执行同样的响应检查:
-
非 2xx 状态码立即停止
-
确认 Content-Type 与请求格式一致
-
拒绝空响应
-
把 X-Generation-Id 与模型、语音、格式、应用请求 ID 一起记录
-
重试前先分类响应:永久性请求失败不进退避循环
重试策略:429/502/503/524/529 可以重试(限流、供应商错误、临时不可用、超时、过载都可能自愈),带 Retry-After 头就按它来,否则用封顶指数退避、少量次数封顶。400/401/402 不要重试——先改请求、凭据或充值。
计费按输入文本字符数计,各模型各供应商价格不同,估算生产成本前查当前模型页或 Models API。
教程覆盖的完整链路按官方结构走:认证、合成、响应校验、流式、换模型换语音五步。每一步的代码都可以直接复制运行,唯一的前置条件是一个有效的 OpenRouter API key。
供应商专属选项的传递位置在 provider.options 下,按供应商名分组。Azure 风格与风格强度只对 MAI-Voice-2 生效,speed 也仅部分模型支持——把供应商选项写在匹配的模型配置旁边,避免”传了没效果”的排查时间。
对语音质量有要求时,先用同一段文本在两三个模型间做对比试听,再固定生产配置。语音自然度是主观指标,官方示例语音(如 en_paul_neutral、eve)只是起点,不是终点。
配合语音转文字接口,这套端点还能组成完整的音频工作流:先用 TTS 生成内容音频,再按需转写归档。两条链路共用同一个 key 和同一套鉴权,运维上只维护一套凭据。
常见故障排查
-
MP3 里有 JSON:API 返回了错误但程序没查状态就存了 body。写文件前调 raise_for_status 或检查状态码。
-
音频文件为空或损坏:请求没返回音频数据,或存错了格式。查响应大小和 Content-Type;audio/mpeg 存 MP3,audio/pcm 按原始 PCM 用正确播放参数处理。
-
语音被拒:语音标识因模型而异。查所选模型页、发送它支持的语音;每次换模型都重查。
-
供应商选项没效果:供应商控制只到达对应供应商。OpenAI 指令放 provider.options.openai,Azure 风格放 provider.options.azure;部分供应商静默忽略不支持的 speed 值。
原文信息
- 作者:OpenRouter 官方博客
- 发布时间:2026-09-11 原文地址:
楼主辛苦了,内容很有参考价值。
收藏了,以后慢慢研究。
这篇文章分析得很透彻,收藏了!
整理得太全面了,省了我不少时间。
写得挺用心的,支持一下。
讲解得很细致,新手也能看懂。
整理得太全面了,省了我不少时间。
内容翔实,正好需要,先收藏再看。
很有价值的分享,感谢整理。