MiniMax TTS Pronunciation, Pauses, and Subtitle Control

Last verified: July 20, 2026.

Evidence status: This guide was validated against MiniMax’s published API references and schemas. The examples were reviewed for request structure, parameter names, response handling, and documented limits. No paid API request, audio-quality comparison, latency measurement, or subtitle-alignment test was performed. Treat the examples as documentation-validated integration patterns, then run a short billed sample with your own voice, language, and script before production use.

Independence notice: MiniMax-AI.chat is an independent guide. It is not owned by, operated by, endorsed by, or affiliated with MiniMax.

MiniMax TTS gives developers several different ways to influence how text is spoken. They are not interchangeable. A pronunciation dictionary replaces matching text before synthesis; inline phonetic annotations target a word at a specific location; pause markers add an explicit duration; language_boost improves recognition for a selected language or dialect; and subtitle parameters request timestamp data alongside the audio.

The important implementation detail is that the three TTS interfaces do not expose these controls in exactly the same way. Synchronous HTTP has the most explicitly documented inline-pronunciation and subtitle behavior. WebSocket is designed for an ongoing low-latency session and puts stable controls in the opening task_start event. Async TTS handles long-form jobs and automatically packages sentence-level subtitles, but it does not expose the HTTP subtitle granularity selector. The official starting points are the HTTP T2A reference, WebSocket T2A reference, and async task reference.

HTTP, WebSocket, or async: which interface fits?

InterfaceUse it forText limitPronunciation and pausesSubtitle behavior
HTTP POST /v1/t2a_v2One request that returns or streams one synthesis resultText must be under 10,000 characters; streaming is advised above 3,000Dictionary, inline IPA/Pinyin/Jyutping, and pause markers are explicitly documentedSentence, word, or streaming-word timestamps through subtitle_type
WebSocket wss://api.minimax.io/ws/v1/t2a_v2Interactive sessions and sequential text segmentsUp to 10,000 characters in a task_continue messageDictionary at task start; pause markers in continued textRequest schema accepts subtitle controls; see the delivery caveat below
Async POST /v1/t2a_async_v2Long narration, books, or batch file workflows50,000 characters as direct text or 1,000,000 through a text fileDictionary is supported; pause markers are explicitly documented for uploaded TXT inputSentence-level subtitle files are generated with result packages; granularity is not selectable

All three interfaces use Bearer authentication against the international API host. Keep MINIMAX_API_KEY on your server; never expose it in browser JavaScript, a mobile bundle, a public repository, or a WordPress page source.

Two ways to control pronunciation

1. Reusable rules with pronunciation_dict

The pronunciation_dict object contains a tone array. Each entry follows original/replacement. The HTTP schema documents replacements as plain text, IPA inside parentheses, Mandarin Pinyin with numbered tones inside parentheses, or Japanese hiragana, katakana, and romaji. Multiple rules can be submitted together.

"pronunciation_dict": {
  "tone": [
    "lead/(liːd)",
    "数据/(shu4)(ju4)",
    "今日/きょう",
    "API/A P I"
  ]
}

For Mandarin Pinyin, the documented tone numbers are 1 through 5, where 5 is neutral. Put each Pinyin syllable in its own half-width parentheses when a replacement contains more than one syllable. An entry may also combine phonetic notation and unchanged characters.

Dictionary rules are best for names, acronyms, product terms, abbreviations, and words that recur throughout a script. Keep the left side specific. A broad replacement such as read/(riːd) would force one pronunciation everywhere, even when the past-tense pronunciation was intended. Split the text or use an inline override when pronunciation depends on context.

2. Inline IPA, Pinyin, and Jyutping

The HTTP text field also supports location-specific pronunciation. Place the phonetic reading in half-width parentheses beside the word or character it should affect. The published rules accept IPA, Mandarin Pinyin with tone numbers 1–5, and Cantonese Jyutping with tone numbers 1–6.

The verb is live (lɪv), while the broadcast is live (laɪv).
请处理 (chu3)(li3) 这份数据。
去街市買餸 (sung3)。

Half-width parentheses matter: do not substitute full-width Chinese punctuation. Inline control is explicitly described in the HTTP schema. The published WebSocket and async text-field descriptions do not make the same promise, so do not assume that copying inline IPA, Pinyin, or Jyutping into those interfaces has identical behavior. For WebSocket or async jobs, prefer the documented pronunciation_dict mechanism and validate any inline notation with a short sample.

Speech 2.8 also recognizes a fixed set of interjection tags such as (laughs), (sighs), (inhale), and (applause). Those are expressive sound instructions, not pronunciation notation. Only use tags listed in the applicable MiniMax schema, and remember that the supported tag list differs slightly across the HTTP and async descriptions. An arbitrary parenthetical stage direction is not guaranteed to work as a sound tag.

