Home / Blog / Suno API Python Example: Generate, Poll and Download
API

Suno API Python Example: Generate, Poll and Download

Copy a Python example for the Suno API: submit a song with requests, poll the task safely, handle 401/403/429, and download the completed MP3 when ready.

Python editor and music waveform connected to a cloud music engine

Photo: Pexels

Short answer: generate music with a POST to /api/music/create, keep the returned song_id, poll /api/music/query every 3-5 seconds, and request the media URL only after the status is complete. Never put the create call inside the polling loop.

What you need

pip install requests

The correct request sequence

  1. Submit a generation to POST /api/music/create.
  2. Store the final song_id; a pending placeholder is not the final ID for querying.
  3. Poll POST /api/music/query with that ID.
  4. When the status is completed or playable, call /api/music/download-url.
  5. Download the file and copy it to your own storage if it must remain available.

Working Python example

import os
import time
import requests

BASE = "https://www.suno-api.io"
API_KEY = os.environ["SUNO_API_KEY"]
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}


def post_json(path, payload):
    response = requests.post(
        BASE + path,
        headers=HEADERS,
        json=payload,
        timeout=120,
    )
    if response.status_code == 429:
        wait = int(response.headers.get("Retry-After", "15"))
        time.sleep(wait)
        return post_json(path, payload)
    response.raise_for_status()
    return response.json()


def generate_and_download(description):
    submitted = post_json("/api/music/create", {
        "description": description,
        "model": "suno-v6",
    })
    clips = submitted.get("data", {}).get("clips") or []
    if not clips:
        raise RuntimeError("No clips in the generation response")

    song_id = clips[0]["song_id"]
    deadline = time.time() + 8 * 60

    while time.time() < deadline:
        result = post_json("/api/music/query", {"song_id": song_id})
        data = result.get("data") or {}
        status = str(data.get("status", "")).lower()
        if status in {"complete", "completed", "playable"}:
            break
        if status in {"failed", "error"}:
            raise RuntimeError(result)
        time.sleep(4)
    else:
        raise TimeoutError("Still processing; query the saved song_id later")

    media = post_json("/api/music/download-url", {
        "song_id": song_id,
        "kind": "song",
    })
    media_url = media.get("data", {}).get("url") or media.get("data", {}).get("audio_url")
    if not media_url:
        raise RuntimeError("Completed task has no media URL")

    response = requests.get(media_url, timeout=180)
    response.raise_for_status()
    with open("output.mp3", "wb") as fh:
        fh.write(response.content)
    return song_id, "output.mp3"

Error handling that avoids duplicate charges

StatusMeaningAction
401Missing or invalid API keyCheck the Authorization: Bearer header
403Balance, account or permission problemRead the error first; do not blindly submit again
429Too many requestsHonor Retry-After and back off
5xxTemporary upstream or platform failureKeep the request ID and query before retrying

Only the create endpoint starts a billed generation. Polling and media retrieval are different operations. Persist song_id and the client request ID before waiting, so a process restart does not force a second generation.

FAQ

How long should polling wait?

Usually several minutes, but load and editing complexity vary. Use status, not an arbitrary fixed delay, to decide when the song is complete.

Why is the audio URL empty?

While the task is pending, submitted or processing, media fields may be empty. Wait for the completed status.

Can I save the media URL forever?

Do not treat an upstream media URL as permanent. Download the file and copy it to storage you control when long-term delivery is required.

Continue with the Node.js example, the polling and timeout guide, the quick start, and the query API documentation.

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