Documentación
Generación de música
Generación de música a partir de texto con Suno V5 / V5.5: voces, letras y control del estilo. Dos formatos de API: un endpoint síncrono que espera hasta que las pistas estén listas y otro par asíncrono de envío y consulta (con webhooks firmados) para producción.
Endpoints: POST /v1/audio/music (síncrono, explicado primero) y POST /v1/audio/music/jobs + GET /v1/audio/music/jobs/{id} (asíncrono, recomendado para producción). Cada solicitud genera dos pistas; la generación tarda aproximadamente entre 1 y 3 minutos. Precio: $0.09 por solicitud de generación en suno-v5, $0.09 en suno-v5-5: ambas pistas se facturan una sola vez con cargo a un saldo prepagado; las generaciones fallidas no se facturan.
Inicio rápido (síncrono)
Envía un prompt y un modelo Suno. La solicitud se mantiene abierta hasta que ambas pistas estén listas y, entonces, devuelve sus URL permanentes.
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"))Parámetros
| Parámetro | Tipo | Notas |
|---|---|---|
model | cadena (obligatorio) | Un slug de modelo de música: suno-v5 o suno-v5-5. |
prompt | cadena (obligatorio) | Descripción de la música (o la letra, en modo personalizado). |
instrumental | bool | true = sin voces. Predeterminado: false. |
customMode | bool | Modo avanzado de Suno: controla explícitamente la estructura, las letras y el estilo. |
style | string | Indicación de estilo o género, por ejemplo, «lofi, jazz, chill». |
title | string | Título de la pista. |
webhook_url | cadena (https) | Solo asíncrono: envía aquí un evento terminal firmado. Consulta Webhooks. |
Modelos de música
| Slug | Proveedor | Notas |
|---|---|---|
suno-v5 | Suno | Suno V5: voces, letras y mezcla de estilos. |
suno-v5-5 | Suno | Suno V5.5: el más reciente, con mayor fidelidad y generaciones más largas. |
Consulta /models para ver la lista actualizada y los precios por llamada.
API asíncrona (enviar + consultar)
Endpoints: POST /v1/audio/music/jobs + GET /v1/audio/music/jobs/{id}. Sigue el mismo patrón que la API asíncrona de vídeo, así que el mismo código de consulta y webhook sirve para ambas modalidades.
El endpoint síncrono mantiene abierta una conexión HTTP durante varios minutos: es práctico para scripts, pero poco fiable en móviles. El par asíncrono devuelve de inmediato un identificador msc_*; consulta GET /v1/audio/music/jobs/{id} hasta que status sea completed o failed. Los modelos, los precios y las URL permanentes de CDN son los mismos.
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"])Formato de la respuesta
| Campo | Tipo | Notas |
|---|---|---|
id | string | Identificador de tarea con el prefijo msc_. |
object | string | "music". |
status | string | en cola | en curso | completada | fallida. |
model | string | El slug del modelo que enviaste. |
created_at | int | Segundos Unix en que aceptamos la solicitud. |
completed_at | int | null | Segundos Unix en que la tarea llegó a un estado terminal; null mientras está pendiente. |
expires_at | int | Segundos Unix tras los cuales se elimina el resultado (unos 30 días). |
progress | int | 0 hasta que la tarea llegue a un estado terminal; después, 100. |
output | object | null | {tracks[], archived} al completarse. |
output. | array | Cada uno: {url, stream_url, image_url}. url / image_url son enlaces permanentes de CDN. |
error | object | null | {code, message} si falla. |
Encabezados y convenciones
Idempotency-Key(opcional, ≤128 caracteres): si se envía dos veces la misma clave en la misma cuenta, se devuelve la tarea original en lugar de crear un duplicado.POST /v1/audio/music/jobsdevuelve 202 Accepted en un envío nuevo y 200 OK cuando una clave de idempotencia reproduce un resultado.GETestá limitado al propietario: consultar una tarea que pertenece a otra cuenta devuelve 404.- Frecuencia de consulta recomendada: cada 5 s, aumentando gradualmente hasta 30 s.
Webhooks
Al enviar la solicitud, proporciona webhook_url (https) y Kunavo enviará un evento terminal firmado music.completed / music.failed en cuanto termine la tarea, con reintentos y espera progresiva. El campo data del evento es exactamente el contenido de la respuesta GET anterior.
# 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... } }HMAC-SHA256 sobre {timestamp}.{rawBody} y se envía como X-Kunavo-Webhook-Signature: sha256=<hex>. El mismo procedimiento que para los webhooks de vídeo, así que un solo verificador sirve para ambos.Cuándo usar cada uno
| Síncrono /v1/ | Asíncrono /v1/ | |
|---|---|---|
| Ideal para | Scripts puntuales y notebooks | Producción, móviles y webhooks |
| Red | Mantiene la conexión abierta entre 1 y 3 min | Envía en segundos; consulta o recibe el resultado automáticamente |
| Recuperación tras fallos | Perder la respuesta = perder el resultado | Vuelve a consultar el identificador en cualquier momento antes de expires_at |
| Webhooks | — | Firmado music. |
Respuesta y almacenamiento
El url (audio) y el image_url (ilustración de portada) de cada pista son enlaces permanentes files.kunavo.com: Kunavo archiva cada resultado en su propia CDN, así que, a diferencia de las URL originales de Suno, nunca caducan. stream_url es el enlace original de streaming de Suno (útil para la reproducción inmediata). Todas las pistas también aparecen en /app/assets.
output.archived será false y las URL pasarán a ser enlaces temporales de Suno (que caducan al cabo de unas 24 h); en ese caso, vuelve a alojarlas por tu cuenta.