Create Music
August 17, 2026
Table of contents
This endpoint generates a song from a style prompt and, optionally, your own lyrics. It is powered by MiniMax Music — Music-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
API tokenis required, see Setup useapi.net for details.
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"
}
accountis optional, if not specified API will select the account with the fewest music generations already running.promptis optional, the musical style and the subject of the song. Max length2000.
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 ofmelancholic slow piano ballad, female vocals, sparse arrangementyields lyrics about the mood itself, while the same prompt followed by, about a lighthouse keeper who lost his dogyields lyrics about exactly that.lyricsis optional, your own lyrics. Max length3500, where each letter and punctuation mark counts as one character.
Leave it out and the model writes the lyrics for you fromprompt, 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, ahorla, la, la.titleis optional, the song name. Max length40.modelis optional. Default:music-3.0. Supported values:music-3.0,music-2.6.quantityis optional, how many tracks to generate from this one call. Default:1. Supported values:1,2,3.
Each track gets its ownmusicIdand finishes independently — they do not complete together.instrumentalis optional, generate music with no vocals. Default:false.
Leavinglyricsempty is not enough to get an instrumental — without this flag the model writes its own words and sings them. Cannot be combined withlyrics.streamis optional, return the audio as a Server-Sent Events stream instead of a JSON response. Default:false.
The streaming response is200withContent-Type: text/event-stream, not the201documented below. See Streaming.replyUrlis 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/musicIdresponse, plusreplyRef,replyUrlandcode.
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.replyRefis optional, your own reference string, echoed back on thereplyUrlcallback. Maximum length 1024 characters.
Notes:
- Supply
prompt,lyrics, or both. A call with neither returns400. - You cannot request a length. The model decides how long each song is, and the only way to influence it is the amount of
lyricsyou supply — more words generally means a longer song. - When
quantityis greater than1the 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
replyUrlor 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
429until 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
-
Returned when
streamisfalse(the default). Withstreamset totruethe response is200and 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 } ] } ] -
{ "error": "Please provide prompt, lyrics, or both", "code": 400 }lyricscannot be combined withinstrumental:{ "error": "Parameter lyrics cannot be combined with instrumental", "code": 400 } -
{ "error": "Prompt or lyrics were rejected by content moderation", "code": 422 } -
{ "error": "Unauthorized", "code": 401 } -
{ "error": "Insufficient credits. Please recharge to get more credits or try again next day." } -
{ "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 differentaccount. -
{ "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, andMusic 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 time — appendBuffer 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.