Create Music

August 17, 2026

Table of contents

  1. Model Comparison: Music
  2. Request Headers
  3. Request Body
  4. Responses
  5. Model
  6. Streaming
    1. Events
    2. When the stream ends without done
    3. Knowing when it is finished
    4. Playing while it generates
    5. Streaming example
    6. Playing it live with MediaSource
  7. Examples
  8. Try It

This endpoint generates a song from a style prompt and, optionally, your own lyrics. It is powered by MiniMax MusicMusic-3.0 is the default, and Music-2.6 remains available.

There are two ways to collect the result, and the response status tells you which one you got.

Mode stream Response Body
Async (default) false 201 JSON with a musicId you poll or receive by webhook
Streaming true 200 text/event-stream carrying the audio as it renders

Streaming lets a player start well before the song is finished — see Streaming. Everything under Request Body applies to both modes.

Configure a MiniMax account at POST accounts/account before calling this endpoint.

Model Comparison: Music
Feature Music-3.0 Music-2.6
Default
prompt max length 2000 2000
lyrics max length 3500 3500
title max length 40 40
Instrumental
Tracks per call 1-3 1-3
Output MP3 256 kbps
44.1 kHz stereo
MP3 256 kbps
44.1 kHz stereo

https://api.useapi.net/v1/minimax/music/create

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": "123456789012345678",
  "prompt": "warm nylon guitar, slow bossa nova, gentle brushes, about a rainy afternoon in Lisbon",
  "lyrics": "[Verse]\nThe tram climbs up the hill\n[Chorus]\nAnd the rain keeps falling still",
  "title": "Lisbon Rain",
  "model": "music-3.0",
  "quantity": 1,
  "instrumental": false,
  "stream": false,
  "replyUrl": "https://webhook.site/abc",
  "replyRef": "your-reference-id"
}
  • account is optional, if not specified API will select the account with the fewest music generations already running.
  • prompt is optional, the musical style and the subject of the song. Max length 2000.
    This one field does two jobs. Describe genre, mood, tempo, instruments and vocal type, and — if you want the lyrics to be about something in particular — say so in the same sentence. A prompt of melancholic slow piano ballad, female vocals, sparse arrangement yields lyrics about the mood itself, while the same prompt followed by , about a lighthouse keeper who lost his dog yields lyrics about exactly that.
  • lyrics is optional, your own lyrics. Max length 3500, where each letter and punctuation mark counts as one character.
    Leave it out and the model writes the lyrics for you from prompt, returning them in the response. Longer lyrics produce longer songs — this is the only way to influence duration, as there is no duration parameter.
    Structure the words with tags on their own line: [Intro], [Verse], [Pre-Chorus], [Chorus], [Hook], [Drop], [Bridge], [Solo], [Build-up], [Instrumental], [Breakdown], [Break], [Interlude], [Outro]. The model may also use tags outside this list in lyrics it writes itself.
    For wordless vocals, spell the syllables out — ah, ah, ah or la, la, la.
  • title is optional, the song name. Max length 40.
  • model is optional. Default: music-3.0. Supported values: music-3.0, music-2.6.
  • quantity is optional, how many tracks to generate from this one call. Default: 1. Supported values: 1, 2, 3.
    Each track gets its own musicId and finishes independently — they do not complete together.
  • instrumental is optional, generate music with no vocals. Default: false.
    Leaving lyrics empty is not enough to get an instrumental — without this flag the model writes its own words and sings them. Cannot be combined with lyrics.
  • stream is optional, return the audio as a Server-Sent Events stream instead of a JSON response. Default: false.
    The streaming response is 200 with Content-Type: text/event-stream, not the 201 documented below. See Streaming.
  • replyUrl is optional, we will send a POST request to this URL when generation completes. Maximum length 1024 characters. Callback body has the same JSON shape as GET music/musicId response, plus replyRef, replyUrl and code.
    If a track is still unfinished 15 minutes after it was submitted we stop tracking it and POST {"code": 400, "error": "More than 15 minutes have passed, the task has expired."} to the same URL.
  • replyRef is optional, your own reference string, echoed back on the replyUrl callback. Maximum length 1024 characters.

