跳转到内容

模型能力

自定义语音

从简短的参考音频片段克隆声音,并在任何内置语音可用的地方使用它。上传音频样本,并立即在我们的 TTS 和语音转语音 API 中使用它。

WARNING

自定义语音目前仅在美国可用,伊利诺伊州除外。

如何使用自定义语音

控制台中创建语音后,点击语音卡片上的三点菜单并选择复制语音 ID。如果您通过 API(仅限企业版)创建了自定义语音,voice_id 将在响应中返回。

自定义语音在所有语音 API 中与内置语音可互换。将您的 voice_id 传递给以下任何一个:

  • POST /v1/tts
  • wss://api.x.ai/v1/tts
  • wss://api.x.ai/v1/realtime

内置语音仍可通过 GET /v1/tts/voices 获取。自定义语音仅通过 GET /v1/custom-voices 返回 — 它们不会出现在内置语音列表中。您的自定义语音仅限于您的团队,永远不会对其他用户可用。

录制您的参考音频

通过克隆最长 120 秒的参考片段来创建自定义语音。为获得最佳效果:

  • 在安静的环境中录制,理想情况下使用高质量麦克风。
  • 自然朗读。如果听起来像在朗读脚本,生成的语音也会匹配这种行为。
  • 越长越好。30 秒以下的片段可能缺乏细节。为获得最佳效果,建议录制 90-120 秒。
  • 富有表现力地说话。生成的语音将匹配您录音的表现力。

录制内容

模型不仅捕捉参考音频的音色,还捕捉其表达模式。为获得最佳效果,使录音与您计划生成的内容相匹配:

  • 客户支持 — 录制真实的支持对话,包括问候语、等待、故障排除步骤和结束语。
  • 有声书朗读 — 以最终输出所需的节奏和语调朗读几段散文。
  • 对话式助手 — 录制自然的即兴演讲,例如向朋友解释一个主题。
  • 新闻或纪录片 — 以自然的广播语音朗读一篇短文。
  • 反映您预期用例的录音将比经过打磨但不相关的样本产生更好的效果。

录制设置

  • 麦克风。建议使用录音室电容式或优质 USB 麦克风。手机耳机可以使用,但会引入明显的噪音。
  • 防喷罩。建议使用。没有防喷罩,爆破音(pb)会被复制成可听见的砰砰声。
  • 房间处理。在小型、柔软布置的房间内录制。硬墙房间会产生回声和混响,这些也会在生成的语音中重现。
  • 单人说话。录音应只包含一个声音,没有背景音乐或音效。
  • 背景噪音。使房间保持安静。关闭 HVAC、风扇和通知。背景噪音将与语音一起被克隆。

创建自定义语音

在控制台开始 — 免费创建最多 30 个自定义语音,并在所有语音 API 中立即使用它们。

在控制台中克隆语音

API 快速开始

WARNING

POST /v1/custom-voices 端点仅对企业版团队开放。**** 以启用 API 访问。

从参考音频文件创建自定义语音,然后用它合成语音:

bash
# 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.mp3
python
import 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)
javascript
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);
swift
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-dataPOST /v1/custom-voices。仅需 file

字段类型必需描述
filebinary参考音频。最大 120 秒。
namestring显示名称。
descriptionstring自由文本描述。
genderstringmale(男性)、female(女性)或 neutral(中性)。
accentstring自由文本(例如 British(英式)、American(美式))。
agestringyoung(年轻)、middle-aged(中年)或 old(年老)。
languagestringISO 639 (en) 或 BCP-47 风格 (en-US, zh-CN)。区域必须大写。
use_casestringconversational(对话)、narration(叙述)、characters(角色)、educational(教育)、advertisement(广告)、social_media(社交媒体)、entertainment(娱乐)。
tonestringwarm(温暖)、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

json
{
  "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 返回您的团队拥有的所有语音,并分页。

查询参数默认值描述
limit100页面大小,1-1000。
pagination_token前一个响应中的令牌。第一页省略。
bash
curl -s "https://api.x.ai/v1/custom-voices?limit=50" \
  -H "Authorization: Bearer $XAI_API_KEY"
python
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')}")
javascript
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}`));

响应:

json
{
  "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 状态码拒绝。

此端点永远不会更改基础音频。要重新录制,请删除语音并创建新的语音。

bash
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"}'
python
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())
javascript
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());

返回完整的更新后语音对象:

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/wavaudio/mpeg)流式传输原始参考文件。

删除自定义语音

DELETE /v1/custom-voices/{voice_id} 删除语音及其基础音频。

bash
curl -X DELETE "https://api.x.ai/v1/custom-voices/nlbqfwie" \
  -H "Authorization: Bearer $XAI_API_KEY"
python
import os
import requests

requests.delete(
    "https://api.x.ai/v1/custom-voices/nlbqfwie",
    headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"},
)
javascript
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

bash
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)。

python
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 文档

python
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服务器错误使用指数退避重试。

本文档为 docs.x.ai 全站中文翻译,由 AI 自动翻译生成。代码示例请以原文为准。