Create text-to-speech audio

December 23, 2024 (August 24, 2026)

Table of contents

  1. Request Headers
  2. Request Body
  3. Model Comparison: Speech
  4. Responses
  5. Model
  6. Examples
  7. Try It

This endpoint creates an mp3 audio from the provided text and returns it in one call.

For longer text use POST speech/create, which takes 5,000 characters, or 10,000 on a turbo model.

600+ pre-built voices provided by GET speech/voices, tagged for filtering with tag_list:

  • Languages: 40, from English, Chinese (Mandarin and Cantonese), Japanese and Korean through to Thai, Hindi, Tamil and Afrikaans
  • Emotions: happy, sad, angry, fearful, disgusted, surprised, neutral, fluent
  • Accents: EN-US (General), EN-Australian, EN-British, EN-Indian, CN-Northern, CN-Southern
  • Ages: Child, Young, Middle-aged, Elderly
  • Genders: Male, Female

tag_list matches these strings exactly, including case. A tag it does not recognise is ignored rather than rejected, so a typo silently widens the result instead of narrowing it — Female,Middle-Aged returns the same 310 voices as Female alone, spanning every age. Check the count when you add a filter. GET speech/config returns the tags the picker UI offers, updated as MiniMax adds to them. Voices carry others besides — Documentary, Calm and Adult all match voices without appearing there.

https://api.useapi.net/v1/minimax/speech/create-mp3

Request Headers
Authorization: Bearer {API token}
Content-Type: application/json
# Alternatively you can use multipart/form-data
# Content-Type: multipart/form-data
Request Body
{
    "account": "Optional MiniMax www.minimax.io/audio API account",
    "text": "Required text",
    "voice_id": "Required voice id"
}
  • account is optional when only one account configured. However, if you have multiple accounts configured, this parameter becomes required.

  • text is required, the words to speak. Maximum 3000 characters whatever the model allows, which is around four minutes of speech. Supports inline markup for emotion, sounds and pauses — for example <#0.5#> inserts a half-second pause.

  • voice_id is required. Use GET speech/voices to get list of all available voices.

  • model is optional. Default: speech-2.8-hd. See Model Comparison below for the ten models, their character ceilings and their credit ratios.

  • language_boost is optional. Use tag_name from array voice_tag_language of GET speech/config.
    Default value Auto.

  • emotion is optional. Use value from array t2a_emotion of GET speech/config.
    Default value Auto.

  • vol is optional.
    Default 1.

  • speed is optional.
    Valid range: 0.5…2, default 1.

  • pitch is optional.
    Valid range: -12…12, default 0.

  • deepen_lighten is optional.
    Valid range: -100…100, default 0.

  • stronger_softer is optional.
    Valid range: -100…100, default 0.

  • nasal_crisp is optional.
    Valid range: -100…100, default 0.

  • spacious_echo is optional.
    Supported values: true, false (default).

  • lofi_telephone is optional.
    Supported values: true, false (default).

  • robotic is optional.
    Supported values: true, false (default).

  • auditorium_echo is optional.
    Supported values: true, false (default).

Model Comparison: Speech
Model Max characters Credits per character Status
speech-2.8-hd 5,000 1 Current, and the default
speech-2.8-turbo 10,000 0.6 Current
speech-2.6-hd 5,000 1 Previous generation
speech-2.6-turbo 10,000 0.6 Previous generation
speech-2.5-hd-preview 5,000 1 Superseded by 2.6
speech-2.5-turbo-preview 10,000 0.6 Superseded by 2.6
speech-02-hd 5,000 1 Legacy
speech-02-turbo 10,000 0.6 Legacy
speech-01-hd 5,000 1 Legacy
speech-01-turbo 10,000 0.6 Legacy

Use speech-2.8-hd unless you have a reason not to. The older models stay selectable because they are still live at MiniMax, and because regenerating audio to match something made earlier needs the model that made it — not because they are worth picking for new work.

Within a generation, hd and turbo are the same model tuned differently. turbo costs 40% less per character and accepts twice the text in a single call. It is not measurably faster — on identical 990-character text the two came out level — so pick turbo for the cheaper rate and the higher ceiling, and hd when voice quality matters most.

Sending more than a model accepts returns 400 before anything is generated, so an oversized request never costs credits. The message names the model and its limit — Parameter text length (5001) exceeds 5000 characters for model speech-2.8-hd — except at the absolute 10,000 ceiling, which is checked first and reports the limit without the model.

On this endpoint the per-model ceiling never comes into play — text is capped at 3,000 characters whichever model you pick, because the connection is held open for the whole generation and 3,000 characters is about as long as a caller should be asked to wait. The ceilings above apply to POST speech/create.