Add exact pauses with <#x#>

A pause marker has the form <#x#>, where x is a duration in seconds. MiniMax documents a range from 0.01 to 99.99, with no more than two decimal places.

Welcome to the lesson.<#0.60#>First, open the settings panel.
This chapter ends here.<#1.25#>The next chapter begins with installation.
  • Place a pause between speakable text segments, not at an empty position.
  • Do not place two pause markers consecutively.
  • Use newline characters for paragraph boundaries; use pause markers only when a measured gap is required.
  • A punctuation mark may already create a natural break, so compare the unmarked and marked versions before adding many pauses.

HTTP documents pause markers directly in its text field, and WebSocket documents them in each task_continue.text message. The async schema explicitly documents pause markers for uploaded TXT input. It does not state the same rule for direct async text, so use a TXT file when precise long-form pauses are a requirement rather than an experiment.

Use language_boost without treating it as translation

language_boost helps the model recognize a specified language or dialect. It does not translate the script, change a mismatched voice into a native speaker, or guarantee accent quality. Set it to the script’s dominant language, or use auto when the language is unknown.

The enum covers 40 language values plus auto. Examples include English, Arabic, Spanish, Portuguese, Japanese, and Afrikaans. Cantonese uses the exact value Chinese,Yue, including the comma. MiniMax notes that the speech-01 and speech-02 families do not support Persian, Filipino, or Tamil; this limitation is not stated for Speech 2.8 or 2.6 in the same field description. The full supported-language table is available in MiniMax’s synchronous TTS guide.

"language_boost": "Portuguese"

For mixed-language narration, there is no documented per-word language_boost switch. Use one value for the request or session, choose a voice that handles the needed language pair, add pronunciation rules for difficult terms, and evaluate each segment. If a long script changes languages by section, separate requests are easier to test and correct.

HTTP example: audio, pronunciation, pauses, and subtitles in Node.js

This server-side Node.js example requests non-streaming MP3 audio as hex and sentence-level subtitle data. It checks both the HTTP status and MiniMax’s nested base_resp.status_code, because an HTTP 200 response can still carry an API-level failure. It also checks for a null data object, as required by the official response schema.

import { writeFile } from "node:fs/promises";

const apiKey = process.env.MINIMAX_API_KEY;
if (!apiKey) throw new Error("MINIMAX_API_KEY is required");

const response = await fetch("https://api.minimax.io/v1/t2a_v2", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "speech-2.8-hd",
    text: "The verb is live (lɪv).<#0.60#>The event is live (laɪv).",
    stream: false,
    language_boost: "English",
    voice_setting: {
      voice_id: "English_expressive_narrator",
      speed: 1,
      vol: 1,
      pitch: 0,
    },
    pronunciation_dict: {
      tone: ["API/A P I"],
    },
    audio_setting: {
      sample_rate: 32000,
      bitrate: 128000,
      format: "mp3",
      channel: 1,
    },
    subtitle_enable: true,
    subtitle_type: "sentence",
    output_format: "hex",
  }),
});

const payload = await response.json();
if (!response.ok || payload.base_resp?.status_code !== 0) {
  throw new Error(
    `MiniMax TTS failed: ${payload.base_resp?.status_code ?? response.status} ` +
    `${payload.base_resp?.status_msg ?? response.statusText}`
  );
}
if (!payload.data) throw new Error("MiniMax returned no data object");

if (payload.data.audio) {
  await writeFile("speech.mp3", Buffer.from(payload.data.audio, "hex"));
}

if (payload.data.subtitle_file) {
  const subtitleResponse = await fetch(payload.data.subtitle_file);
  if (!subtitleResponse.ok) throw new Error("Subtitle download failed");
  await writeFile("speech-subtitles.json", await subtitleResponse.text(), "utf8");
}

console.log({
  traceId: payload.trace_id,
  billedCharacters: payload.extra_info?.usage_characters,
  audioLengthMs: payload.extra_info?.audio_length,
});

The HTTP response schema describes data.subtitle_file as a download link to JSON with millisecond timestamps. For sentence output, it describes segments of no more than 50 characters. The request exposes three values: sentence, word, and word_streaming. Use word_streaming only when stream is true. Do not treat the returned JSON as SRT or WebVTT without converting and validating it.

For non-streaming HTTP, output_format can be hex or url; a returned audio URL is documented as valid for 24 hours. Streaming HTTP supports only hex output. If you enable streaming, process every chunk, wait until the response status indicates completion, and avoid accidentally writing the final aggregated copy twice. The HTTP schema’s stream_options.exclude_aggregated_audio controls whether the final chunk repeats the complete audio.