Notes:

  • Supply prompt, lyrics, or both. A call with neither returns 400.
  • You cannot request a length. The model decides how long each song is, and the only way to influence it is the amount of lyrics you supply — more words generally means a longer song.
  • When quantity is greater than 1 the tracks are independent songs, not variations of one length. Each gets its own duration, and they finish at different times.
  • Generation usually takes a few minutes, varying with the length of the song and how busy MiniMax is. Use replyUrl or GET music/musicId rather than assuming a fixed wait.
  • One generation per account at a time. MiniMax enforces this, so it counts songs you start on minimax.io as well as those started here, and a second request returns 429 until the first finishes.
  • Music is billed separately from video and images, at 300 credits a song. A new account has a small allowance to try it; beyond that, music needs a MiniMax audio subscription — covering music and speech — bought from MiniMax directly. Without one the call returns 412 Insufficient credits, however much video credit the account holds.
Responses
  • 201 Created

    Returned when stream is false (the default). With stream set to true the response is 200 and an event stream instead — see Streaming.

    [
      {
        "musicId": "user:12345-minimax:123456789012345678-music:987654321098765",
        "title": "Lisbon Rain",
        "idea": "warm nylon guitar, slow bossa nova, gentle brushes, about a rainy afternoon in Lisbon",
        "lyrics": "[Verse]\nThe tram climbs up the hill\n[Chorus]\nAnd the rain keeps falling still",
        "audio_url": "",
        "cover_url": "https://cdn.hailuoai.video/moss/staging/2025-06-22-16/music_cover/1700000000000000001-other_43.png",
        "model": "music-3.0",
        "status": 1,
        "statusLabel": "processing",
        "statusFinal": false,
        "instrumental": false,
        "duration": 0,
        "create_time": 1786849532637,
        "update_time": 0,
        "is_favorite": false,
        "hasWav": false,
        "tag_list": [
          {
            "tag_name": "warm nylon guitar, slow bossa nova, gentle brushes, about a rainy afternoon in Lisbon",
            "tag_type": 1
          }
        ]
      }
    ]
    
  • 400 Bad Request

    {
      "error": "Please provide prompt, lyrics, or both",
      "code": 400
    }
    

    lyrics cannot be combined with instrumental:

    {
      "error": "Parameter lyrics cannot be combined with instrumental",
      "code": 400
    }
    
  • 422 Unprocessable Content

    {
      "error": "Prompt or lyrics were rejected by content moderation",
      "code": 422
    }
    
  • 401 Unauthorized

    {
      "error": "Unauthorized",
      "code": 401
    }
    
  • 412 Precondition Failed

    {
      "error": "Insufficient credits. Please recharge to get more credits or try again next day."
    }
    
  • 429 Too Many Requests

    {
      "error": "This account already has a music generation running. Retry once it completes.",
      "code": 429
    }
    

    MiniMax allows one music generation per account at a time and reports this itself, so the limit is theirs rather than ours — it applies to songs you start on minimax.io too, not only through this API. Wait for the running track to reach statusFinal, or use a different account.

  • 504 Gateway Timeout

    {
      "error": "No response from minimax music service",
      "code": 504
    }
    

    The same status covers the other socket-level failures — Unable to open music WebSocket (HTTP nnn), Music WebSocket failed before the generation was accepted, and Music WebSocket closed before the generation was accepted. All mean the request never reached MiniMax; retry it.

  • 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."
    }
    
Model
{ // TypeScript, all fields are optional
  musicId: string,       // Use with GET music/musicId
  title: string,
  idea: string,          // Echo of the prompt you supplied
  lyrics: string,        // Yours, or written by the model when you leave it empty
  audio_url: string,     // Empty until the track completes
  cover_url: string,     // Cover art, assigned immediately
  model: string,         // music-3.0 | music-2.6
  status: number,        // See the status table on GET music/musicId
  statusLabel: string,
  statusFinal: boolean,
  instrumental: boolean,
  duration: number,      // Milliseconds, 0 until complete
  create_time: number,
  update_time: number,
  is_favorite: boolean,
  hasWav: boolean,
  tag_list: [{ tag_name: string, tag_type: number }]
}
Streaming

