语音克隆
上传参考音频创建可复用的私有音色,确认后用它的 speaker ID 调用语音合成接口。
语音克隆 API 把一段录音变成可反复使用的私有音色。上传参考音频、轮询到克隆完成、确认,就得到一个 speakerId,可以像官方音色一样用在 /v1/speech、/v1/tts 和 /v1/audio/speech 上。克隆出的音色属于 API Key 对应的账号,也会出现在 GET /v1/speakers/list 的结果里。
所有端点位于 https://api.marswave.ai/openapi/v1/voice-clone,使用 Authorization: Bearer $LISTENHUB_API_KEY 鉴权。
克隆他人声音必须获得本人授权。每次创建请求都要带 consentConfirmed=true——这是你对「已获得被克隆者授权」的声明,不带就会被拒绝,声明本身会随任务存证。获取并遵守该授权是调用方的责任。
所有响应都包在 { "code": 0, "message": "", "data": { ... } } 里。code 非 0 表示错误,见 错误处理。下面的示例都从 data 取字段。
限制与费用
| 项目 | 取值 |
|---|---|
| 参考音频 | 1–6 个文件,单个 ≤5MB,总计 ≤20MB |
| 语言 | zh、en、ja |
| 限流 | 每用户每分钟 5 次创建 |
| 订阅 | 仅付费套餐可用,免费账号确认时返回 NEED_UPGRADE |
| 确认次数 | 套餐周期配额内免费,超出后每次 300 积分 |
| 音色数量 | 按套餐设上限(maxSpeakers),删除音色可释放名额 |
| 未确认任务 | 7 天后过期——想留下音色就要确认 |
超出配额后,只有传了 useCredits=true 才会真的扣积分;不传则返回 NEED_CREDIT,不扣费。
两种克隆方式
两步(默认)——先上传、试听,再决定:
POST /v1/voice-clone/clone返回taskId。- 轮询
GET /v1/voice-clone/clone/{taskId}直到status为completed,响应里带demoAudioUrl,即临时音色的试听。 POST /v1/voice-clone/confirm带上名称和性别,把任务确认成永久私有音色,返回speakerId。
一键——创建时传 autoConfirm=true(连带 name 和 gender)。之后第一次发现克隆完成的那次轮询会顺带完成确认,并在同一个响应里返回 speakerId,不需要第二次请求。
开启 autoConfirm=true 时,扣积分发生在轮询请求上。重复轮询或并发轮询不会重复扣费——确认由原子锁保护,第二次尝试会被判为已确认。
读懂轮询响应
GET /v1/voice-clone/clone/{taskId} 有三种终态,按下面顺序判断:
| 终态 | 判据 | 拿到什么 |
|---|---|---|
| 克隆失败 | status: "failed" | errorCode 与 errorMessage |
| 克隆完成但未确认 | status: "completed" 且没有 speakerId | demoAudioUrl;自动确认失败时另带 confirmError |
| 确认成功 | 有 speakerId | speakerId,可直接用于语音合成 |
中间那一格在 autoConfirm=true 时最容易漏:克隆成功了,但音色没保存下来——积分不足、配额用完或音色数达上限,具体原因看 confirmError。克隆结果还在,排除原因后显式调 POST /v1/voice-clone/confirm 即可。
| 状态 | 含义 |
|---|---|
pending | 任务已创建,等待处理 |
processing | 克隆进行中 |
completed | 克隆完成——可以试听,但不代表已确认 |
failed | 克隆失败,errorMessage 说明原因 |
重试语义
| 状态码 | 什么时候出现 | 怎么处理 |
|---|---|---|
429 | 同一账号已有一次确认在进行中,或创建超过每分钟 5 次 | 按 Retry-After(默认 2 秒)后重试 |
503 | 确认所依赖的组件暂时不可用 | 按 Retry-After(默认 5 秒)后重试 |
两者都可以安全重试,都不会扣积分。
创建克隆任务
POST /v1/voice-clone/clone
请求体是 multipart/form-data,不是 JSON。多个文件就重复传 audioFiles 字段。
# 两步
curl -X POST "https://api.marswave.ai/openapi/v1/voice-clone/clone" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-F "audioFiles=@reference.mp3" \
-F "language=zh" \
-F "consentConfirmed=true"
# 一键:克隆完成后直接确认
curl -X POST "https://api.marswave.ai/openapi/v1/voice-clone/clone" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-F "audioFiles=@part-1.mp3" \
-F "audioFiles=@part-2.mp3" \
-F "language=ja" \
-F "consentConfirmed=true" \
-F "autoConfirm=true" \
-F "name=我的 API 音色" \
-F "gender=female" \
-F "useCredits=true"import { readFile } from 'node:fs/promises'
const form = new FormData()
form.append('audioFiles', new Blob([await readFile('reference.mp3')]), 'reference.mp3')
form.append('language', 'zh')
form.append('consentConfirmed', 'true')
const response = await fetch('https://api.marswave.ai/openapi/v1/voice-clone/clone', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}` },
body: form,
})
const { data } = await response.json()
console.log('任务:', data.taskId)import os
import requests
with open('reference.mp3', 'rb') as audio:
response = requests.post(
'https://api.marswave.ai/openapi/v1/voice-clone/clone',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
files=[('audioFiles', ('reference.mp3', audio, 'audio/mpeg'))],
data={'language': 'zh', 'consentConfirmed': 'true'},
)
data = response.json()['data']
print('任务:', data['taskId'])请求参数:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
audioFiles | file | 是 | 1–6 个参考音频文件,多个文件重复传该字段 |
language | string | 是 | zh、en 或 ja |
consentConfirmed | boolean | 是 | 必须为 true,即声明已获得被克隆者授权 |
mode | string | 否 | upload(默认,也是唯一接受的值) |
autoConfirm | boolean | 否 | 让发现克隆完成的那次轮询直接确认,默认 false |
name | string | 用 autoConfirm 时必填 | 音色名称,最长 50 字符 |
gender | string | 用 autoConfirm 时必填 | male、female 或 other |
useCredits | boolean | 否 | 授权超配额后的 300 积分扣费,默认 false |
返回:
{
"code": 0,
"message": "",
"data": {
"taskId": "6915bde9cca4d3c8ecb3eaf5",
"status": "pending"
}
}轮询克隆任务
GET /v1/voice-clone/clone/{taskId}
curl -X GET "https://api.marswave.ai/openapi/v1/voice-clone/clone/{taskId}" \
-H "Authorization: Bearer $LISTENHUB_API_KEY"const response = await fetch(
`https://api.marswave.ai/openapi/v1/voice-clone/clone/${taskId}`,
{ headers: { Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}` } },
)
const { data } = await response.json()
if (data.status === 'failed') throw new Error(data.errorMessage)
if (data.speakerId) console.log('可以用它合成了:', data.speakerId)
else if (data.confirmError) console.warn('克隆成功但没保存:', data.confirmError)
else if (data.demoAudioUrl) console.log('试听:', data.demoAudioUrl)import os
import requests
response = requests.get(
f'https://api.marswave.ai/openapi/v1/voice-clone/clone/{task_id}',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
)
data = response.json()['data']
if data['status'] == 'failed':
raise RuntimeError(data['errorMessage'])
if data.get('speakerId'):
print('可以用它合成了:', data['speakerId'])
elif data.get('confirmError'):
print('克隆成功但没保存:', data['confirmError'])
elif data.get('demoAudioUrl'):
print('试听:', data['demoAudioUrl'])克隆完成、等待确认:
{
"code": 0,
"message": "",
"data": {
"status": "completed",
"demoAudioUrl": "https://assets.listenhub.ai/voice-clone/demo-6915bde9.mp3"
}
}已确认:
{
"code": 0,
"message": "",
"data": {
"status": "completed",
"demoAudioUrl": "https://assets.listenhub.ai/voice-clone/demo-6915bde9.mp3",
"speakerId": "voice-clone-6915bde9cca4d3c8ecb3eaf5"
}
}确认克隆任务
POST /v1/voice-clone/confirm
把已完成的任务确认成永久私有音色。同一个任务重复调用返回 ALREADY_CONFIRMED,不会二次扣费。
curl -X POST "https://api.marswave.ai/openapi/v1/voice-clone/confirm" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"taskId": "6915bde9cca4d3c8ecb3eaf5",
"name": "我的 API 音色",
"gender": "female",
"useCredits": true
}'const response = await fetch('https://api.marswave.ai/openapi/v1/voice-clone/confirm', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ taskId, name: '我的 API 音色', gender: 'female', useCredits: true }),
})
const { data } = await response.json()
console.log('音色:', data.speakerId)| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
taskId | string | 是 | 已完成的克隆任务 |
name | string | 是 | 音色名称,最长 50 字符 |
gender | string | 是 | male、female 或 other |
useCredits | boolean | 否 | 授权超配额后的 300 积分扣费,默认 false |
返回:
{
"code": 0,
"message": "",
"data": { "speakerId": "voice-clone-6915bde9cca4d3c8ecb3eaf5" }
}用克隆音色合成语音
把 speakerId 填到需要音色的位置即可:
curl -X POST "https://api.marswave.ai/openapi/v1/speech" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"scripts": [
{
"content": "这句话由我自己的克隆音色朗读。",
"speakerId": "voice-clone-6915bde9cca4d3c8ecb3eaf5"
}
]
}'列出私有音色
GET /v1/voice-clone/speakers
curl -X GET "https://api.marswave.ai/openapi/v1/voice-clone/speakers" \
-H "Authorization: Bearer $LISTENHUB_API_KEY"{
"code": 0,
"message": "",
"data": {
"speakers": [
{
"id": "6915c0a2cca4d3c8ecb3eb01",
"name": "我的 API 音色",
"speakerInnerId": "voice-clone-6915bde9cca4d3c8ecb3eaf5",
"language": "zh",
"gender": "female",
"demoAudioUrl": "https://assets.listenhub.ai/voice-clone/demo-6915bde9.mp3",
"createdAt": "2026-07-30T09:10:11.000Z"
}
],
"quota": 2,
"isLimitReached": false,
"maxSpeakers": 2,
"remainingConfirmations": 1
}
}| 字段 | 说明 |
|---|---|
speakers[].speakerInnerId | 传给语音合成接口的音色 ID |
quota | 每个订阅周期包含的确认次数 |
remainingConfirmations | 当前周期剩余确认次数 |
maxSpeakers | 套餐允许同时保留的私有音色数量 |
isLimitReached | 当前周期确认次数已用完时为 true |
查看、改名与删除音色
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /v1/voice-clone/speakers/{speakerId} | 查看单个私有音色 |
PUT | /v1/voice-clone/speakers/{speakerId} | 改 name 和/或 gender(至少传一个) |
DELETE | /v1/voice-clone/speakers/{speakerId} | 删除音色 |
# 改名
curl -X PUT "https://api.marswave.ai/openapi/v1/voice-clone/speakers/{speakerId}" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "旁白(中文)" }'
# 删除——释放一个 maxSpeakers 名额
curl -X DELETE "https://api.marswave.ai/openapi/v1/voice-clone/speakers/{speakerId}" \
-H "Authorization: Bearer $LISTENHUB_API_KEY"GET 与 PUT 返回音色本身:
{
"code": 0,
"message": "",
"data": {
"id": "6915c0a2cca4d3c8ecb3eb01",
"speakerInnerId": "voice-clone-6915bde9cca4d3c8ecb3eaf5",
"name": "旁白(中文)",
"language": "zh",
"gender": "female",
"demoAudioUrl": "https://assets.listenhub.ai/voice-clone/demo-6915bde9.mp3",
"createdAt": "2026-07-30T09:10:11.000Z",
"updatedAt": "2026-07-30T10:02:44.000Z"
}
}DELETE 返回 { "speakerId": "..." }。删除只释放音色名额,本周期已消耗的确认次数不会退回。
错误
| 错误 | 含义 |
|---|---|
NEED_UPGRADE | 语音克隆需要付费套餐 |
NEED_CREDIT | 配额已用完且没传 useCredits,未扣费 |
SPEAKER_LIMIT_REACHED | 私有音色数已达上限,先删一个再确认 |
ALREADY_CONFIRMED | 该任务已确认,不会二次扣费 |
AUDIO_DURATION_INVALID | 参考音频时长不符合要求 |
NO_VALID_SPEECH | 参考音频里没检测到有效人声 |
TASK_FAILED | 克隆失败,errorMessage 带具体原因 |
完整错误结构见 错误处理。