Responses
  • 200 OK

    Use returned audio_url to download generated mp3 audio file.
    Use returned audio_id to retrieve full details using GET speech/audio_id.

    {
        "audio_id": "user:user_id-minimax:account_id-audio:audio_id",
        "audio_length": 0,
        "audio_sample_rate": 0,
        "audio_size": 0,
        "bitrate": 0,
        "word_count": 0,
        "invisible_character_ratio": 0,
        "usage_characters": 22,
        "input_sensitive": false,
        "trace_id": "<trace id>",
        "chunks": 2,
        "subtitles": [
            { "text": "Phase one verification", "time_begin": 0, "time_end": 2245.2, "text_begin": 0, "text_end": 22, "timestamped_words": [] }
        ],
        "audio_review": 0,
        "user_id": "123456789012345678",
        "audio_title": "<autogenerated title>",
        "audio_url": "https://cdn.hailuoai.video/...mp3",
        "update_time": 123456789,
        "text": "Phase one verification",
        "voice_name": "Deep Storyteller",
        "status": 0,
        "async": 0,
        "has_srt": true,
        "has_wav": true
    }
    

    Only usage_characters is populated on the size fields — audio_length, audio_sample_rate, audio_size and bitrate all come back 0, which is what MiniMax sends.

  • 400 Bad Request

    {
      "error": "<Error message>"
    }
    
  • 401 Unauthorized

    {
      "error": "Unauthorized"
    }
    
  • 412 Precondition Failed

    The account cannot pay for the generation. MiniMax reports this two different ways, and both mean the same thing — check the speech balance with GET features, where audio.total_credit is the MiniMax audio wallet the API bills against.

    {
      "error": "Insufficient credits. Please recharge to get more credits or try again next day."
    }
    
    {
      "error": "MiniMax rejected the generation — the account is out of speech credits. Confirm with GET /features (audio.total_credit) and top up at https://www.minimax.io/audio/subscribe."
    }
    
  • 429 Too Many Requests

    The account already has its maximum number of speech generations in flight. The running generations are unaffected — retry once one finishes. The ceiling depends on the MiniMax plan. A free account allows three.

    {
      "error": "This account already has the maximum number of speech generations running. Retry once one completes.",
      "code": 429
    }
    
  • 504 Gateway Timeout

    The generation did not finish inside the 90 second ceiling this endpoint waits:

    {
      "error": "minimax did not finish within 90 seconds. Use POST /v1/minimax/speech/create for text this long.",
      "code": 504
    }
    

    Or it finished but had not appeared in the account history in time:

    {
      "error": "Audio was generated but did not appear in history in time. Call GET /v1/minimax/speech/ to retrieve it.",
      "code": 504
    }
    
  • 596 Account Error

    {
      "error": "Your minimax account has pending error. Please address this issue at https://useapi.net/docs/api-minimax-v1/post-minimax-accounts-account before making any new API calls.",
      "code": 596
    }
    
Model
{ // TypeScript, all fields are optional
    audio_id: string
    audio_length: number
    audio_sample_rate: number
    audio_size: number
    bitrate: number
    word_count: number
    invisible_character_ratio: number
    usage_characters: number
    input_sensitive: boolean
    trace_id: string
    chunks: number         // Non-heartbeat frames received, 2 for a short generation
    subtitles: {           // Per-phrase timings, milliseconds
        text: string
        time_begin: number
        time_end: number
        text_begin: number
        text_end: number
        timestamped_words: []
    }[]
    audio_review: number
    user_id: string        // The MiniMax account id
    audio_title: string
    audio_url: string
    update_time: number
    text: string
    voice_name: string
    status: number
    async: number
    has_srt: boolean       // Word timings are available for this recording
    has_wav: boolean       // A WAV rendition exists alongside the MP3
}
Examples
  • curl -H "Accept: application/json" \
         -H "Content-Type: application/json" \
         -H "Authorization: Bearer …" \
         -X POST "https://api.useapi.net/v1/minimax/speech/create-mp3" \
         -d '{"text": "…", "voice_id": "…"}'
    
  • const text = "text";      
    const voice_id = "voice_id";      
    const apiUrl = `https://api.useapi.net/v1/minimax/speech/create-mp3`; 
    const token = "API token";
    const data = { 
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json' }
    };
    data.body = JSON.stringify({ 
      text, voice_id
    });
    const response = await fetch(apiUrl, data);
    const result = await response.json();
    console.log("response", {response, result});
    
  • import requests
    text = "text"
    voice_id = "voice_id"
    apiUrl = f"https://api.useapi.net/v1/minimax/speech/create-mp3" 
    token = "API token"
    headers = {
        "Content-Type": "application/json", 
        "Authorization" : f"Bearer {token}"
    }
    body = {
        "text": f"{text}",
        "voice_id": f"{voice_id}"
    }
    response = requests.post(apiUrl, headers=headers, json=body)
    print(response, response.json())
    
Try It