Set stream to true and the response is 200 with Content-Type: text/event-stream, instead of the 201 JSON documented in Responses. The audio arrives in chunks as MiniMax renders it, so a player can begin well before the song is finished.

The response carries an X-Music-Id header with the musicId — comma-separated when quantity is greater than 1 — so you can identify or poll the track even if you stop reading the stream.

Events
Event Description When Sent
progress The track record, same shape as the Model above Whenever MiniMax reports a state change
audio { "musicId": "...", "chunk": "<base64>" } — a slice of the MP3 As each chunk is rendered
done Array of the finished track records, with audio_url and duration set Once every track is complete
error { "error": "...", "code": 422 } Moderation rejected the prompt or lyrics, or MiniMax failed mid-generation — code carries the mapped HTTP status (422, 412, 429, 500)
: keepalive An SSE comment, not an event Every 10 seconds
When the stream ends without done

Two cases end the stream with neither done nor error, so do not block on done forever. The upstream connection can drop, which closes the SSE stream where it stands; and there is a hard 10 minute cap on any single stream. In both cases the generation itself keeps going — poll GET music/musicId with the musicId from the X-Music-Id header to collect the finished track.

Knowing when it is finished

Wait for the done event. It is the only signal that means the job succeeded, and it fires once every track in the call is complete. For a single track a progress event with statusFinal: true means the same thing.

Audio stops arriving before the song is finished — do not read that as completion. After the last chunk MiniMax spends up to a minute finalising the file, and only then are audio_url and duration filled in. A client that stops at the last chunk reports the song as ready while still holding duration: 0 and an empty audio_url.

That gap works in your favour for playback: you have the whole song in hand before done arrives, so you can start playing straight away and use done only to confirm success and pick up the final metadata.

Playing while it generates

Concatenate the base64 chunk values in arrival order, per musicId. The result is a plain MP3, playable as soon as you have the first chunk.

The stream is silent for the first 20 to 50 seconds while MiniMax renders the opening. Nothing is wrong — the : keepalive comments are there to stop proxies closing what looks like an idle connection, and your client should not time out during this. Show the user a “generating” state until the first audio event.

After that, playback will not stutter. Audio arrives faster than it plays, so the buffer only ever grows — by the time the first 7 seconds have played you already hold 30 or more, and the full song lands well before playback reaches the end. You do not need to pre-buffer or throttle.

Chunk sizes are uneven — anywhere from a few kilobytes to a couple of megabytes — so append whatever arrives rather than expecting a steady feed.

Streaming example
const response = await fetch("https://api.useapi.net/v1/minimax/music/create", {
  method: "POST",
  headers: {
    "Authorization": "Bearer …",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    prompt: "gentle fingerpicked acoustic guitar, warm and short",
    instrumental: true,
    stream: true
  })
});

console.log("musicId:", response.headers.get("X-Music-Id"));

const reader = response.body.getReader();
const decoder = new TextDecoder();
const chunks = [];
let buffer = "";

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  buffer += decoder.decode(value, { stream: true });
  const parts = buffer.split("\n\n");
  buffer = parts.pop();

  for (const part of parts) {
    const event = /^event: (.+)$/m.exec(part)?.[1];
    const data = /^data: (.+)$/m.exec(part)?.[1];
    if (!event || !data) continue;

    const payload = JSON.parse(data);

    if (event === "audio")
      chunks.push(Uint8Array.from(atob(payload.chunk), c => c.charCodeAt(0)));
    else if (event === "progress")
      console.log("status:", payload.statusLabel);
    else if (event === "done")
      console.log("finished:", payload[0].duration, "ms");
    else if (event === "error")
      console.error(payload.error);
  }
}

const audio = new Blob(chunks, { type: "audio/mpeg" });
document.querySelector("audio").src = URL.createObjectURL(audio);
Playing it live with MediaSource