WebSocket constraints and event order

A WebSocket session begins at wss://api.minimax.io/ws/v1/t2a_v2 with the same Bearer credential. Wait for connected_success, send task_start, wait for task_started, send one or more task_continue messages, then send task_finish. MiniMax says the connection closes automatically if no new event is sent for 120 seconds after the last result.

// Stable settings belong in task_start.
ws.send(JSON.stringify({
  event: "task_start",
  model: "speech-2.8-turbo",
  language_boost: "English",
  voice_setting: {
    voice_id: "English_expressive_narrator",
    speed: 1,
    vol: 1,
    pitch: 0,
  },
  pronunciation_dict: { tone: ["API/A P I"] },
  audio_setting: {
    sample_rate: 32000,
    bitrate: 128000,
    format: "mp3",
    channel: 1,
  },
  subtitle_enable: true,
  subtitle_type: "word_streaming",
}));

// Send this only after task_started.
ws.send(JSON.stringify({
  event: "task_continue",
  text: "First segment.<#0.40#>Second segment.",
}));

// Finish after all intended text has been submitted.
ws.send(JSON.stringify({ event: "task_finish" }));

The WebSocket request schema includes subtitle_enable and all three subtitle types. However, the published task_continued response schema lists hex audio, metadata, session IDs, status, and final-state fields without naming a subtitle payload or subtitle download field. Because this guide did not run a live WebSocket session, do not build a production dependency around an assumed subtitle event shape. Capture and inspect a real response for your account, retain unknown fields, and fall back to HTTP when a documented subtitle_file response is required.

Async long-form TTS and sentence subtitles

Async TTS is a task workflow rather than a live stream. Direct text accepts up to 50,000 characters. An uploaded TXT or ZIP referenced through text_file_id accepts up to 1,000,000 characters. The two inputs are mutually exclusive: submit one, not both. MiniMax’s async long TTS guide documents the sequence: optionally upload input, create a task, poll its status, then retrieve the generated file.

Async output includes audio, a sentence-level subtitle file, and extra JSON metadata for each generated unit. A ZIP input must contain files of one type. JSON files inside the archive can use title, content, and extra; each non-empty field creates its own output set. Unlike HTTP, the async request schema does not expose subtitle_enable or subtitle_type, so do not promise word-level async timestamps.

const created = await fetch("https://api.minimax.io/v1/t2a_async_v2", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MINIMAX_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "speech-2.8-hd",
    text: "A long-form narration sample for the async workflow.",
    language_boost: "English",
    voice_setting: {
      voice_id: "English_expressive_narrator",
      speed: 1,
      vol: 1,
      pitch: 0,
    },
    pronunciation_dict: { tone: ["API/A P I"] },
    audio_setting: {
      audio_sample_rate: 32000,
      bitrate: 128000,
      format: "mp3",
      channel: 2,
    },
  }),
}).then((r) => r.json());

if (created.base_resp?.status_code !== 0) {
  throw new Error(created.base_resp?.status_msg || "Async task creation failed");
}

const status = await fetch(
  `https://api.minimax.io/v1/query/t2a_async_query_v2?task_id=${created.task_id}`,
  { headers: { Authorization: `Bearer ${process.env.MINIMAX_API_KEY}` } }
).then((r) => r.json());

console.log(status.status, status.file_id);

The query endpoint reports processing, success, failed, or expired, and is limited to 10 status queries per second. Use capped backoff rather than polling in a tight loop. After success, use the returned file_id with MiniMax’s Retrieve Content API. The async documentation says the download URL remains valid for nine hours, or 32,400 seconds, after generation; copy the result to storage you control within that window.

Python example for synchronous HTTP

import os
import requests

response = requests.post(
    "https://api.minimax.io/v1/t2a_v2",
    headers={
        "Authorization": f"Bearer {os.environ['MINIMAX_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "model": "speech-2.8-turbo",
        "text": "The verb is live (lɪv).<#0.50#>The event is live (laɪv).",
        "stream": False,
        "language_boost": "English",
        "voice_setting": {
            "voice_id": "English_expressive_narrator",
            "speed": 1,
            "vol": 1,
            "pitch": 0,
        },
        "pronunciation_dict": {"tone": ["API/A P I"]},
        "audio_setting": {
            "sample_rate": 32000,
            "bitrate": 128000,
            "format": "mp3",
            "channel": 1,
        },
        "subtitle_enable": True,
        "subtitle_type": "word",
        "output_format": "hex",
    },
    timeout=120,
)
response.raise_for_status()
payload = response.json()

