Home / Blog / Suno API Batch Generation: Queue, Concurrency and Retry
API

Suno API Batch Generation: Queue, Concurrency and Retry

Build a reliable Suno API batch pipeline with bounded workers, request IDs, idempotency keys, polling, 429 backoff and restart-safe task storage.

Multiple music generation jobs running through a controlled parallel queue

Photo: Pexels

Short answer: do not create an unlimited number of threads. Give each business operation a unique Idempotency-Key, submit with accept_mode=async, persist the returned request_id, and process work through a bounded worker pool. A single key is commonly limited to five concurrent generation tasks.

Four objects in a batch system

ObjectExamplePurpose
Business batchbatch_20261027_aA group delivered together
Business itembrief_001One prompt or catalogue entry
Generation requestrequest_idAsynchronous acceptance receipt
Songsong_idQueryable and downloadable result

Queue first, then use bounded concurrency

  • Start with three or four generation workers; approach the documented limit only after observing real behaviour.
  • Keep polling workers separate from generation workers.
  • Query several song IDs in one request using the comma-separated song_ids value.
  • Estimate delivery time from concurrency multiplied by average completion time, not from a tight retry loop.

Python worker-pool example

import concurrent.futures
import requests

BASE = "https://www.suno-api.io"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}


def submit_one(item):
    response = requests.post(
        f"{BASE}/api/music/create",
        headers={
            **HEADERS,
            "Content-Type": "application/json",
            "Idempotency-Key": item["idempotency_key"],
        },
        json={
            "description": item["description"],
            "model": "suno-v6",
            "accept_mode": "async",
        },
        timeout=60,
    )
    response.raise_for_status()
    data = response.json()["data"]
    return item["brief_id"], data["request_id"], data["status"]


items = [
    {
        "brief_id": f"brief_{i:03d}",
        "idempotency_key": f"batch-20261027-{i:03d}",
        "description": f"short-film score {i}: suspense strings, no vocals",
    }
    for i in range(1, 21)
]

with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
    accepted = list(pool.map(submit_one, items))

print("accepted", len(accepted))

The submit worker stores the receipt and finishes. A separate polling worker later checks GET /api/music/requests/{request_id}. This separation makes a restart safe and prevents create calls from being repeated inside a polling loop.

Poll the acceptance receipt

def refresh(request_id):
    response = requests.get(
        f"{BASE}/api/music/requests/{request_id}",
        headers=HEADERS,
        timeout=60,
    )
    response.raise_for_status()
    data = response.json()["data"]
    for task in data.get("tasks", []):
        if task.get("song_id"):
            save_song(request_id, task["index"], task["song_id"], task["status"])
    return data["status"]

On partial, save completed task items rather than waiting for both. On failed, record the error and stop that request. Only a new business decision should create a new paid task.

Retry matrix

CaseRetry?Action
400 validation errorNoFix the input
401 / 403NoFix key or account state
402 insufficient balanceNoTop up and resume the queue
429 rate limitedYesHonor retry_after, reduce concurrency, back off
5xx temporary errorYesExponential backoff with the same idempotency key
Create timeoutCheck firstLook for an existing request or song before resubmitting

Capacity estimate

If a batch has 100 business items and each creates one generation that returns two songs, expect 100 paid generation requests and up to 200 song results. Delivery time is driven primarily by the number of concurrent generation jobs and upstream completion time, not by how often the poller runs.

FAQ

Does a two-song response count as two concurrent generations?

No. Concurrency is based on generation requests. One request is one generation job.

Can I retry immediately after a 429?

No. Wait for the retry window, lower active concurrency, and preserve the idempotency key.

How do I recover after a process restart?

Load request_id and song_id values from durable storage and continue querying. Never depend on an in-memory queue.

Related: cURL and Postman, response fields, authentication and limits, and the acceptance receipt mode.

¥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.