SHOAL 语音 API 接入说明
版本:2026-10-20。适用于应用后端、自动化脚本、剪辑工具和 AI 工作流。
请以本文列出的接口和能力为准。API Key 与网页账号共用积分和生成记录;不要把 Key 放进网页前端、公开仓库或分享的提示词中。
1. 三分钟完成第一次调用
- 登录网站,打开「API 接入」。
- 为应用创建一个 Key,立即复制并妥善保存。完整 Key 仅在创建成功时显示一次,之后只能查看尾号。
- 在声音库选好声音,复制声音详情中的 ID;也可以通过下方声音列表接口获取
reference_id。 - 将 Key 保存到应用服务器的环境变量
SHOAL_API_KEY。 - 调用
POST /v1/tts,把成功响应直接保存为音频。
- 服务地址:
https://peiyin520.top - API Base URL:
https://peiyin520.top/v1 - 鉴权:
Authorization: Bearer <你的 API Key> - 默认输出:MP3。
- 每次调用返回一个音频结果。
# Linux / macOS / 服务端终端。请把占位符换成自己的声音 ID。
export SHOAL_API_KEY='你的 API Key'
export SHOAL_VOICE_ID='声音 ID'
curl --fail-with-body --no-buffer --request POST \
'https://peiyin520.top/v1/tts' \
--header "Authorization: Bearer $SHOAL_API_KEY" \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: first-speech-20261020-001' \
--data "{\"text\":\"你好,欢迎使用语音创作。\",\"reference_id\":\"$SHOAL_VOICE_ID\",\"format\":\"mp3\"}" \
--output speech.mp3
不要在每次生成时固定复用示例里的幂等编号。一个新任务使用一个新编号;同一任务的重连和重试使用原编号。
2. 接口能力与范围
| 能力 | 当前支持情况 |
|---|---|
| 文本转语音 | 单说话人,JSON 或 MessagePack 请求 |
| HTTP 流式音频 | 支持,尽早返回音频字节 |
| 输出格式 | MP3、WAV、OPUS |
| 音量、语速、响度统一、文本归一化 | 支持 |
| 声音列表、声音详情 | 支持公共声音与当前账号的私人声音 |
| 后台任务、状态查询、重新获取音频 | 支持 |
| 幂等请求、积分查询 | 支持 |
| 多说话人、PCM、内联参考录音 | 当前不支持,接口明确报错 |
| WebSocket 实时文字输入、字级时间戳、ASR、模型训练或删除 | 当前 API 不提供 |
这是平台的语音合成接口,不能当作任意第三方 API 的透明转发地址。若 SDK 默认调用了本文未列出的路径,请关闭该功能或改用本文的 HTTP 示例。
3. 鉴权和 Key 管理
每次请求都在 HTTP Header 中传入:
Authorization: Bearer shoal_sk_你的完整Key
- 不接受在 URL 查询参数中传 Key。
- Key 仅能访问当前账号的语音能力、任务和积分概况,不能进入管理员接口、修改密码、充值或创建其他 Key。
- 每个账号最多保留 5 个有效 Key。不同应用建议使用不同 Key,方便识别和撤销。
- Key 遗失后不能找回原文;请撤销后新建。
- 撤销立即阻止新的调用;尚未执行的任务会停止。已经发送的任务请在网页生成记录核对。
- 不再需要的 Key 可点击「删除 Key」,确认后从列表移除。删除有效 Key 会同时使其失效;已撤销的 Key 也能删除。相关生成历史和积分流水保留,删除 Key 不会退还已扣积分。
- 账号停用或管理员关闭 API 后,该账号所有 Key 均不可继续调用。
- 接口面向服务端,不开放浏览器跨域调用。自己的网页应用应由后端持有 Key 并转发必要请求。
- 不要让 AI 把真实 Key 写入源码、日志、聊天记录或下载的文档。
4. 文本转语音:POST /v1/tts
请求头
| 请求头 | 要求 |
|---|---|
| Authorization | 必填,Bearer Key |
| Content-Type | application/json 或 application/msgpack |
| Idempotency-Key | 强烈建议提供;1–100 位字母、数字、点、下划线、冒号或短横线 |
主要请求字段
| 字段 | 类型与默认值 | 说明 |
|---|---|---|
| text | string,必填 | 非空;最多 6,000 UTF-8 字节,约 2,000 个汉字。不会自动截断 |
| reference_id | string,必填 | 单个声音 ID;可使用声音列表的 reference_id 或公共声音 ID |
| format | string,mp3 | mp3 / wav / opus |
| model | string,可省略 | 客户端兼容字段。新接入建议省略,或填写平台服务名 shoal-voice |
| prosody.speed | number,1 | 0.5–2,1 为正常速度 |
| prosody.volume | number,0 | -20–20 dB |
| prosody.normalize_loudness | boolean,true | 让输出响度更一致 |
| normalize | boolean,true | 对数字、日期、单位等进行朗读归一化 |
| temperature | number,0.7 | 0–1,调整表现的变化程度 |
| top_p | number,0.7 | 0–1,调整采样多样性 |
| streaming | boolean,true | true 尽早返回;false 等完整音频就绪后再返回 |
voice_id、voiceAssetId 可作为 reference_id 的别名;若同时传多个声音字段,它们必须相同。新接入请统一使用 reference_id。
可选音频与分段参数
| 字段 | 允许值 |
|---|---|
| chunk_length | 整数 100–300 |
| latency | low / normal / balanced |
| sample_rate | 8000 / 16000 / 22050 / 24000 / 32000 / 44100 / 48000 |
| mp3_bitrate | 64 / 128 / 192,单位 kbps |
| opus_bitrate | -1000 / 24000 / 32000 / 48000 / 64000,单位 bps;-1000 为自动 |
| language | 最多 35 位 BCP-47 风格语言标签,例如 zh-CN、ja、en;用于文本归一化 |
| max_new_tokens | 整数 1–4096 |
| repetition_penalty | 数值 1–2 |
| min_chunk_length | 整数 0–100 |
| condition_on_previous_chunks | boolean |
| early_stop_threshold | 数值 0–1 |
这些参数不保证每个声音都有相同表现。新接入先使用主要字段验证声音,再逐项调节。未列出的扩展参数会明确报错。为了兼容部分客户端的默认序列化,允许空 references、空 features 和 use_memory_cache 字段;它们不提供额外功能。
完整请求示例
{
"text": "[开心] 欢迎回来。[短停顿] 这是一段语音示例。",
"reference_id": "替换为声音ID",
"format": "mp3",
"prosody": {
"speed": 1.1,
"volume": 0,
"normalize_loudness": true
},
"normalize": true,
"temperature": 0.7,
"top_p": 0.7,
"streaming": true,
"latency": "balanced"
}
语气指令写在要影响的文字前。中文自然表达提示如 [开心]、[强调]、[短停顿] 可用于当前版本;效果随声音不同而变化。停顿是自然表达提示,不是精确时长的静音命令。不要使用 <break time="2s"/>;精确秒数停顿当前不支持。
成功返回
HTTP 200,响应体直接是音频字节,不是 JSON,也不是 Base64 字符串。
| 响应头 | 含义 |
|---|---|
| Content-Type | audio/mpeg、audio/wav 或 audio/ogg |
| X-Job-Id | 本次平台任务 ID,用于查询状态 |
| Idempotency-Key | 本次实际使用的幂等编号 |
| X-Model | 平台服务名 shoal-voice |
| X-Request-Id | 本次 HTTP 请求排查编号 |
流式响应不要先执行 response.json()、.blob() 或一次性读取完整响应,再开始播放。MP3 更适合逐段播放;WAV 与 OPUS 的边收边播能力还取决于客户端解码器。
流式中断的处理
发送音频前发生错误时,会返回 JSON 错误。已经开始发送音频后发生中断时,连接会结束或报传输错误,接口不会把 JSON 错误拼进音频文件。
请使用响应头中的 X-Job-Id 查询最终状态。部分已收到的字节不代表任务最终成功,也不保证可组成完整文件。重新获取该任务的音频不会重新生成或再次扣积分。
5. 长任务推荐:异步生成
文字较长、网络波动或运行环境存在 HTTP 等待时限时,建议使用异步方式。这样应用可以立即拿到任务 ID,不需要一直保持首次请求连接。
5.1 创建任务
POST /v1/tts/jobs
Authorization: Bearer <API Key>
Content-Type: application/json
Idempotency-Key: app-order-20261020-0001
请求体与 /v1/tts 完全相同。成功返回 HTTP 202:
{
"id": "平台任务ID",
"status": "queued",
"model": "shoal-voice",
"format": "mp3",
"input_bytes": 36,
"points": {"cost": 36, "state": "held"},
"status_url": "/v1/tts/jobs/平台任务ID",
"audio_url": "/v1/tts/jobs/平台任务ID/audio"
}
时间字段为 Unix 毫秒。示例中的金额只是示意,以实际 input_bytes 和 points 为准。
5.2 查询任务
GET /v1/tts/jobs/{任务ID}
Authorization: Bearer <API Key>
| status | 含义 |
|---|---|
| queued | 已受理,正在等待资源或等待安全恢复 |
| running | 正在生成,可尝试连接 audio_url 接收音频 |
| succeeded | 完成,可获取完整音频 |
| failed | 未得到可用结果,查看错误和积分状态 |
| unknown | 结果无法确认,请先核对记录与积分,避免重复提交 |
| cancelled | 已停止,查看积分状态 |
每 1–2 秒查询一次即可。不要对同一个任务开启多个高频轮询循环。任务最多等待 15 分钟,安全恢复也有次数上限,不会无限排队。
5.3 获取或流式接收音频
GET /v1/tts/jobs/{任务ID}/audio
Authorization: Bearer <API Key>
- 生成中:首段可用后返回音频流。
- 已完成:返回完整音频;支持
Range断点读取。 - 已失败:发送音频前返回 JSON 错误。
- 音频受网站保留策略影响,过期后不能再次下载;生成记录仍可能保留。
6. 幂等与重试:避免重复扣费
- 新的业务任务先生成一个新的
Idempotency-Key,并在自己的数据库中保存。 - 同一 Key、同一幂等编号、同样的文案与声音重复请求,会返回原任务;不会创建新任务或再次冻结积分。
- 同一幂等编号改了文案、声音、格式或控制参数,会返回 409
IDEMPOTENCY_CONFLICT。 - 幂等编号作用于当前 API Key。轮换 Key 后,不要把旧任务当新任务重发;先用任务 ID 查询或下载原结果。
- 没传幂等编号时,服务端会生成并在响应头返回,但客户端若没收到响应头就无法确认该编号,所以生产接入应自己生成。
- 断线、502 或超时后,优先查询任务。需要重新连接时使用原幂等编号,不能在自动重试中每次生成新编号。
- 故意重新生成同一文案时,使用新的幂等编号,会作为新任务计费。
7. 并发和排队
- 新 Key 默认最多 10 次并发;账户默认最多 10 次并发。
- 一个账号下所有 Key 共用账户额度。实际可用额度取账户和单 Key 限额的较小值。
- 管理员可以为账户及单个 Key 设置 1–100 的上限,用户不能通过多建 Key 绕过账户限额。
- 并发额度是上限,资源繁忙时任务可能排队,不代表固定的完成速度或服务承诺。
- 已受理但未结束的异步任务也占任务并发额度;保持打开的音频连接也受连接并发限制。
- 超额返回 HTTP 429
API_CONCURRENCY_LIMIT和Retry-After,不会受理新任务或为该次超额请求冻结积分。 - 收到 429 时按
Retry-After延迟,并增加少量随机抖动,避免集中重试。 - 常规接口每个 Key 每分钟最多 600 次、每个账号每分钟最多 1,200 次,另有来源请求保护。请避免高频轮询。
8. 积分、计费和记录
API 与网页使用同一个积分余额。以原始 text 的 UTF-8 字节数计算,1 字节对应 1 积分。汉字通常占 3 字节,标点、空格、换行、语气标签和表情也计入原始文本字节。
- 每次 API 调用生成一个结果,受理时冻结本次所需积分。
- 生成成功后扣除,原任务的查询和重复下载不再扣生成积分。
- 已明确没有生成成功时,按原结算规则释放冻结积分。
- 已发出但结果未知时,积分可能暂时冻结,由管理员核对处理。不要把
unknown当作可以免费无限重发的信号。 - 如果积分不足,会在受理前返回
POINTS_INSUFFICIENT。 - API 和网页任务都可在网站生成记录中查看。不要把示例 Key 当作无需积分的测试通道。
GET /v1/wallet
Authorization: Bearer <API Key>
响应包括 wallet.available、wallet.reserved、wallet.consumed 和 limits。金额以实际响应为准。
UTF-8 字节数计算:
size = len(text.encode("utf-8"))
const size = new TextEncoder().encode(text).length;
9. 获取声音与平台模型
公共声音列表
GET /v1/voices?page_number=1&page_size=40&title=搜索词
Authorization: Bearer <API Key>
兼容路径:GET /model 或 GET /v1/model。支持 page_number、page_size、title;self=true 或 catalog=mine 只列出当前账号自己的声音。
返回 items、total、has_more、page_number 和 page_size。每个声音带有 reference_id;优先把这个字段原样用于生成请求。total 在目录无法给出精确数量时可能为 null。
声音详情
GET /v1/voices/{声音ID}
GET /model/{声音ID}
私人声音仅允许归属账号访问。请先在网页上传录音、核对逐字稿并完成保存,再使用该私人声音 ID。API 当前不代替上传与逐字稿核对流程。
私人声音与声音 ID
声音 ID 用于选择音色,与 model 服务名称是不同字段。公共声音详情可能显示来源声音 ID;直接上传到本平台的私人声音使用平台分配的 ID,只能通过本平台 API 使用。
直接上传的私人声音不会自动发布或创建共享模型。每次使用它合成时,平台会读取加密保存的参考录音与已确认逐字稿,提交外部语音服务处理;并发合成也会分别提交。与专用服务资源绑定的云端私人声音仍只能使用原资源,超出该资源容量时排队,不能保证达到账户设置的全部并发数。
平台语音服务列表
GET /v1/models
Authorization: Bearer <API Key>
此接口返回平台服务名称 shoal-voice。注意:这里的服务名称与 reference_id 声音 ID 是两个不同字段。
10. Python 流式接收示例
示例使用常见 HTTP 客户端 requests。环境变量中的声音 ID 需要替换为真实可用声音。
import os
import uuid
from pathlib import Path
import requests
base = "https://peiyin520.top"
key = os.environ["SHOAL_API_KEY"]
voice_id = os.environ["SHOAL_VOICE_ID"]
request_id = str(uuid.uuid4()) # 为该业务任务持久保存,重试时复用
headers = {
"Authorization": f"Bearer {key}",
"Idempotency-Key": request_id,
}
payload = {
"text": "你好,这是接入后的第一段语音。",
"reference_id": voice_id,
"format": "mp3",
"streaming": True,
}
partial = Path("speech.mp3.part")
with requests.post(
base + "/v1/tts", headers=headers, json=payload,
stream=True, timeout=(15, 960),
) as response:
if not response.ok:
raise RuntimeError(response.text) # 实际日志请避免打印 Key
job_id = response.headers.get("X-Job-Id")
print("任务:", job_id)
with partial.open("wb") as output:
for chunk in response.iter_content(chunk_size=4096):
if chunk:
output.write(chunk)
output.flush() # 此处也可以送入支持流式解码的播放器
partial.replace("speech.mp3") # 仅完整接收成功后替换最终文件
iter_content 能逐段接收;若要边接收边出声,还需要把数据持续送给流式音频解码器,不能把“保存文件”本身理解为“已经播放”。发生传输异常时请保留任务 ID 并查询状态,避免把 .part 文件当作完整结果。
11. Node.js 流式接收示例
适用于支持内置 fetch 的 Node.js。Key 保存在服务器环境变量中。
import { randomUUID } from 'node:crypto';
import { createWriteStream } from 'node:fs';
import { rename } from 'node:fs/promises';
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
const requestId = randomUUID(); // 业务重试时复用
const response = await fetch('https://peiyin520.top/v1/tts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SHOAL_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': requestId,
},
body: JSON.stringify({
text: '你好,欢迎使用语音创作。',
reference_id: process.env.SHOAL_VOICE_ID,
format: 'mp3',
streaming: true,
}),
signal: AbortSignal.timeout(960_000),
});
if (!response.ok) throw new Error(await response.text());
console.log('任务 ID:', response.headers.get('x-job-id'));
await pipeline(Readable.fromWeb(response.body), createWriteStream('speech.mp3.part'));
await rename('speech.mp3.part', 'speech.mp3');
自己的播放器应直接消费响应流。如果先调用 arrayBuffer() 再交给播放器,客户端仍会等待整个音频收完。
12. 给 AI 编程助手的接入约束
可以下载本文 MD 后交给 AI,但请不要把真实 Key 一起发送。
请接入 SHOAL 语音 API:
1. Base URL 使用 https://peiyin520.top/v1;POST /tts 返回二进制音频。
2. 从服务端环境变量 SHOAL_API_KEY 读取 Bearer Key,禁止写入前端。
3. text 最多 6000 UTF-8 字节;reference_id 使用声音列表返回值。
4. 一个业务任务持久保存一个 Idempotency-Key,断线重试复用原值。
5. 实现 HTTP 流式接收。播放时逐段送入解码器,不要等待 blob/完整缓冲。
6. 长任务优先 POST /tts/jobs,保存任务 ID,每 1–2 秒查询状态。
7. 使用 GET /tts/jobs/{id}/audio 下载或流式读取原结果,不要重新生成。
8. 处理 401、402、403、409、429、502;429 尊重 Retry-After。
9. 新 Key 和账户默认10并发,实际使用当前账户配置;异步未结束任务也占额度。
10. 成功响应是音频,错误响应是JSON。先判断HTTP状态,勿把错误JSON保存为MP3。
11. 对未知结果先查询任务及积分状态,不自动创建新的幂等编号重试。
12. 不调用本文未提供的管理、训练、WebSocket、时间戳或ASR接口。
13. 错误处理速查
错误在发送音频前采用如下 JSON 结构:
{"error":{"code":"API_KEY_INVALID","message":"API Key 无效或已停用"}}
| HTTP / code | 处理方式 |
|---|---|
| 400 VALIDATION / JSON / MSGPACK_INVALID | 检查请求字段、类型和编码 |
| 400 TEXT_TOO_LONG | 文案超长,按段落拆分,原文不自动截断 |
| 400 UNSUPPORTED_FORMAT / UNSUPPORTED_FEATURE | 当前版本不支持该格式或功能 |
| 401 API_KEY_INVALID | 检查完整 Key、Bearer 前缀、尾号及是否撤销 |
| 403 API_DISABLED | 管理员关闭了账号 API,联系管理员 |
| 403 ORIGIN | 请从服务器调用,不要在浏览器中直接携带 Key |
| 404 VOICE_NOT_FOUND / NOT_FOUND | ID 不存在、不可见、已停用或无权访问 |
| 409 POINTS_INSUFFICIENT | 可用积分不足,请充值后发起新任务 |
| 409 IDEMPOTENCY_CONFLICT | 相同幂等编号对应不同内容,先核对业务任务 |
| 409 JOB_DELETED | 原任务已删除,不可复用该编号 |
| 429 API_CONCURRENCY_LIMIT | 等待现有任务/连接结束,按 Retry-After 重试 |
| 429 API_RATE_LIMIT / QUEUE_CAPACITY | 降低请求或轮询频率,稍后重试 |
| 502 GENERATION_FAILED | 查询任务与积分状态,避免盲目创建新任务 |
| GENERATION_DEADLINE / GENERATION_RETRY_LIMIT / GENERATION_ATTEMPT_TIMEOUT | 已停止等待或恢复;查看任务状态与积分,必要时联系管理员 |
| 501 UNSUPPORTED_FEATURE | 该接口能力尚未提供,请使用本文支持的 HTTP 接口 |
排查时提供:X-Request-Id、X-Job-Id、发生时间、HTTP 状态和错误码。不要发送完整 Key,提供尾号即可。