Docs

Music generation

Text-to-music on Suno V5 / V5.5 — vocals, lyrics and style control. Two API shapes: a one-shot synchronous endpoint that blocks until the tracks are ready, and a submit-and-poll async pair (with signed webhooks) for production.

Endpoints: POST /v1/audio/music (synchronous, covered first), and POST /v1/audio/music/jobs + GET /v1/audio/music/jobs/{id} (async, recommended for production). Each request generates two tracks; generation runs about 1–3 minutes.

Quick start (synchronous)

Pass a prompt and a Suno model. The request holds open until both tracks are ready, then returns their permanent URLs.

import requests

# Synchronous: one request blocks until the tracks are ready (1-3 min).
resp = requests.post(
    "https://api.kunavo.com/v1/audio/music",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "suno-v5",
        "prompt": "upbeat lofi hip hop for late-night coding, mellow piano",
        "instrumental": True,
    },
    timeout=600,  # generation can take a few minutes
)
# Suno returns ~2 tracks per request.
for track in resp.json()["data"]:
    print(track["url"], track.get("image_url"))
Suno renders take 1–3 minutes, so set a generous client timeout (600s). On mobile or unreliable networks, prefer the async API — the submit returns in seconds.

Parameters

ParamTypeNotes
modelstring (required)A music model slug — suno-v5 or suno-v5-5.
promptstring (required)Description of the music (or the lyrics, in custom mode).
instrumentalbooltrue = no vocals. Default false.
customModeboolAdvanced Suno mode — drive structure / lyrics / style explicitly.
stylestringStyle / genre hint, e.g. "lofi, jazz, chill".
titlestringTrack title.
webhook_urlstring (https)Async only — push a signed terminal event here. See Webhooks.

Music models

SlugProviderNotes
suno-v5SunoSuno V5 — vocals, lyrics, style blending.
suno-v5-5SunoSuno V5.5 — newest, higher fidelity and longer generations.

Check /models for the live list and per-call pricing.

Async API (submit + poll)

Endpoints: POST /v1/audio/music/jobs + GET /v1/audio/music/jobs/{id}. Mirrors the async video API, so the same polling / webhook code works across modalities.

The sync endpoint holds an HTTP connection open for minutes — convenient for scripts but unreliable on mobile. The async pair returns immediately with a msc_* id; you poll GET /v1/audio/music/jobs/{id} until status is completed or failed. Same models, same pricing, same permanent CDN URLs.

import requests, time

# 1. Submit — returns immediately with a msc_ task id. No long-lived connection.
submit = requests.post(
    "https://api.kunavo.com/v1/audio/music/jobs",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        # Optional: retrying with the same key inside ~24h returns the
        # original task rather than submitting again.
        "Idempotency-Key": "my-song-uuid",
    },
    json={
        "model": "suno-v5",
        "prompt": "dreamy synthwave with a driving bassline",
        "title": "Midnight Drive",
    },
    timeout=60,
).json()

task_id = submit["id"]              # "msc_abc..."
status  = submit["status"]          # "queued"

# 2. Poll until terminal. Recommended cadence: 5s, backing off to 30s.
while status not in ("completed", "failed"):
    time.sleep(5)
    r = requests.get(
        f"https://api.kunavo.com/v1/audio/music/jobs/{task_id}",
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=30,
    ).json()
    status = r["status"]

if status == "failed":
    raise RuntimeError(r["error"]["message"])
for track in r["output"]["tracks"]:
    print(track["url"])

Response shape

FieldTypeNotes
idstringmsc_-prefixed task id.
objectstring"music".
statusstringqueued | in_progress | completed | failed.
modelstringThe model slug you submitted.
created_atintUnix seconds when we accepted the submit.
completed_atint | nullUnix seconds when terminal — null while pending.
expires_atintUnix seconds after which the result is pruned (~30 days).
progressint0 until terminal, then 100.
outputobject | null{tracks[], archived} on completed.
output.tracksarrayEach: {url, stream_url, image_url}. url / image_url are permanent CDN links.
errorobject | null{code, message} on failed.

Headers & conventions

  • Idempotency-Key (optional, ≤128 chars) — submitting twice with the same key on the same account returns the original task instead of creating a duplicate.
  • POST /v1/audio/music/jobs returns 202 Accepted on a fresh submit and 200 OK when an idempotency key replays a result.
  • GET is owner-scoped: querying a task that belongs to another account returns 404.
  • Recommended polling cadence: 5 s, backing off to 30 s.

Webhooks

Pass webhook_url (https) when you submit, and Kunavo POSTs a signed music.completed / music.failed event the moment the task terminates — delivered with retry-and-backoff. The event's data field is exactly the GET payload above.

# Pass webhook_url on submit to be pushed a signed event on terminal —
# instead of (or alongside) polling:
#   {"model": "suno-v5", "prompt": "...", "webhook_url": "https://you.com/hook"}
#
# Verify the HMAC signature on your receiver (Flask shown):
import hmac, hashlib
from flask import request, abort

def verify(secret: str) -> dict:
    ts  = request.headers["X-Kunavo-Webhook-Timestamp"]
    sig = request.headers["X-Kunavo-Webhook-Signature"]   # "sha256=<hex>"
    raw = request.get_data(as_text=True)
    expected = "sha256=" + hmac.new(
        secret.encode(), f"{ts}.{raw}".encode(), hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(expected, sig):
        abort(401)
    return request.get_json()

# Event body:
#   { "id": "evt_...", "object": "event",
#     "type": "music.completed" | "music.failed",
#     "created_at": 1700000000,
#     "data": { ...the GET /v1/audio/music/jobs/{id} payload... } }
The signature is HMAC-SHA256 over {timestamp}.{rawBody}, sent as X-Kunavo-Webhook-Signature: sha256=<hex>. Same recipe as the video webhooks, so one verifier handles both.

When to use which

Sync /v1/audio/musicAsync /v1/audio/music/jobs
Best forOne-off scripts, notebooksProduction, mobile, webhooks
NetworkHolds connection 1-3 minSubmit in seconds; poll or get pushed
Failure recoveryLose the response = lose the resultRe-query the id any time before expires_at
WebhooksSigned music.completed / music.failed

Response & storage

Each track's url (audio) and image_url (cover art) are permanent files.kunavo.com links — Kunavo archives every result to its own CDN, so unlike the raw Suno URLs they never expire. stream_url is Suno's original streaming link (handy for instant playback). Every track also shows up in /app/assets.

If archiving ever fails, output.archived is false and the URLs fall back to Suno's temporary links (which expire after ~24h) — re-host them yourself in that case.

Where to go next