This guide shows you how to generate a full song — vocals, instruments, and structure — from a style prompt and lyrics using minimax-music-1.5 on hiapi's unified task API. Everything below was verified against the live endpoint, so you can copy, paste, and run it.
What you're building, and what you need
The goal: submit a text description of a musical style plus your lyrics, and get back a downloadable audio track.
You need exactly one thing: a hiapi API key. Grab it from your dashboard — it starts with sk-. All examples below assume it's in an environment variable:
export HIAPI_KEY="sk-your-key-here"
Music generation on hiapi runs through the same async task interface as every other model: POST /v1/tasks to create a job, then either poll GET /v1/tasks/<taskId> or receive a callback when it finishes.
The input schema (verified)
minimax-music-1.5 accepts these fields inside input — and rejects anything else with a 400:
| Field | Required | Type | Constraints |
|---|---|---|---|
prompt | yes | string | 10–300 characters. Describes genre, mood, instrumentation, vocal style. |
lyrics | yes | string | 10–600 characters. The words to sing; supports section tags like [Verse] and [Chorus]. |
sample_rate | no | integer | One of 16000, 24000, 32000, 44100. |
bitrate | no | integer | One of 32000, 64000, 128000, 256000. |
Two things trip people up:
- Both
promptandlyricsare required. The prompt controls how it sounds; the lyrics control what is sung. Omit either and you getinvalid input: ... missing required field. - The schema is strict. Fields you might expect from other music APIs —
duration,seed,format,instrumental— are not accepted. Sending them returnsadditional properties ... not allowed.
Minimal working example: curl
Create the task:
curl -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "minimax-music-1.5",
"input": {
"prompt": "Uplifting acoustic pop, warm female vocals, gentle guitar and light percussion",
"lyrics": "[Verse]\nMorning light on a quiet street\nCoffee steam and a steady beat\n[Chorus]\nWe keep on going, we find our way\nOne small step at a time, every day",
"sample_rate": 44100,
"bitrate": 256000
}
}'
A successful create returns a task id:
{"code": 200, "data": {"taskId": "task_abc123..."}}
Then poll until the task reaches a terminal state:
curl https://api.hiapi.ai/v1/tasks/task_abc123 \
-H "Authorization: Bearer $HIAPI_KEY"
While the job is running you'll see a non-terminal status; when it finishes, status becomes success and the audio URL appears in output:
{
"code": 200,
"data": {
"taskId": "task_abc123",
"status": "success",
"output": [{"url": "https://.../track.mp3"}]
}
}
Download the file promptly — output URLs are time-limited, so treat them as a pickup window, not permanent storage:
curl -o track.mp3 "https://.../track.mp3"
Full Python script: submit, poll, download
import os
import time
import requests
API_BASE = "https://api.hiapi.ai/v1/tasks"
HEADERS = {
"Authorization": f"Bearer {os.environ['HIAPI_KEY']}",
"Content-Type": "application/json",
}
def create_task() -> str:
payload = {
"model": "minimax-music-1.5",
"input": {
"prompt": ("Uplifting acoustic pop, warm female vocals, "
"gentle guitar and light percussion"),
"lyrics": ("[Verse]\nMorning light on a quiet street\n"
"Coffee steam and a steady beat\n"
"[Chorus]\nWe keep on going, we find our way\n"
"One small step at a time, every day"),
"sample_rate": 44100,
"bitrate": 256000,
},
}
r = requests.post(API_BASE, headers=HEADERS, json=payload, timeout=60)
data = r.json()
task_id = (data.get("data") or {}).get("taskId")
if not task_id:
raise RuntimeError(f"create failed: {data}")
return task_id
def wait_task(task_id: str, timeout_s: int = 600, poll_s: int = 5) -> dict:
deadline = time.time() + timeout_s
while time.time() < deadline:
r = requests.get(f"{API_BASE}/{task_id}", headers=HEADERS, timeout=30)
task = (r.json().get("data") or {})
status = task.get("status")
if status == "success":
return task
if status == "fail":
err = task.get("error") or {}
raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
time.sleep(poll_s)
raise TimeoutError(f"task {task_id} still running after {timeout_s}s")
def download(url: str, path: str = "track.mp3") -> str:
r = requests.get(url, timeout=120)
r.raise_for_status()
with open(path, "wb") as f:
f.write(r.content)
return path
if __name__ == "__main__":
task_id = create_task()
print(f"submitted: {task_id}")
task = wait_task(task_id)
url = task["output"][0]["url"]
print(f"done, downloading: {url}")
print(f"saved to {download(url)}")
Run it with python3 make_song.py. The script submits the job, polls every 5 seconds, and saves the finished track locally.
Production patterns
Callbacks instead of polling
For anything beyond a quick script, register a callback so hiapi pushes the result to you when the task hits a terminal state. Add a top-level callback object (it sits next to model, not inside input):
{
"model": "minimax-music-1.5",
"input": {"prompt": "...", "lyrics": "..."},
"callback": {"url": "https://your.domain.example/hiapi/callback", "when": "final"}
}
Only "when": "final" is supported — you get one POST when the task succeeds or fails, not incremental progress events. Polling is fine for development and one-off jobs; callbacks win when you're generating many tracks or running behind a queue, because you hold no open loops and hit no rate limits from tight polling. If your callback never arrives, work through the checklist in why your hiapi task callback isn't firing.
Idempotency and retries
Task creation is not idempotent by itself: retrying a timed-out POST /v1/tasks can create two jobs (and two charges). Persist the taskId as soon as the create call returns, and on restart resume by polling that id rather than blindly re-submitting. If a task seems stuck, don't kill and re-create it immediately — see when a hiapi /v1/tasks job hangs or times out for how to tell a slow job from a dead one.
Error handling
The two failure surfaces look different:
- Request-level errors come back synchronously. A bad or unauthorized key returns a
permission_deniederror ("This API key cannot use the selected model...") with arequest_idyou can quote to support. Schema violations return400 INVALID_REQUESTwith a precise message, e.g.prompt: minLength: got 1, want 10. - Task-level failures show up in the poll/callback result as
status: "fail"with anerror.codeanderror.message— the create call succeeded, but generation didn't. These are the ones to route to retry logic.
Validate lengths client-side before submitting: prompt maxes out at 300 characters and lyrics at 600, and the API rejects oversized inputs rather than truncating them.
Pricing and limits
MiniMax Music 1.5 is billed per generated track on usage-based pricing — check the current rate on the hiapi pricing page and the minimax-music-1.5 model page, where you can also try the model in the playground before writing any code.
FAQ
What input fields does minimax-music-1.5 accept?
Exactly four: prompt (required, 10–300 chars), lyrics (required, 10–600 chars), sample_rate (optional, 16000/24000/32000/44100), and bitrate (optional, 32000/64000/128000/256000). Anything else is rejected with additional properties not allowed.
Can I control song structure?
Yes — structure your lyrics with section tags like [Verse] and [Chorus]. The model uses them to shape the arrangement. Keep the whole lyrics string within the 600-character limit, tags included.
Can I generate instrumental-only music?
The input schema has no instrumental switch. The endpoint is lyrics-driven, so describe the instrumentation and mood you want in prompt; for purely instrumental use cases, check the model page for the current capability list.
How do I get the audio file?
When the task status is success, data.output[0].url holds the download link. Fetch it right away and store the bytes yourself — output URLs expire.
Should I poll or use a callback?
Poll for development and single jobs (a 5-second interval is plenty). Use callback with when: "final" in production so you don't hold connections open — especially if you batch-generate tracks.
Why am I getting permission_denied?
Your key is invalid, or it doesn't have access to this model. Verify the key in your dashboard and make sure you're sending it as Authorization: Bearer sk-....