The example above collects every chunk and plays the result at the end. To play while the song is still being generated, push each chunk into a MediaSource SourceBuffer instead.

Two details make or break this. Chunks must be appended one at a timeappendBuffer throws if the buffer is still updating — so queue them and append on updateend. And call endOfStream() when the stream finishes, otherwise the element never learns the track has ended.

<audio id="player" controls></audio>
const player = document.getElementById("player");
const mediaSource = new MediaSource();
player.src = URL.createObjectURL(mediaSource);

const queue = [];
let sourceBuffer = null;
let finished = false;

const pump = () => {
  if (!sourceBuffer || sourceBuffer.updating || queue.length === 0) {
    if (finished && sourceBuffer && !sourceBuffer.updating && queue.length === 0
        && mediaSource.readyState === "open")
      mediaSource.endOfStream();
    return;
  }
  sourceBuffer.appendBuffer(queue.shift());
};

mediaSource.addEventListener("sourceopen", async () => {
  sourceBuffer = mediaSource.addSourceBuffer("audio/mpeg");
  sourceBuffer.addEventListener("updateend", pump);

  const response = await fetch("https://api.useapi.net/v1/minimax/music/create", {
    method: "POST",
    headers: {
      "Authorization": "Bearer …",
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      prompt: "gentle fingerpicked acoustic guitar, warm and short",
      instrumental: true,
      stream: true
    })
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const parts = buffer.split("\n\n");
    buffer = parts.pop();

    for (const part of parts) {
      const event = /^event: (.+)$/m.exec(part)?.[1];
      const data = /^data: (.+)$/m.exec(part)?.[1];
      if (!event || !data) continue;

      const payload = JSON.parse(data);

      if (event === "audio") {
        queue.push(Uint8Array.from(atob(payload.chunk), c => c.charCodeAt(0)));
        pump();
        if (player.paused) player.play();
      } else if (event === "done") {
        finished = true;
        pump();
      } else if (event === "error") {
        console.error(payload.error);
        finished = true;
        pump();
      }
    }
  }
});

Playback starts as soon as the first chunk lands and runs to the end without stalling, because the stream stays ahead of the playhead.

Browser support. MediaSource with audio/mpeg works in Chrome, Edge, and Firefox. Safari’s MSE support does not cover MP3, so check MediaSource.isTypeSupported("audio/mpeg") and fall back to the collect-then-play approach shown earlier.

Examples
  • curl -X POST "https://api.useapi.net/v1/minimax/music/create" \
      -H "Authorization: Bearer …" \
      -H "Content-Type: application/json" \
      -d '{
        "prompt": "warm nylon guitar, slow bossa nova, about a rainy afternoon in Lisbon",
        "title": "Lisbon Rain",
        "quantity": 1
      }'
    
  • const response = await fetch("https://api.useapi.net/v1/minimax/music/create", {
      method: "POST",
      headers: {
        "Authorization": "Bearer …",
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        prompt: "warm nylon guitar, slow bossa nova, about a rainy afternoon in Lisbon",
        title: "Lisbon Rain",
        quantity: 1
      })
    });
    
    const tracks = await response.json();
    console.log(tracks.map(track => track.musicId));
    
  • import requests
    
    response = requests.post(
        "https://api.useapi.net/v1/minimax/music/create",
        headers={
            "Authorization": "Bearer …",
            "Content-Type": "application/json"
        },
        json={
            "prompt": "warm nylon guitar, slow bossa nova, about a rainy afternoon in Lisbon",
            "title": "Lisbon Rain",
            "quantity": 1
        }
    )
    
    for track in response.json():
        print(track["musicId"])
    
Try It

Leave stream unset for the normal JSON response: the call comes back with a musicId while the track is still rendering, which you then poll with GET music/musicId. Set stream to true and this console switches to the streaming path — it plays the song in the player below while MiniMax is still generating it. Expect a silent gap of roughly 20 to 50 seconds before the first audio arrives — the status line above the player keeps counting throughout.