← 返回工作台下载 MD 文档 ↓

SHOAL 语音 API 接入说明

版本:2026-10-20。适用于应用后端、自动化脚本、剪辑工具和 AI 工作流。

请以本文列出的接口和能力为准。API Key 与网页账号共用积分和生成记录;不要把 Key 放进网页前端、公开仓库或分享的提示词中。

1. 三分钟完成第一次调用

  1. 登录网站,打开「API 接入」。
  2. 为应用创建一个 Key,立即复制并妥善保存。完整 Key 仅在创建成功时显示一次,之后只能查看尾号。
  3. 在声音库选好声音,复制声音详情中的 ID;也可以通过下方声音列表接口获取 reference_id。
  4. 将 Key 保存到应用服务器的环境变量 SHOAL_API_KEY。
  5. 调用 POST /v1/tts,把成功响应直接保存为音频。
# 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

4. 文本转语音:POST /v1/tts

请求头

请求头要求
Authorization必填,Bearer Key
Content-Typeapplication/json 或 application/msgpack
Idempotency-Key强烈建议提供;1–100 位字母、数字、点、下划线、冒号或短横线

主要请求字段

字段类型与默认值说明
textstring,必填非空;最多 6,000 UTF-8 字节,约 2,000 个汉字。不会自动截断
reference_idstring,必填单个声音 ID;可使用声音列表的 reference_id 或公共声音 ID
formatstring,mp3mp3 / wav / opus
modelstring,可省略客户端兼容字段。新接入建议省略,或填写平台服务名 shoal-voice
prosody.speednumber,10.5–2,1 为正常速度
prosody.volumenumber,0-20–20 dB
prosody.normalize_loudnessboolean,true让输出响度更一致
normalizeboolean,true对数字、日期、单位等进行朗读归一化
temperaturenumber,0.70–1,调整表现的变化程度
top_pnumber,0.70–1,调整采样多样性
streamingboolean,truetrue 尽早返回;false 等完整音频就绪后再返回

voice_id、voiceAssetId 可作为 reference_id 的别名;若同时传多个声音字段,它们必须相同。新接入请统一使用 reference_id。

可选音频与分段参数

字段允许值
chunk_length整数 100–300
latencylow / normal / balanced
sample_rate8000 / 16000 / 22050 / 24000 / 32000 / 44100 / 48000
mp3_bitrate64 / 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_chunksboolean
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-Typeaudio/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>

6. 幂等与重试:避免重复扣费

  1. 新的业务任务先生成一个新的 Idempotency-Key,并在自己的数据库中保存。
  2. 同一 Key、同一幂等编号、同样的文案与声音重复请求,会返回原任务;不会创建新任务或再次冻结积分。
  3. 同一幂等编号改了文案、声音、格式或控制参数,会返回 409 IDEMPOTENCY_CONFLICT。
  4. 幂等编号作用于当前 API Key。轮换 Key 后,不要把旧任务当新任务重发;先用任务 ID 查询或下载原结果。
  5. 没传幂等编号时,服务端会生成并在响应头返回,但客户端若没收到响应头就无法确认该编号,所以生产接入应自己生成。
  6. 断线、502 或超时后,优先查询任务。需要重新连接时使用原幂等编号,不能在自动重试中每次生成新编号。
  7. 故意重新生成同一文案时,使用新的幂等编号,会作为新任务计费。

7. 并发和排队

8. 积分、计费和记录

API 与网页使用同一个积分余额。以原始 text 的 UTF-8 字节数计算,1 字节对应 1 积分。汉字通常占 3 字节,标点、空格、换行、语气标签和表情也计入原始文本字节。

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_FOUNDID 不存在、不可见、已停用或无权访问
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,提供尾号即可。