模型能力
自定义语音
从简短的参考音频片段克隆声音,并在任何内置语音可用的地方使用它。上传音频样本,并立即在我们的 TTS 和语音转语音 API 中使用它。
WARNING
自定义语音目前仅在美国可用,伊利诺伊州除外。
如何使用自定义语音
在控制台中创建语音后,点击语音卡片上的三点菜单并选择复制语音 ID。如果您通过 API(仅限企业版)创建了自定义语音,voice_id 将在响应中返回。
自定义语音在所有语音 API 中与内置语音可互换。将您的 voice_id 传递给以下任何一个:
POST /v1/ttswss://api.x.ai/v1/ttswss://api.x.ai/v1/realtime
内置语音仍可通过 GET /v1/tts/voices 获取。自定义语音仅通过 GET /v1/custom-voices 返回 — 它们不会出现在内置语音列表中。您的自定义语音仅限于您的团队,永远不会对其他用户可用。
录制您的参考音频
通过克隆最长 120 秒的参考片段来创建自定义语音。为获得最佳效果:
- 在安静的环境中录制,理想情况下使用高质量麦克风。
- 自然朗读。如果听起来像在朗读脚本,生成的语音也会匹配这种行为。
- 越长越好。30 秒以下的片段可能缺乏细节。为获得最佳效果,建议录制 90-120 秒。
- 富有表现力地说话。生成的语音将匹配您录音的表现力。
录制内容
模型不仅捕捉参考音频的音色,还捕捉其表达模式。为获得最佳效果,使录音与您计划生成的内容相匹配:
- 客户支持 — 录制真实的支持对话,包括问候语、等待、故障排除步骤和结束语。
- 有声书朗读 — 以最终输出所需的节奏和语调朗读几段散文。
- 对话式助手 — 录制自然的即兴演讲,例如向朋友解释一个主题。
- 新闻或纪录片 — 以自然的广播语音朗读一篇短文。
- 反映您预期用例的录音将比经过打磨但不相关的样本产生更好的效果。
录制设置
- 麦克风。建议使用录音室电容式或优质 USB 麦克风。手机耳机可以使用,但会引入明显的噪音。
- 防喷罩。建议使用。没有防喷罩,爆破音(
p、b)会被复制成可听见的砰砰声。 - 房间处理。在小型、柔软布置的房间内录制。硬墙房间会产生回声和混响,这些也会在生成的语音中重现。
- 单人说话。录音应只包含一个声音,没有背景音乐或音效。
- 背景噪音。使房间保持安静。关闭 HVAC、风扇和通知。背景噪音将与语音一起被克隆。
创建自定义语音
在控制台开始 — 免费创建最多 30 个自定义语音,并在所有语音 API 中立即使用它们。
API 快速开始
WARNING
POST /v1/custom-voices 端点仅对企业版团队开放。**** 以启用 API 访问。
从参考音频文件创建自定义语音,然后用它合成语音:
# 1. Create the voice from a reference clip (max 120s).
CREATE_RESPONSE=$(curl -s -X POST https://api.x.ai/v1/custom-voices \
-H "Authorization: Bearer $XAI_API_KEY" \
-F "name=Friendly Narrator" \
-F "language=en" \
-F "gender=female" \
-F "tone=warm" \
-F "use_case=narration" \
-F "file=@reference.wav;type=audio/wav")
echo "$CREATE_RESPONSE"
# {"voice_id":"abc123xy","name":"Friendly Narrator",...}
# Extract the voice_id from the response (requires jq).
VOICE_ID=$(echo "$CREATE_RESPONSE" | jq -r '.voice_id')
# 2. Use the new voice for TTS.
curl -X POST https://api.x.ai/v1/tts \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"text\": \"Hello! This audio was synthesized using my custom voice.\",
\"voice_id\": \"$VOICE_ID\",
\"language\": \"en\"
}" \
--output hello.mp3import os
import requests
# 1. Create the voice.
with open("reference.wav", "rb") as f:
create = requests.post(
"https://api.x.ai/v1/custom-voices",
headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"},
files={"file": ("reference.wav", f, "audio/wav")},
data={
"name": "Friendly Narrator",
"language": "en",
"gender": "female",
"tone": "warm",
"use_case": "narration",
},
)
create.raise_for_status()
voice_id = create.json()["voice_id"]
# 2. Synthesize speech with it.
speech = requests.post(
"https://api.x.ai/v1/tts",
headers={
"Authorization": f"Bearer {os.environ['XAI_API_KEY']}",
"Content-Type": "application/json",
},
json={
"text": "Hello! This audio was synthesized using my custom voice.",
"voice_id": voice_id,
"language": "en",
},
)
speech.raise_for_status()
with open("hello.mp3", "wb") as f:
f.write(speech.content)import fs from "fs";
// 1. Create the voice.
const form = new FormData();
form.append("file", new Blob([fs.readFileSync("reference.wav")]), "reference.wav");
form.append("name", "Friendly Narrator");
form.append("language", "en");
form.append("gender", "female");
form.append("tone", "warm");
form.append("use_case", "narration");
const createResp = await fetch("https://api.x.ai/v1/custom-voices", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.XAI_API_KEY}` },
body: form,
});
if (!createResp.ok) throw new Error(`Create error ${createResp.status}`);
const { voice_id } = await createResp.json();
// 2. Synthesize speech with it.
const speech = await fetch("https://api.x.ai/v1/tts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.XAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "Hello! This audio was synthesized using my custom voice.",
voice_id,
language: "en",
}),
});
const buffer = Buffer.from(await speech.arrayBuffer());
fs.writeFileSync("hello.mp3", buffer);import Foundation
let apiKey = ProcessInfo.processInfo.environment["XAI_API_KEY"]!
// 1. Create the voice.
let boundary = UUID().uuidString
var body = Data()
func appendField(_ name: String, _ value: String) {
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n".data(using: .utf8)!)
body.append("\(value)\r\n".data(using: .utf8)!)
}
appendField("name", "Friendly Narrator")
appendField("language", "en")
appendField("gender", "female")
appendField("tone", "warm")
appendField("use_case", "narration")
let audioData = try Data(contentsOf: URL(fileURLWithPath: "reference.wav"))
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"file\"; filename=\"reference.wav\"\r\n".data(using: .utf8)!)
body.append("Content-Type: audio/wav\r\n\r\n".data(using: .utf8)!)
body.append(audioData)
body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
var request = URLRequest(url: URL(string: "https://api.x.ai/v1/custom-voices")!)
request.httpMethod = "POST"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.httpBody = body
let (data, _) = try await URLSession.shared.upload(for: request, from: body)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
let voiceId = json["voice_id"] as! String
// 2. Synthesize speech with it.
var ttsRequest = URLRequest(url: URL(string: "https://api.x.ai/v1/tts")!)
ttsRequest.httpMethod = "POST"
ttsRequest.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
ttsRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
ttsRequest.httpBody = try JSONSerialization.data(withJSONObject: [
"text": "Hello! This audio was synthesized using my custom voice.",
"voice_id": voiceId,
"language": "en",
])
let (audioBytes, _) = try await URLSession.shared.data(for: ttsRequest)
try audioBytes.write(to: URL(fileURLWithPath: "hello.mp3"))端点
所有端点都在 https://api.x.ai/v1/custom-voices 下,并使用 Bearer API 密钥进行身份验证。
创建自定义语音
使用 multipart/form-data 的 POST /v1/custom-voices。仅需 file。
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
file | binary | 是 | 参考音频。最大 120 秒。 |
name | string | 显示名称。 | |
description | string | 自由文本描述。 | |
gender | string | male(男性)、female(女性)或 neutral(中性)。 | |
accent | string | 自由文本(例如 British(英式)、American(美式))。 | |
age | string | young(年轻)、middle-aged(中年)或 old(年老)。 | |
language | string | ISO 639 (en) 或 BCP-47 风格 (en-US, zh-CN)。区域必须大写。 | |
use_case | string | conversational(对话)、narration(叙述)、characters(角色)、educational(教育)、advertisement(广告)、social_media(社交媒体)、entertainment(娱乐)。 | |
tone | string | warm(温暖)、casual(随意)、professional(专业)、friendly(友好)、authoritative(权威)、expressive(表现力强)、calm(平静)。 |
建议上传参考文件使用以下格式和设置:
| 设置 | 建议 |
|---|---|
| 编解码器 | 推荐 .wav(未压缩 PCM)。也接受 MP3、FLAC、OGG、Opus、M4A、AAC、MKV 和 MP4,但有损格式可能会引入压缩伪影,这些伪影会在生成的语音中重现。 |
| 采样率 | 推荐 24 kHz。更高采样率(44.1 kHz、48 kHz)会在服务器端降采样。较低采样率会导致保真度降低。 |
| 位深度 | 16-bit PCM 足够。也支持 24-bit。 |
| 声道 | 推荐单声道。立体声文件会自动降混,但单声道录制可避免潜在的相位伪影。 |
长度
- 无最小值,最大 120 秒。 接受任何长度不超过 120 秒的片段;更长的片段会以
400状态码被拒绝。 - 建议 90 秒以上。 更长的片段捕捉更多的韵律和语调变化,产生更自然和富有表现力的语音。
成功的创建会返回带有新语音对象的 201:
{
"voice_id": "nlbqfwie",
"name": "Friendly Narrator",
"description": "Warm, conversational tone for narration.",
"gender": "female",
"accent": "American",
"age": "young",
"language": "en",
"use_case": "narration",
"tone": "warm",
"created_at": "2026-04-26T18:56:34.872993+00:00"
}voice_id 是一个 8 位小写字母数字标识符。
列出自定义语音
GET /v1/custom-voices 返回您的团队拥有的所有语音,并分页。
| 查询参数 | 默认值 | 描述 |
|---|---|---|
limit | 100 | 页面大小,1-1000。 |
pagination_token | 前一个响应中的令牌。第一页省略。 |
curl -s "https://api.x.ai/v1/custom-voices?limit=50" \
-H "Authorization: Bearer $XAI_API_KEY"import os
import requests
response = requests.get(
"https://api.x.ai/v1/custom-voices",
headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"},
params={"limit": 50},
)
for voice in response.json()["voices"]:
print(f"{voice['voice_id']:10s} {voice.get('name')}")const response = await fetch(
"https://api.x.ai/v1/custom-voices?limit=50",
{ headers: { Authorization: `Bearer ${process.env.XAI_API_KEY}` } },
);
const { voices } = await response.json();
voices.forEach((v) => console.log(`${v.voice_id} ${v.name}`));响应:
{
"voices": [
{
"voice_id": "nlbqfwie",
"name": "Friendly Narrator",
"description": "Warm, conversational tone for narration.",
"gender": "female",
"accent": "American",
"age": "young",
"language": "en",
"use_case": "narration",
"tone": "warm",
"created_at": "2026-04-26T18:56:34.872993+00:00"
}
],
"pagination_token": null
}获取自定义语音
GET /v1/custom-voices/{voice_id} 返回单个语音的元数据。对于未知 ID 或属于其他团队的语音,返回 404。
响应体与创建部分显示的语音对象格式匹配。
更新元数据
使用 JSON 体的 PATCH /v1/custom-voices/{voice_id}。所有字段都是可选的,并遵循以下规则:
- 省略字段 — 不更改。
- 字段设置为
null— 清除值。 - 字段设置为非空字符串 — 更新值。
- 字段设置为
""— 以400状态码拒绝。
此端点永远不会更改基础音频。要重新录制,请删除语音并创建新的语音。
curl -X PATCH "https://api.x.ai/v1/custom-voices/nlbqfwie" \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"description": "Updated after a tuning pass.", "tone": "calm"}'import os
import requests
response = requests.patch(
"https://api.x.ai/v1/custom-voices/nlbqfwie",
headers={
"Authorization": f"Bearer {os.environ['XAI_API_KEY']}",
"Content-Type": "application/json",
},
json={"description": "Updated after a tuning pass.", "tone": "calm"},
)
print(response.json())const response = await fetch(
"https://api.x.ai/v1/custom-voices/nlbqfwie",
{
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.XAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
description: "Updated after a tuning pass.",
tone: "calm",
}),
},
);
if (!response.ok) throw new Error(`Update error ${response.status}: ${await response.text()}`);
console.log(await response.json());返回完整的更新后语音对象:
{
"voice_id": "nlbqfwie",
"name": "Friendly Narrator",
"description": "Updated after a tuning pass.",
"gender": "female",
"accent": "American",
"age": "young",
"language": "en",
"use_case": "narration",
"tone": "calm",
"created_at": "2026-04-26T18:56:34.872993+00:00"
}下载参考音频
GET /v1/custom-voices/{voice_id}/audio 以适当的 Content-Type 头(例如 audio/wav、audio/mpeg)流式传输原始参考文件。
删除自定义语音
DELETE /v1/custom-voices/{voice_id} 删除语音及其基础音频。
curl -X DELETE "https://api.x.ai/v1/custom-voices/nlbqfwie" \
-H "Authorization: Bearer $XAI_API_KEY"import os
import requests
requests.delete(
"https://api.x.ai/v1/custom-voices/nlbqfwie",
headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"},
)await fetch("https://api.x.ai/v1/custom-voices/nlbqfwie", {
method: "DELETE",
headers: { Authorization: `Bearer ${process.env.XAI_API_KEY}` },
});响应是 {"deleted": true}。删除后,对相同 voice_id 的后续请求将返回 404,任何引用它的 TTS / 语音转语音调用将因未知语音错误而失败。
使用自定义语音
创建后,自定义 voice_id 在任何内置 voice_id 可用的地方都可用。
REST TTS
curl -X POST https://api.x.ai/v1/tts \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Welcome back. How can I help today?",
"voice_id": "nlbqfwie",
"language": "en"
}' \
--output welcome.mp3流式 TTS WebSocket
打开连接时,将自定义语音作为 voice 查询参数传递。完整的事件协议请参阅文本转语音 - 流式传输。
先决条件: 安装 WebSocket 客户端库 — pip install websockets(Python)或 npm install ws(Node.js)。
import asyncio
import base64
import json
import os
import websockets
async def stream_with_custom_voice(voice_id: str):
uri = f"wss://api.x.ai/v1/tts?language=en&voice={voice_id}&codec=mp3"
async with websockets.connect(
uri,
additional_headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"},
) as ws:
await ws.send(json.dumps({"type": "text.delta", "delta": "Streaming with my custom voice."}))
await ws.send(json.dumps({"type": "text.done"}))
audio = bytearray()
async for msg in ws:
event = json.loads(msg)
if event["type"] == "audio.delta":
audio.extend(base64.b64decode(event["delta"]))
elif event["type"] == "audio.done":
break
with open("stream.mp3", "wb") as f:
f.write(audio)
asyncio.run(stream_with_custom_voice("nlbqfwie"))语音转语音 API
在 session.update 消息中设置 voice。完整会话生命周期请参阅语音转语音 API 文档。
import asyncio
import json
import os
import websockets
async def realtime_with_custom_voice(voice_id: str):
async with websockets.connect(
"wss://api.x.ai/v1/realtime",
additional_headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"},
) as ws:
await ws.send(json.dumps({
"type": "session.update",
"session": {
"voice": voice_id,
"instructions": "You are a helpful assistant.",
"turn_detection": {"type": "server_vad"},
},
}))
# ... continue with the standard realtime event loop ...
asyncio.run(realtime_with_custom_voice("nlbqfwie"))限制
| 值 | |
|---|---|
| 参考音频最大持续时间 | 120 秒 |
| 每个团队的自定义语音数 | 30 |
| 语音 ID 长度 | 8 位字符,小写字母数字 |
需要 30 个以上的语音?
默认限制是每个团队 30 个自定义语音。如果您需要更多,请联系我们讨论更高的限制。
错误处理
| 状态 | 含义 | 操作 |
|---|---|---|
201 | 语音已创建 | 保存 voice_id 并开始使用。 |
200 | 成功读取/更新/删除 | - |
400 | 错误请求 | 检查:音频少于 120 秒;标签值在允许的枚举范围内;PATCH 不包含空字符串。当团队达到 30 个语音限制时也返回此错误 — 删除现有语音或请求更多。 |
401 | 未授权 | API 密钥缺失或无效。 |
403 | 此团队未启用自定义语音,或在没有企业合同的情况下调用了 POST /v1/custom-voices | 在控制台游乐场中创建语音,或联系销售以启用创建 API。 |
404 | 语音未找到 | ID 不存在或属于另一个团队。 |
500 | 服务器错误 | 使用指数退避重试。 |