Short answer:
request_idtracks an asynchronous acceptance order.tasks[].song_idis the real song identifier used for status, detail and download calls.tasks[].indexonly records the position inside that request, so it is not a song ID.
The objects in one generation
| Field | What it identifies | Next use |
|---|---|---|
Idempotency-Key | Your business-level retry key | Reuse after uncertain network failures |
request_id | Platform asynchronous acceptance | Call GET /api/music/requests/{request_id} |
tasks[].index | Position inside one acceptance | Store with request_id for recovery |
song_id | Real generated song | Query status, details and download media |
client_request_id | Persisted business request reference | Trace source in detail responses |
What request progress looks like
{
"success": true,
"data": {
"request_id": "ac_2026102709000000000000000001",
"status": "processing",
"tasks": [
{
"index": 0,
"status": "processing",
"song_id": "f3fa09f5-3d55-4953-826c-0d23f043ec57"
},
{
"index": 1,
"status": "pending"
}
]
}
}The first task now has a real song ID while the second does not. Save each task independently. Never invent or copy a song ID for the unfinished slot.
Two status layers
| Layer | Status | Meaning |
|---|---|---|
| Acceptance | queued | Accepted, but songs may not be registered yet |
| Acceptance | processing | Generation is running |
| Acceptance | partial | Some songs are complete |
| Acceptance | completed | All tasks are complete and song IDs are available |
| Acceptance | failed | Acceptance failed; read error_message |
| Song | pending / processing | Still generating; media fields may be empty |
| Song | completed / playable | Ready for media retrieval |
| Song | failed | This individual song failed |
How to send song_ids
The current query contract recommends a comma-separated string:
curl -X POST "https://www.suno-api.io/api/music/query" \
-H "Authorization: Bearer $SUNO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "suno-v6",
"song_ids": "id_one,id_two"
}'The query response is normally a song array without another data envelope. Add a compatibility fallback for older envelopes, but do not infer the response shape from a fixed position.
Important result fields
| Field | Purpose | Caution |
|---|---|---|
song_id | Primary key for later calls | Persist it in your database |
status | Controls the workflow | Do not infer failure from an empty URL |
audio_url | MP3 media location | Can be empty while processing; expires |
wav_url | Prepared WAV media | Prepare it through the WAV endpoint when needed |
cover_url | Cover image | May appear only after completion |
duration | Length in seconds | Format for presentation |
parameters | Retained generation input in detail responses | Historical fields can be unavailable |
Debug checklist
- Only request_id: keep checking request progress.
- Song ID but no audio: inspect the song status first.
- Empty song query: reject
pending:placeholders and verify account scope. - Completed but no media: call the download endpoint and record the song ID if media is still absent.
- Old URL returns 404: refresh the URL; do not regenerate the song.
Minimum persistence model
generation_request(client_request_id, idempotency_key, request_id, status)
generated_song(request_id, task_index, song_id, song_status, media_url)FAQ
Can request_id download a song?
No. It identifies the acceptance workflow. Use song_id for all media operations.
Is song_ids an array or a comma-separated string?
Use the current comma-separated string form for new integrations.
Why did a song query return an empty array?
Check the ID type and account scope. The task may still be represented by a request receipt rather than a queryable song yet.
Read the related generation API, query API, and polling guide.
¥0.6 per generation, 2 tracks, unlimited downloads
Runs on the Suno V6 engine. Download MP3 or WAV as often as you like. Signing up needs an email address, not a phone number.