if payload.get("base_resp", {}).get("status_code") != 0:
    raise RuntimeError(payload.get("base_resp"))
if not payload.get("data"):
    raise RuntimeError("MiniMax returned no data object")

audio_hex = payload["data"].get("audio")
if audio_hex:
    with open("speech.mp3", "wb") as audio_file:
        audio_file.write(bytes.fromhex(audio_hex))

subtitle_url = payload["data"].get("subtitle_file")
if subtitle_url:
    subtitle = requests.get(subtitle_url, timeout=30)
    subtitle.raise_for_status()
    with open("speech-subtitles.json", "wb") as subtitle_file:
        subtitle_file.write(subtitle.content)

A practical validation workflow

  1. Start with one or two sentences and a system voice. Record the model, voice ID, interface, request body, and verification date.
  2. Generate a baseline without pronunciation rules or pause markers.
  3. Add one control at a time. This isolates whether a dictionary rule, inline phonetic reading, language setting, or pause caused the change.
  4. Test each ambiguous word in at least two contexts. Avoid a global dictionary rule when context changes the reading.
  5. Compare subtitle text and timestamps with the final encoded audio, not with the input script alone.
  6. Log trace_id, usage_characters, audio duration, model, and API error details. Remove API keys and private text before sharing logs.
  7. Repeat a small regression set after changing model, voice, interface, or text-normalization settings.

Pronunciation and timing are perceptual outputs, so schema correctness is only the first check. The same rule may sound acceptable with one voice and unnatural with another. Subtitles can also be structurally valid yet poorly aligned for a fast voice, abbreviations, code-switching, or aggressive pause control. Human listening and timestamp review remain part of production QA.

Common implementation mistakes

  • Using full-width parentheses: inline phonetic annotations require half-width parentheses.
  • Confusing Pinyin and Jyutping ranges: Mandarin uses tone numbers 1–5; Cantonese Jyutping uses 1–6.
  • Stacking pause markers: consecutive <#x#> markers are not allowed.
  • Assuming language_boost translates: it helps language recognition; it does not rewrite or translate the input.
  • Requesting word_streaming without streaming: that subtitle type is valid only when stream=true.
  • Expecting SRT: HTTP documents a JSON subtitle link, while async returns a subtitle file in its generated package.
  • Using HTTP field names in async audio settings: HTTP and WebSocket use sample_rate; async uses audio_sample_rate.
  • Ignoring API-level errors: inspect base_resp.status_code and keep the trace_id even when the transport status is 200.

Frequently asked questions

Does MiniMax TTS support IPA?

Yes. The HTTP API documents IPA in half-width parentheses for inline pronunciation, and its pronunciation dictionary accepts IPA as a replacement. Use a short test because valid IPA notation does not by itself guarantee the desired accent or prosody for every voice.

Can I control Mandarin and Cantonese pronunciation?

HTTP inline overrides support Mandarin Pinyin with tone numbers 1–5 and Cantonese Jyutping with tone numbers 1–6. For a Cantonese request, set language_boost to the exact value Chinese,Yue. The pronunciation dictionary also documents numbered Pinyin replacements.

Can MiniMax return word-level subtitles?

HTTP exposes word and word_streaming, with the latter limited to streaming requests. WebSocket accepts the same subtitle type values in its request schema, but its published response schema does not identify the subtitle payload. Async output is documented at sentence level and has no granularity selector.

Can I use pause markers in a one-million-character async job?

Yes, when the long input is supplied as a TXT file. The async reference explicitly documents <#x#> pause markers for TXT file input. Keep each duration within 0.01–99.99 seconds, place it between speakable segments, and do not use consecutive markers.

Where should I get voice IDs?

Use MiniMax’s official system voice list or the Voice Management API rather than copying an ID from an old tutorial. Availability can differ between system, cloned, and designed voices.

For model selection, see the MiniMax Speech 2.8 guide and the broader MiniMax Audio guide. If your application streams text in segments, continue with the dedicated MiniMax WebSocket TTS guide.

Bottom line

Use synchronous HTTP when you need the clearest documented combination of inline IPA/Pinyin/Jyutping, measured pauses, and downloadable subtitle JSON. Use WebSocket when text arrives in segments and low-latency audio matters, but verify subtitle delivery before depending on an undocumented response field. Use async TTS for long narration and file-based workloads, accepting that its published subtitle output is sentence-level. Across all three, keep reusable terms in pronunciation_dict, use <#x#> sparingly, set one valid language_boost value, and test the final audio and timestamps with the exact voice and script you plan to ship.