MiniMax Speech Synthesis
Synchronous Speech Synthesis (HTTP)
Use this endpoint to convert text to speech, supporting both streaming and non-streaming modes.
POST /v1/t2a_v2Request Headers
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
Content-Type | string | Yes | application/json | Data exchange format |
Authorization | string | Yes | - | Authentication info, format Bearer <API_KEY> |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | Yes | Model version. Options: speech-2.8-hd, speech-2.8-turbo |
text | string | Yes | Text to synthesize, up to 10,000 characters. For text over 3,000 characters, the streaming mode is recommended. Supports <#x#> pauses (seconds), inline pronunciation replacement (pinyin or IPA), and interjection tags |
stream | boolean | No | Whether to output in streaming mode, default false |
voice_setting | object | Yes | Voice settings, see below |
audio_setting | object | No | Audio output parameters, see below |
pronunciation_dict | object | No | Custom pronunciation dictionary |
language_boost | string | No | Enhancement for minority languages/dialects, optional auto or a specific language |
voice_modify | object | No | Voice effect settings |
subtitle_enable | boolean | No | Whether to enable subtitles, default false |
subtitle_type | string | No | Subtitle granularity: sentence, word, word_streaming |
output_format | string | No | Non-streaming result format: hex (default) or url. url returns a download link valid for 24 hours |
aigc_watermark | boolean | No | Whether to append a watermark rhythm indicator at the end of the audio, default false |
voice_setting Detailed Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
voice_id | string | Yes | - | Voice ID, supports system voices, cloned voices, and text-generated voices |
speed | number | No | 1.0 | Speech rate, range [0.5, 2] |
vol | number | No | 1.0 | Volume, range (0, 10] |
pitch | integer | No | 0 | Pitch, range [-12, 12], 0 is the original pitch |
emotion | string | No | - | Emotion control. Options: happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper |
text_normalization | boolean | No | false | Chinese/English text normalization, enabling it improves number-reading performance |
latex_read | boolean | No | false | Whether to read LaTeX formulas (requires setting language_boost to Chinese, with formulas wrapped in $$) |
audio_setting Detailed Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
sample_rate | integer | 32000 | Sample rate. Options: 8000, 16000, 22050, 24000, 32000, 44100 |
bitrate | integer | 128000 | Bitrate. Options: 32000, 64000, 128000, 256000 (only effective for mp3) |
format | string | mp3 | Audio format. Options: mp3, pcm, flac, wav, pcmu_raw, pcmu_wav, opus |
channel | integer | 1 | Number of channels, 1 for mono, 2 for stereo |
force_cbr | boolean | false | Constant bitrate control, only valid for streaming mp3 |
pronunciation_dict Detailed Parameters
| Parameter | Type | Description |
|---|---|---|
tone | array<string> | Custom pronunciation rules. Format: "original/replacement". The replacement can be text, pinyin with tone numbers, IPA, or kana. Examples: "处理/(chu3)(li3)", "危险/dangerous" |
voice_modify Detailed Parameters
| Parameter | Type | Range | Description |
|---|---|---|---|
pitch | integer | [-100, 100] | Pitch adjustment: -100 deep, 100 bright |
intensity | integer | [-100, 100] | Intensity adjustment: -100 strong, 100 gentle |
timbre | integer | [-100, 100] | Timbre adjustment: -100 mellow, 100 crisp |
sound_effects | string | - | Sound effects. Options: spacious_echo (spacious echo), auditorium_echo (auditorium broadcast), lofi_telephone (telephone distortion), robotic (robotic) |
Request Example
curl -X POST "https://neolink.com/api/minimax/v1/t2a_v2" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <API_KEY>" \
-d '{
"model": "speech-2.8-hd",
"text": "今天是不是很开心呀(laughs),当然了!",
"stream": false,
"voice_setting": {
"voice_id": "male-qn-qingse",
"speed": 1,
"vol": 1,
"pitch": 0,
"emotion": "happy"
},
"audio_setting": {
"sample_rate": 32000,
"bitrate": 128000,
"format": "mp3",
"channel": 1
},
"pronunciation_dict": {
"tone": [
"处理/(chu3)(li3)",
"危险/dangerous"
]
},
"subtitle_enable": false
}'Non-Streaming Response (HTTP 200)
{
"data": {
"audio": "hex编码的完整音频数据",
"status": 2,
"extra_info": {
"audio_length": 9900,
"audio_sample_rate": 32000,
"audio_size": 160323,
"bitrate": 128000,
"word_count": 52,
"usage_characters": 26,
"audio_format": "mp3",
"audio_channel": 1
},
"trace_id": "01b8bf9bb7433cc75c18eee6cfa8fe21"
},
"base_resp": {
"status_code": 0,
"status_msg": "success"
}
}| Field | Type | Description |
|---|---|---|
data.audio | string | Audio data. Hex-encoded when output_format=hex, or a download link (valid for 24 hours) when output_format=url |
data.status | int | Synthesis status: 2 indicates synthesis is complete |
data.extra_info | object | Audio metadata and billing info |
data.extra_info.audio_length | int64 | Audio duration (milliseconds) |
data.extra_info.audio_sample_rate | int64 | Audio sample rate |
data.extra_info.audio_size | int64 | Audio size (bytes) |
data.extra_info.bitrate | int64 | Bitrate |
data.extra_info.word_count | int64 | Number of synthesized characters (excluding punctuation) |
data.extra_info.usage_characters | int64 | Number of billed characters |
data.extra_info.audio_format | string | Audio format |
data.extra_info.audio_channel | int64 | Number of audio channels |
data.trace_id | string | Request trace ID |
base_resp.status_code | int | Status code; see error codes for details |
base_resp.status_msg | string | Status details |
Python Example (Non-Streaming)
import requests
url = "https://neolink.com/api/minimax/v1/t2a_v2"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer <API_KEY>"
}
payload = {
"model": "speech-2.8-hd",
"text": "今天是不是很开心呀(laughs),当然了!",
"stream": False,
"voice_setting": {
"voice_id": "male-qn-qingse",
"speed": 1,
"vol": 1,
"pitch": 0,
"emotion": "happy"
},
"audio_setting": {
"sample_rate": 32000,
"bitrate": 128000,
"format": "mp3",
"channel": 1
}
}
resp = requests.post(url, headers=headers, json=payload)
resp_data = resp.json()
if resp_data["base_resp"]["status_code"] == 0:
audio_hex = resp_data["data"]["audio"]
audio_bytes = bytes.fromhex(audio_hex)
with open("output.mp3", "wb") as f:
f.write(audio_bytes)
print("音频已保存为 output.mp3")
else:
print("错误:", resp_data["base_resp"]["status_msg"])Asynchronous Long-Text Speech Synthesis
Suitable for offline synthesis of large batches of long text. After creating a task, task_id and file_id are returned. You need to poll the status via the task query endpoint, and after completion, download the audio file through the file retrieval endpoint.
POST /v1/t2a_async_v2Request Headers
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
Content-Type | string | Yes | application/json | Data exchange format |
Authorization | string | Yes | - | Authentication info, format Bearer <API_KEY> |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | Yes | Model version. Options: speech-2.8-hd, speech-2.8-turbo |
voice_setting | object | Yes | Voice settings, see the voice_setting parameter in synchronous speech synthesis (text_normalization changed to english_normalization) |
text | string | One of two | Text to synthesize, up to 50,000 characters. 2.8 series models support interjection tags |
text_file_id | int64 | One of two | Text file ID for the text to synthesize. A single file must be < 1,000,000 characters, supports txt and zip formats. Zip files support the structured fields title, content, extra |
language_boost | string | No | Enhance a specific minority language or dialect, optional auto or a specific language |
audio_setting | object | No | Audio output parameters. Fields: audio_sample_rate (default 32000), bitrate (default 128000), format (default mp3), channel (default 2) |
pronunciation_dict | object | No | Custom pronunciation dictionary, same as synchronous speech synthesis |
voice_modify | object | No | Voice effect settings, only supported for mp3/wav/flac formats |
aigc_watermark | boolean | No | Whether to append a watermark at the end of the audio, default false |
Note: Interjection tags are supported only by the speech-2.8 series, including
(laughs),(chuckle),(coughs),(groans),(breath),(sighs),(crying),(applause), and 23 others in total.
Request Example (Passing Text)
curl -X POST "https://neolink.com/api/minimax/v1/t2a_async_v2" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <API_KEY>" \
-d '{
"model": "speech-2.8-hd",
"text": "真正的危险不是计算机开始像人一样思考(sighs),而是人开始像计算机一样思考。",
"language_boost": "auto",
"voice_setting": {
"voice_id": "audiobook_male_1",
"speed": 1,
"vol": 1,
"pitch": 1
},
"audio_setting": {
"audio_sample_rate": 32000,
"bitrate": 128000,
"format": "mp3",
"channel": 2
}
}'Request Example (Passing File ID)
curl -X POST "https://neolink.com/api/minimax/v1/t2a_async_v2" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <API_KEY>" \
-d '{
"model": "speech-2.8-hd",
"text_file_id": 1234567890,
"language_boost": "auto",
"voice_setting": {
"voice_id": "audiobook_male_1",
"speed": 1,
"vol": 10,
"pitch": 1
},
"audio_setting": {
"audio_sample_rate": 32000,
"bitrate": 128000,
"format": "mp3",
"channel": 2
}
}'Successful Response (HTTP 200)
{
"task_id": "95157322514444",
"file_id": 95157322514496,
"task_token": "xxxxxxxxxxxx",
"usage_characters": 30,
"base_resp": {
"status_code": 0,
"status_msg": "success"
}
}| Field | Type | Description |
|---|---|---|
task_id | string | Unique ID of the asynchronous task |
file_id | int64 | Audio file ID. After the task completes, download via Retrieve File. The download link is valid for 9 hours from generation |
task_token | string | Task token |
usage_characters | int64 | Number of billed characters |
base_resp.status_code | int | Status code; 0=success, 1002=rate limited, 1004=authentication failed, 1039=TPM rate limited, 1042=illegal characters exceed 10%, 2013=parameter error |
base_resp.status_msg | string | Status details |
Query Asynchronous Synthesis Task Status
Poll to query the processing status of an asynchronous speech synthesis task.
GET /v1/query/t2a_async_query_v2Request Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
Authorization | string | Yes | Authentication info, format Bearer <API_KEY> |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | int64 | Yes | Asynchronous task ID (returned when the task is created) |
Rate limit: Up to 10 QPS.
Request Example
curl -X GET "https://neolink.com/api/minimax/v1/query/t2a_async_query_v2?task_id=95157322514444" \
-H "Authorization: Bearer <API_KEY>"Successful Response (HTTP 200)
{
"task_id": 95157322514444,
"status": "success",
"file_id": 95157322514496,
"base_resp": {
"status_code": 0,
"status_msg": "success"
}
}| Field | Type | Description |
|---|---|---|
task_id | int64 | Task ID |
status | string | Task status: processing (in progress), success (success), failed (failed), expired (expired) |
file_id | int64 | Audio file ID (returned only when status=success). Obtain the download link via Retrieve File; the link is valid for 9 hours |
base_resp.status_code | int | Status code; see error codes for details |
base_resp.status_msg | string | Status details |
Polling Example (Python)
import requests
import time
url = "https://neolink.com/api/minimax/v1/query/t2a_async_query_v2"
headers = {"Authorization": "Bearer <API_KEY>"}
task_id = 95157322514444
while True:
resp = requests.get(url, headers=headers, params={"task_id": task_id})
data = resp.json()
status = data.get("status")
if status == "success":
print("合成完成,file_id:", data.get("file_id"))
break
elif status == "failed":
print("合成失败:", data)
break
else:
print(f"状态: {status},30秒后重试...")
time.sleep(30)Synchronous Speech Synthesis (WebSocket)
Achieve low-latency streaming speech synthesis through a long-lived WebSocket connection, suitable for scenarios such as real-time dialogue.
WSS /ws/v1/t2a_v2Communication Flow
客户端 服务端
| ----------- 建立 WebSocket -----------→ |
| ←-------- connected_success ---------- |
| ----------- task_start --------------→ |
| ←-------- task_started --------------- |
| ----------- task_continue -----------→ |
| ←-------- task_continued (audio) ----- |
| ----------- task_continue -----------→ |
| ←-------- task_continued (audio) ----- |
| ...(可多次)... |
| ----------- task_finish -------------→ |
| ←-------- task_finished -------------- |If no new event is received within 120 seconds after the last server response, the connection will automatically close.
Client → Server Events
task_start - Start the Synthesis Task
You must receive the server's task_started before continuing to send task_continue.
| Parameter | Type | Required | Description |
|---|---|---|---|
event | string | Yes | Fixed value task_start |
model | string | Yes | Model version. Options: speech-2.8-hd, speech-2.8-turbo, etc. |
voice_setting | object | Yes | Voice settings, see Synchronous Speech Synthesis (HTTP) |
audio_setting | object | No | Audio output parameters, see Synchronous Speech Synthesis (HTTP) |
language_boost | string | No | Enhancement for minority languages/dialects, optional auto or a specific language |
pronunciation_dict | object | No | Custom pronunciation dictionary |
Request example:
{
"event": "task_start",
"model": "speech-2.8-hd",
"language_boost": "Chinese",
"voice_setting": {
"voice_id": "male-qn-qingse",
"speed": 1,
"vol": 1,
"pitch": 0
},
"pronunciation_dict": {
"tone": [
"处理/(chu3)(li3)",
"危险/dangerous"
]
},
"audio_setting": {
"sample_rate": 32000,
"bitrate": 128000,
"format": "mp3",
"channel": 1
}
}task_continue - Send Synthesis Text
Can be sent multiple times. Only available after receiving task_started.
| Parameter | Type | Required | Description |
|---|---|---|---|
event | string | Yes | Fixed value task_continue |
text | string | Yes | Text to synthesize, supports interjection tags such as (sighs), (laughs) |
Request example:
{
"event": "task_continue",
"text": "真正的危险不是计算机开始像人一样思考(sighs),而是人开始像计算机一样思考。"
}task_finish - End the Task
Notify the server that no more text will be sent, and close the connection after waiting for the queued synthesis tasks to complete.
{
"event": "task_finish"
}Server → Client Events
connected_success - Connection Successful
{
"session_id": "301871346491491",
"event": "connected_success",
"trace_id": "0303a2882bf18235ae7a809ae0f3cca7",
"base_resp": {
"status_code": 0,
"status_msg": "success"
}
}task_started - Task Started
After receiving this event, you can start sending task_continue.
{
"session_id": "301871346491491",
"event": "task_started",
"trace_id": "0303a2882bf18235ae7a809ae0f3cca7",
"base_resp": {
"status_code": 0,
"status_msg": "success"
}
}task_continued - Audio Data Returned
After each task_continue is sent, the server returns the corresponding audio data.
{
"data": {
"audio": "base64编码的音频数据"
},
"extra_info": {
"audio_channel": 1,
"audio_format": "mp3",
"audio_length": 9914,
"audio_sample_rate": 32000,
"audio_size": 157869,
"bitrate": 128000,
"invisible_character_ratio": 0,
"usage_characters": 158,
"word_count": 158
},
"is_final": true,
"session_id": "301871346491491",
"trace_id": "04ee3794e2c9e4a",
"base_resp": {
"status_code": 0,
"status_msg": "success"
}
}| Field | Type | Description |
|---|---|---|
data.audio | string | Base64-encoded audio data |
is_final | boolean | Whether this is the final audio segment |
extra_info | object | Audio metadata (channels, format, duration, size, etc.) |
session_id | string | Session identifier |
trace_id | string | Request trace ID |
base_resp.status_code | int | Status code; 0=success |
task_finished - Task Finished
In the normal flow, after all audio corresponding to every task_continue has been returned, the server sends this event to close the task.
{
"session_id": "301871346491491",
"event": "task_finished",
"trace_id": "0303a2882bf18235ae7a809ae0f3cca7",
"base_resp": {
"status_code": 0,
"status_msg": "success"
}
}task_failed - Task Failed
{
"session_id": "301871346491491",
"event": "task_failed",
"trace_id": "0303a2882bf18235ae7a809ae0f3cca7",
"base_resp": {
"status_code": 1004,
"status_msg": "authentication failed"
}
}Python WebSocket Example
import asyncio
import websockets
import json
import base64
url = "wss://neolink.com/minimax/ws/v1/t2a_v2"
headers = {"Authorization": "Bearer <API_KEY>"}
async def tts_websocket():
async with websockets.connect(url, extra_headers=headers) as ws:
# 1. 等待连接成功
msg = json.loads(await ws.recv())
print(f"连接成功: {msg['event']}")
# 2. 启动合成任务
task_start = {
"event": "task_start",
"model": "speech-2.8-hd",
"language_boost": "Chinese",
"voice_setting": {
"voice_id": "male-qn-qingse",
"speed": 1,
"vol": 1,
"pitch": 0
},
"audio_setting": {
"sample_rate": 32000,
"bitrate": 128000,
"format": "mp3",
"channel": 1
}
}
await ws.send(json.dumps(task_start))
# 3. 等待 task_started
msg = json.loads(await ws.recv())
print(f"任务状态: {msg['event']}")
# 4. 发送合成文本
task_continue = {
"event": "task_continue",
"text": "真正的危险不是计算机开始像人一样思考,而是人开始像计算机一样思考。"
}
await ws.send(json.dumps(task_continue))
# 5. 接收音频数据
audio_data = b""
while True:
msg = json.loads(await ws.recv())
if msg["event"] == "task_continued":
chunk = base64.b64decode(msg["data"]["audio"])
audio_data += chunk
if msg.get("is_final"):
break
elif msg["event"] == "task_failed":
print(f"任务失败: {msg['base_resp']}")
break
# 6. 结束任务
await ws.send(json.dumps({"event": "task_finish"}))
msg = json.loads(await ws.recv())
print(f"任务结束: {msg['event']}")
# 保存音频
with open("output_ws.mp3", "wb") as f:
f.write(audio_data)
print("音频已保存为 output_ws.mp3")
asyncio.run(tts_websocket())