مستندات المطور UlazAI
Suno API
One REST API for Suno music generation: submit a job, poll it, download the audio. UlazAI exposes 23 operations on six Suno models, priced in purchased credits, with idempotent submission and private storage for generated media. UlazAI is an independent integration, not Suno’s official API. The catalog route returns the same field specs as data.
- Base URL
https://ulazai.com/api/v1/music/studio- Authentication
- API key in the
Authorization: Bearerheader. The catalog needs no key. - Request format
- JSON with snake_case field names, or multipart for uploads.
- Credits
- Purchased credits only. Failed jobs are refunded automatically.
To call the API you need a verified email address, an API key from your dashboard and credits from the credit packages page. Prefer pointing and clicking? The Music Studio is the same engine without code, and the older audio docs stay available for the legacy music routes.
One of the two takes returned by the example below. Generated and downloaded through this API.
Three calls to your first song
Generate a 20-second instrumental with Suno V5.5. The complete examples below handle submission, polling and downloading for you.
- Submit the song. Send
POST /jobs/with your title, style and a request UUID. Save the returned job ID. Reuse that UUID when retrying the same request. - Wait for the audio. Check
GET /jobs/{id}/every five seconds until the status iscompleted. Stop onfailedorawaiting_user. - Download each take. Use each track's
audio_url. Authenticate the UlazAI request, then follow the signed download URL without forwarding your API key.
Choose a complete curl, Python or Node example
Example response after submission
A successful submit returns 201 with the new job. The track list fills when generation completes.
{
"success": true,
"data": {
"id": "3c605fd4-f298-4d19-b5c3-0683d1afa6f7",
"status": "processing",
"operation": "generate",
"model": "V5_5",
"credits_used": 12,
"tracks": []
}
}
Generation takes minutes, not seconds. The job keeps running on the server even if you
stop polling, so your script can hit its polling limit, walk away and pick the job up
later with the same GET call.
Keep the API key on your server
The key authenticates every request as you and spends your credits. Read it from an environment variable in server-side code. A key embedded in a browser app, mobile app or public repository is a key anyone can use to drain your balance; rotate it in the dashboard the moment that happens.
Downloads redirect to a short-lived signed storage URL. Do not send your
Authorization header to that host: the signed URL already carries its own
credentials, and forwarding your key alongside it leaks the key to storage logs and
can cause authentication failures. Resolve the redirect first, then fetch the target without
the header. Python and Node resolve the redirect explicitly; curl automatically removes the header when the host changes.
Header formats and programmatic key creation are covered in the authentication docs.
Credits per operation
Music Studio and this API spend purchased credits only. Welcome credits cannot pay for music jobs. Credits are charged when the job is accepted, and a failed job is refunded automatically, exactly once, without a support request.
| Operation | Credits | Notes |
|---|---|---|
generate |
12 | Create a song |
extend |
12 |
Extend a track
needs a source job (see operation rules) |
upload_cover |
12 |
Cover uploaded audio
needs an upload |
upload_extend |
12 |
Extend uploaded audio
needs an upload |
add_vocals |
12 |
Add vocals
needs an upload |
add_instrumental |
12 |
Add instrumental
needs an upload |
replace_section |
6 |
Replace a section
needs a source take or an upload |
mashup |
12 |
Mashup two tracks
needs 2 uploads |
stems |
10
by mode: vocals + instrumental 10, all stems (up to 12) 50, one specific stem 20 |
Separate stems
needs a source take or an upload |
wav |
1 |
Convert to WAV
needs a source job (see operation rules) |
midi |
0 |
Generate MIDI
free, shares the 25-per-day free cap needs a source job (see operation rules) |
lyrics |
1 | Write lyrics |
timestamped_lyrics |
1 |
Timestamped lyrics
can complete during submission; inspect the returned status needs a source job (see operation rules) |
style_boost |
1 |
Boost a style description
can complete during submission; inspect the returned status |
cover_art |
0 |
Cover art
free, shares the 25-per-day free cap needs a source job (see operation rules) |
music_video |
2 |
Make a music video
needs a source job (see operation rules) |
sounds |
3 | Generate a sound |
recovery |
0 |
Recover lost audio
free, shares the 25-per-day free cap needs a source job (see operation rules) |
voice_validate |
0 |
Start custom voice
free, shares the 25-per-day free cap needs an upload |
voice_regenerate |
0 |
New phrase for existing attempt
free, shares the 25-per-day free cap needs a source job (see operation rules) |
voice_generate |
0 |
Finish custom voice
free, shares the 25-per-day free cap needs a source job (see operation rules) needs an upload |
persona |
0 |
Persona
free, shares the 25-per-day free cap can complete during submission; inspect the returned status needs a source job (see operation rules) |
voice_check |
0 |
Check custom voice
free, shares the 25-per-day free cap can complete during submission; inspect the returned status needs a source job (see operation rules) |
Operations priced at 0 credits share one cap: 25 free jobs per account per day. Paid operations require sufficient purchased credits; provider availability can still limit requests.
Six Suno models
Pass the id in the model field. V5.5 is the default for new work and the only
model that reads duration. V4 is the odd one out for style descriptions: it
accepts 200 characters where every other model accepts 1000.
| Id | Model | What to expect |
|---|---|---|
V5_5 |
Suno V5.5 | Newest model. Supports custom duration. |
V5 |
Suno V5 | Superior expression, faster generation. |
V4_5PLUS |
Suno V4.5+ | Richer sound, tracks up to 8 minutes. |
V4_5ALL |
Suno V4.5 All | Smart prompts, faster generations. |
V4_5 |
Suno V4.5 | Smart prompts, faster generations. |
V4 |
Suno V4 | Improved vocals, tracks up to 4 minutes. Style descriptions cap at 200 characters. |
Seven routes, one namespace
| Method | Route | Purpose |
|---|---|---|
| GET | https://ulazai.com/api/v1/music/studio/catalog/ |
Public. Models, operations, fields and credit prices. No key needed. |
| POST | https://ulazai.com/api/v1/music/studio/jobs/ |
Submit an operation. Body: operation, request_id plus catalog fields. |
| GET | https://ulazai.com/api/v1/music/studio/jobs/ |
Your library, 25 jobs per page. Pass ?cursor= with the next value to keep reading. |
| GET | https://ulazai.com/api/v1/music/studio/jobs/<uuid>/ |
Job status and result. Poll this route. |
| PATCH | https://ulazai.com/api/v1/music/studio/jobs/<uuid>/ |
Rename a track (title) or toggle is_favorite. |
| POST | https://ulazai.com/api/v1/music/studio/upload/ |
Multipart upload of your own audio; returns the reference for the upload field. |
| GET | https://ulazai.com/api/v1/music/studio/jobs/<uuid>/media/<kind>/<index>/ |
Private download. kind is track, image or asset; index is zero-based. |
There are no customer webhooks. The provider callback route in this namespace is internal infrastructure with a signed token; you cannot register a URL and should not try to call it. Polling the job route is the supported integration path, and jobs also complete while you are not polling.
The catalog is open
GET https://ulazai.com/api/v1/music/studio/catalog/ needs no key and returns models, operations,
field specs and credit prices. Machine-readable field specs mean a client can render its
own forms without hardcoding this page. Pass ?language= with a supported
language code to localise the labels.
curl --fail-with-body -sS "https://ulazai.com/api/v1/music/studio/catalog/"
# Optional: Dutch field labels.
curl --fail-with-body -sS "https://ulazai.com/api/v1/music/studio/catalog/?language=nl"
Submit a job
Every operation goes through POST https://ulazai.com/api/v1/music/studio/jobs/ with the same
envelope: operation, request_id and the operation's fields in
snake_case. This example body matches the complete scripts below:
{
"operation": "generate",
"request_id": "a20e890f-2e90-550b-9c28-1d48c531385c",
"custom_mode": true,
"instrumental": true,
"title": "Moonlit platform",
"style": "Gentle solo piano, warm analog texture",
"model": "V5_5",
"duration": 20
}request_id makes retries safe
Generate a UUID once per logical submission and store it. Resending the request with the
same request_id returns the original job instead of charging again, which is
what you want after a timeout. Sending a different body under the same
request_id fails with 409 request_id_conflict.
Order of checks, before any charge
- Field validation against the catalog: types, limits, required and conditional fields.
- Ownership checks:
source_idand upload references must be jobs and files from your own library. - Prompt moderation on the text fields. Rejected prompts return
400and nothing is charged. - Credit balance. Shortfalls return
402 insufficient_creditswith the amount needed.
Derived operations build on earlier results. source_id is the UUID of a
completed job in your library; audio_id is the id of one take or asset inside
that job's result, found in its tracks[].id or assets[].id. The
two are easy to mix up: source_id selects the job, audio_id selects the take.
Generate, poll, download
Each script submits the song, checks its status and downloads every take. Keep your API key in the ULAZAI_API_KEY environment variable on your server.
Create one request UUID before running a script. Keep it unchanged for retries:
export ULAZAI_REQUEST_ID="$(python3 -c 'import uuid; print(uuid.uuid4())')"curl · complete shell script
Requires bash, curl and jq.
#!/usr/bin/env bash
set -euo pipefail
: "${ULAZAI_API_KEY:?Set your API key in the environment}"
: "${ULAZAI_REQUEST_ID:?Set one UUID and keep it for retries}"
BASE=https://ulazai.com/api/v1/music/studio
# Requires curl and jq. Keep both the UUID and payload unchanged on retry.
payload=$(jq -n --arg id "$ULAZAI_REQUEST_ID" '{operation:"generate",request_id:$id,custom_mode:true,instrumental:true,title:"Moonlit platform",style:"Gentle solo piano, warm analog texture",model:"V5_5",duration:20}')
curl --fail-with-body -sS "$BASE/jobs/" \
-H "Authorization: Bearer $ULAZAI_API_KEY" \
-H "Content-Type: application/json" --data "$payload" > music-job.json
jq -e '.success == true' music-job.json >/dev/null
JOB_ID=$(jq -r '.data.id' music-job.json)
echo "Job: $JOB_ID"
for attempt in $(seq 1 120); do
status=$(jq -r '.data.status' music-job.json)
case "$status" in completed|failed|awaiting_user) break;; esac
sleep 5
curl --fail-with-body -sS "$BASE/jobs/$JOB_ID/" \
-H "Authorization: Bearer $ULAZAI_API_KEY" > music-job.json
jq -e '.success == true' music-job.json >/dev/null
done
if [ "$(jq -r '.data.status' music-job.json)" != completed ]; then
echo "Job $JOB_ID is not completed. Resume with GET; keep the UUID for submit retries." >&2
exit 1
fi
index=0
while IFS= read -r media_path; do
case "$media_path" in
/api/v1/music/studio/jobs/*/media/*) ;;
*) echo "Legacy external media URL: download it separately without your API key." >&2; exit 1;;
esac
index=$((index + 1))
# curl strips Authorization on cross-host redirects. Never add --location-trusted.
curl --fail-with-body -sSL "https://ulazai.com$media_path" \
-H "Authorization: Bearer $ULAZAI_API_KEY" -o "take-$index.mp3"
echo "Saved take-$index.mp3"
done < <(jq -r '.data.tracks[].audio_url' music-job.json)
Python · standard library only
Requires Python 3. No packages to install.
import json
import os
import time
import urllib.error
import urllib.request
from pathlib import Path
from urllib.parse import urljoin, urlsplit
BASE = "https://ulazai.com/api/v1/music/studio/"
KEY = os.environ["ULAZAI_API_KEY"]
REQUEST_ID = os.environ["ULAZAI_REQUEST_ID"] # keep this UUID for retries
def call(method, path, payload=None):
request = urllib.request.Request(
urljoin(BASE, path), method=method,
data=json.dumps(payload).encode() if payload is not None else None,
headers={"Authorization": "Bearer " + KEY, "Content-Type": "application/json", "User-Agent": "UlazAI-Music-Example/1.0"},
)
with urllib.request.urlopen(request, timeout=60) as response:
body = json.load(response)
if not body.get("success"):
raise RuntimeError(body.get("error") or body.get("detail") or "API request failed")
return body["data"]
payload = {
"operation": "generate", "request_id": REQUEST_ID,
"custom_mode": True, "instrumental": True,
"title": "Moonlit platform", "style": "Gentle solo piano, warm analog texture",
"model": "V5_5", "duration": 20,
}
job = call("POST", "jobs/", payload)
print("Job:", job["id"], flush=True)
for _ in range(120):
if job["status"] in {"completed", "failed", "awaiting_user"}:
break
time.sleep(5)
job = call("GET", "jobs/" + job["id"] + "/")
if job["status"] != "completed":
raise RuntimeError(f"Job {job['id']}: {job['status']}. {job.get('error_message', '')} Resume with GET; keep the same request_id for submit retries.")
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *args, **kwargs):
return None
opener = urllib.request.build_opener(NoRedirect)
for index, track in enumerate(job["tracks"], start=1):
url = urljoin(BASE, track["audio_url"])
if urlsplit(url).scheme != "https" or urlsplit(url).netloc != urlsplit(BASE).netloc:
raise ValueError("Legacy external media URL: download it separately without your API key.")
request = urllib.request.Request(url, headers={"Authorization": "Bearer " + KEY, "User-Agent": "UlazAI-Music-Example/1.0"})
try:
audio = opener.open(request, timeout=60)
except urllib.error.HTTPError as error:
if error.code not in {301, 302, 303, 307, 308} or not error.headers.get("Location"):
raise
# Storage URLs carry their own signature. Send no API key to storage.
audio = urllib.request.urlopen(urllib.request.Request(urljoin(url, error.headers["Location"]), headers={"User-Agent": "UlazAI-Music-Example/1.0"}), timeout=120)
with audio, Path(f"take-{index}.mp3").open("wb") as output:
while chunk := audio.read(65536):
output.write(chunk)
print(f"Saved take-{index}.mp3", flush=True)
Node · built-in fetch
Requires Node 20 or later.
import { writeFile } from "node:fs/promises";
const BASE = "https://ulazai.com/api/v1/music/studio/";
const key = process.env.ULAZAI_API_KEY;
const requestId = process.env.ULAZAI_REQUEST_ID; // keep this UUID for retries
if (!key || !requestId) throw new Error("Set ULAZAI_API_KEY and ULAZAI_REQUEST_ID.");
const auth = { Authorization: `Bearer ${key}` };
async function call(path, payload) {
const response = await fetch(new URL(path, BASE), {
method: payload ? "POST" : "GET",
headers: { ...auth, "Content-Type": "application/json" },
body: payload ? JSON.stringify(payload) : undefined,
signal: AbortSignal.timeout(60000),
});
const body = await response.json();
if (!response.ok || !body.success) {
throw new Error(`HTTP ${response.status}: ${body.error || body.detail || "API request failed"}`);
}
return body.data;
}
let job = await call("jobs/", {
operation: "generate", request_id: requestId,
custom_mode: true, instrumental: true,
title: "Moonlit platform", style: "Gentle solo piano, warm analog texture",
model: "V5_5", duration: 20,
});
console.log("Job:", job.id);
for (let i = 0; i < 120 && !["completed", "failed", "awaiting_user"].includes(job.status); i++) {
await new Promise(resolve => setTimeout(resolve, 5000));
job = await call(`jobs/${job.id}/`);
}
if (job.status !== "completed") {
throw new Error(`Job ${job.id}: ${job.status}. ${job.error_message || ""} Resume with GET; keep the same request_id for submit retries.`);
}
for (const [index, track] of job.tracks.entries()) {
const url = new URL(track.audio_url, BASE);
if (url.origin !== new URL(BASE).origin) {
throw new Error("Legacy external media URL: download it separately without your API key.");
}
let audio = await fetch(url, { headers: auth, redirect: "manual", signal: AbortSignal.timeout(60000) });
if ([301, 302, 303, 307, 308].includes(audio.status)) {
const location = audio.headers.get("Location");
if (!location) throw new Error("Missing media redirect.");
// Storage URLs carry their own signature. Send no API key to storage.
audio = await fetch(new URL(location, url), { signal: AbortSignal.timeout(120000) });
}
if (!audio.ok) throw new Error(`Download HTTP ${audio.status}`);
await writeFile(`take-${index + 1}.mp3`, Buffer.from(await audio.arrayBuffer()));
console.log(`Saved take-${index + 1}.mp3`);
}
The scripts stop polling after 10 minutes on purpose. A track that still needs another
minute is not lost: fetch the job again later and download when the status says
completed.
Upload your own audio
Cover versions, extensions, vocal and instrumental additions and mashups start from your
own file. Send it as multipart form data to the upload route, then reference it in the
job. MP3, WAV, M4A, OGG and FLAC up to 50 MiB are accepted. Send the matching audio MIME type; the MP3 example explicitly sets audio/mpeg.
# Run in bash with curl and jq. Use an audio file you own.
curl --fail-with-body -sS "https://ulazai.com/api/v1/music/studio/upload/" \
-H "Authorization: Bearer $ULAZAI_API_KEY" \
-F "[email protected];type=audio/mpeg" > music-upload.json
UPLOAD_REFERENCE=$(jq -er '.data.upload_url' music-upload.json)
The response field is called upload_url, but it is a reference to your stored
file, not a destination. Pass that value in the upload field of the job
(and upload2 for the second file of a mashup):
# Create this UUID once for the cover, then keep it for retries.
# Use a different UUID from the quickstart's generation.
export ULAZAI_COVER_REQUEST_ID="$(python3 -c 'import uuid; print(uuid.uuid4())')"
payload=$(jq -n --arg id "$ULAZAI_COVER_REQUEST_ID" --arg upload "$UPLOAD_REFERENCE" \
'{operation:"upload_cover",request_id:$id,upload:$upload,custom_mode:true,instrumental:true,title:"Warehouse bounce",style:"industrial techno, driving percussion",model:"V5_5"}')
curl --fail-with-body -sS "https://ulazai.com/api/v1/music/studio/jobs/" \
-H "Authorization: Bearer $ULAZAI_API_KEY" \
-H "Content-Type: application/json" --data "$payload"
# Read data.id and poll /jobs/<returned-id>/ as in the quickstart.
Rename a track or mark a favourite
PATCH changes the title and favourite flag of a job you own.
The title is capped at 120 characters and cannot be empty; is_favorite takes
a real boolean.
# JOB_ID is the data.id returned by your generation, saved in music-job.json.
JOB_ID=$(jq -er '.data.id' music-job.json)
curl --fail-with-body -sS -X PATCH "https://ulazai.com/api/v1/music/studio/jobs/$JOB_ID/" \
-H "Authorization: Bearer $ULAZAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Moonlit platform (final)","is_favorite":true}'
The job object
Submit and job-detail responses wrap one job in {"success": true, "data": {...}}. The
library route wraps a list plus a cursor and returns 25 jobs per page; pass the
next value, URL-encoded, as ?cursor= to keep reading. Songs from before the
Music Studio appear in the library too, marked with is_legacy: true. Their audio URLs may point directly to an external host. Download those separately without your UlazAI API key; the authenticated generation examples reject external media URLs before sending any credentials. Legacy items are read-only and cannot serve as sources for new studio operations.
| Field | Meaning |
|---|---|
id |
UUID of the job. Use it to poll and to download media. |
music_id |
Internal provider task reference. Keep it for support questions; you never need it to call the API. |
title |
Track title. |
prompt |
The idea or lyrics the job was created with. |
style |
Style description the job was created with. |
instrumental |
true when the job requested a track without vocals. |
model |
Model id, e.g. V5_5. |
operation |
Operation id, e.g. generate. |
status |
pending, processing, awaiting_user, completed or failed. |
credits_used |
Credits charged. 0 for free operations. |
created_at |
ISO-8601 timestamp in UTC. |
error_message |
Sanitized error message, filled on failed jobs. |
tracks |
Generated takes, usually two for generate. Each has id, title, audio_url, image_url, duration (seconds). |
assets |
Extra outputs such as stems, WAV, MIDI, cover art or the music video. Each has label, url, type and sometimes id. |
text |
Text output: lyrics, timestamped lyrics, boosted style, or the phrase to record for a custom voice. |
is_favorite |
Your favourite flag, toggled with PATCH. |
voice_id |
Persona or custom voice id when the job created one. |
source_id |
The source job's UUID when this job built on an earlier track; otherwise null. |
For new Music Studio jobs, media URLs in tracks and assets are relative API routes, stable
for the life of the job. They answer authenticated requests with the file directly or a redirect to a signed
URL that expires in minutes, which is why scripts fetch them fresh at download time
instead of storing the links.
Five job states
| Status | Meaning |
|---|---|
pending |
Accepted and charged; submission to the provider has not been confirmed yet. |
processing |
The provider is working. Keep polling. |
awaiting_user |
Custom voice flow: the job's text holds the phrase to record. Continue with the voice operations below. |
completed |
Result stored in private storage. Download via the media routes. |
failed |
Something failed. Credits are refunded exactly once and error_message says what, as a sanitized message. |
Refunds are automatic on failure. If a submit times out ambiguously, the charge stays in place while the server keeps verifying; if no result ever turns up, the job fails and the refund follows on its own. Keep the job ID while reconciliation is pending; a timeout is not proof that generation failed.
All 23 operations
Each operation below lists its field table straight from the live catalog, plus the rules
the API actually enforces. R marks a required field.
generateextendupload_extendupload_coveradd_vocalsadd_instrumentalreplace_sectionmashupsoundsstemswavmidilyricstimestamped_lyricsstyle_boostcover_artmusic_videovoice_validatevoice_regeneratevoice_generatevoice_checkpersonarecovery
Create a song
Create a song generate 12 credits
Generate an original song from an idea or your own lyrics. Two takes are generated; both stay in your library.
Rules the API enforces
- Idea mode (custom_mode off): prompt is required and capped at 3000 characters; model and instrumental still apply; title, style and detailed generation controls are not used.
- Detailed controls on and instrumental on: style and title are required.
- Detailed controls on and instrumental off: style, title and prompt are required.
- duration is only read by V5_5 with custom_mode on. Leave it out otherwise. Valid range: 10-360 seconds.
- persona_id needs custom_mode on and model V5 or V5_5, and must reference a persona or custom voice from your own library.
- V4 caps style at 200 characters; the other models allow 1000.
| Field | R | Constraints | Notes |
|---|---|---|---|
prompt |
– |
string
max 5000 characters |
A description of the song, or your own lyrics in custom mode. |
custom_mode |
– |
boolean
true or false (JSON boolean) defaults to off |
On: set style, title and lyrics yourself. Off: describe the song and go. |
title |
– |
string
max 80 characters |
|
style |
– |
string
max 1000 characters |
Genre and mood, e.g. 'upbeat pop, summer vibes'. |
instrumental |
– |
boolean
true or false (JSON boolean) defaults to on |
|
model |
yes |
string (enum)
one of the six model ids, see the model table defaults to V5_5 |
|
duration |
– |
number
10 to 360 |
Only used by V5.5. Leave empty for automatic length. |
negative_tags |
– |
string
max 500 characters |
Styles to keep out, e.g. 'heavy metal'. |
vocal_gender |
– |
string (enum)
one of: m, f |
A preference, not a guarantee. |
style_weight |
– |
number
0 to 1, step 0.01 |
How strictly to follow the style (0-1). |
weirdness_constraint |
– |
number
0 to 1, step 0.01 |
How experimental the result may be (0-1). |
audio_weight |
– |
number
0 to 1, step 0.01 |
Balance audio features against other input (0-1). |
persona_id |
– | string | Optional persona or custom voice from your library (V5/V5.5). |
persona_model |
– |
string (enum)
one of: style_persona, voice_persona |
Only for V5 and V5.5. |
Continue existing material
Extend a track extend 12 credits
Continue one of your generated tracks from a chosen point.
Builds on an eligible job via source_id.
Allowed source operations:
generate or extend or upload_cover or upload_extend or add_vocals or add_instrumental or replace_section or mashup or sounds
(see the state requirements below).
Rules the API enforces
- audio_id and continue_at are required. continue_at must land before the take ends.
- default_param_flag defaults to true: pass new prompt, style and title. Set it false to inherit style and lyrics from the source take.
| Field | R | Constraints | Notes |
|---|---|---|---|
audio_id |
yes | string | Select the take to continue from its detail view. |
continue_at |
yes |
number
at least 0 |
|
default_param_flag |
– |
boolean
true or false (JSON boolean) defaults to on |
Off: inherit style and lyrics from the original take. |
prompt |
– |
string
max 5000 characters |
|
title |
– |
string
max 80 characters |
|
style |
– |
string
max 1000 characters |
|
model |
yes |
string (enum)
one of the six model ids, see the model table defaults to V5_5 |
|
instrumental |
– |
boolean
true or false (JSON boolean) |
|
negative_tags |
– |
string
max 500 characters |
|
vocal_gender |
– |
string (enum)
one of: m, f |
|
style_weight |
– |
number
0 to 1, step 0.01 |
How strictly to follow the style (0-1). |
weirdness_constraint |
– |
number
0 to 1, step 0.01 |
How experimental the result may be (0-1). |
audio_weight |
– |
number
0 to 1, step 0.01 |
Balance audio features against other input (0-1). |
Extend uploaded audio upload_extend 12 credits
Upload your own audio and continue it with generated material.
Needs 1 uploaded file via the upload field.
Rules the API enforces
- upload and continue_at are required.
- default_param_flag defaults to true: pass prompt, style and title. Set it false to keep the uploaded take's own material and just continue it.
| Field | R | Constraints | Notes |
|---|---|---|---|
upload |
yes | string (upload reference) | MP3 or WAV, up to 8 minutes. |
continue_at |
yes |
number
at least 0 |
|
default_param_flag |
– |
boolean
true or false (JSON boolean) defaults to on |
|
prompt |
– |
string
max 5000 characters |
|
title |
– |
string
max 80 characters |
|
style |
– |
string
max 1000 characters |
|
instrumental |
– |
boolean
true or false (JSON boolean) |
|
model |
yes |
string (enum)
one of the six model ids, see the model table defaults to V5_5 |
|
negative_tags |
– |
string
max 500 characters |
|
vocal_gender |
– |
string (enum)
one of: m, f |
|
style_weight |
– |
number
0 to 1, step 0.01 |
How strictly to follow the style (0-1). |
weirdness_constraint |
– |
number
0 to 1, step 0.01 |
How experimental the result may be (0-1). |
audio_weight |
– |
number
0 to 1, step 0.01 |
Balance audio features against other input (0-1). |
Work from your own audio
Cover uploaded audio upload_cover 12 credits
Upload your own audio and turn it into a new production.
Needs 1 uploaded file via the upload field.
Rules the API enforces
- upload is required: MP3 or WAV up to 8 minutes.
- custom_mode defaults to true: style and title are required, plus prompt when instrumental is off. With custom_mode off, only prompt is needed.
| Field | R | Constraints | Notes |
|---|---|---|---|
upload |
yes | string (upload reference) | MP3 or WAV, up to 8 minutes. |
custom_mode |
– |
boolean
true or false (JSON boolean) defaults to on |
|
instrumental |
– |
boolean
true or false (JSON boolean) defaults to on |
|
prompt |
– |
string
max 5000 characters |
|
title |
– |
string
max 80 characters |
|
style |
– |
string
max 1000 characters |
|
model |
yes |
string (enum)
one of the six model ids, see the model table defaults to V5_5 |
|
negative_tags |
– |
string
max 500 characters |
|
vocal_gender |
– |
string (enum)
one of: m, f |
|
style_weight |
– |
number
0 to 1, step 0.01 |
How strictly to follow the style (0-1). |
weirdness_constraint |
– |
number
0 to 1, step 0.01 |
How experimental the result may be (0-1). |
audio_weight |
– |
number
0 to 1, step 0.01 |
Balance audio features against other input (0-1). |
Add vocals add_vocals 12 credits
Add a singing voice on top of your uploaded instrumental.
Needs 1 uploaded file via the upload field.
Rules the API enforces
- upload, prompt (the lyrics), title and style are all required.
| Field | R | Constraints | Notes |
|---|---|---|---|
upload |
yes | string (upload reference) | |
prompt |
yes |
string
max 5000 characters |
|
title |
yes |
string
max 80 characters |
|
style |
yes |
string
max 1000 characters |
|
negative_tags |
– |
string
max 500 characters |
|
vocal_gender |
– |
string (enum)
one of: m, f |
|
model |
– |
string (enum)
one of the six model ids, see the model table defaults to V5_5 |
|
style_weight |
– |
number
0 to 1, step 0.01 |
How strictly to follow the style (0-1). |
weirdness_constraint |
– |
number
0 to 1, step 0.01 |
How experimental the result may be (0-1). |
audio_weight |
– |
number
0 to 1, step 0.01 |
Balance audio features against other input (0-1). |
Add instrumental add_instrumental 12 credits
Generate an accompaniment under your uploaded vocal take.
Needs 1 uploaded file via the upload field.
Rules the API enforces
- upload, title and tags are required.
| Field | R | Constraints | Notes |
|---|---|---|---|
upload |
yes | string (upload reference) | |
title |
yes |
string
max 80 characters |
|
tags |
yes |
string
max 1000 characters |
Genre and mood for the accompaniment. |
negative_tags |
– |
string
max 500 characters |
|
model |
– |
string (enum)
one of the six model ids, see the model table defaults to V5_5 |
|
vocal_gender |
– |
string (enum)
one of: m, f |
|
style_weight |
– |
number
0 to 1, step 0.01 |
How strictly to follow the style (0-1). |
weirdness_constraint |
– |
number
0 to 1, step 0.01 |
How experimental the result may be (0-1). |
audio_weight |
– |
number
0 to 1, step 0.01 |
Balance audio features against other input (0-1). |
Replace a section replace_section 6 credits
Swap a chosen time range of a track for new material (infill).
Rules the API enforces
- Provide exactly one input: source_id of a generated take, or upload. Not both.
- With a source, audio_id selects the take.
- prompt, tags, title, full_lyrics, infill_start_s and infill_end_s are required.
- The section must be at least 10 seconds long, end within the track, and cover at most half of its duration.
| Field | R | Constraints | Notes |
|---|---|---|---|
source |
– | string (job UUID) | One of your generated takes, or upload audio below. |
upload |
– | string (upload reference) | Use instead of a generated take. |
audio_id |
– | string | Required when replacing from a generated track. |
prompt |
yes |
string
max 5000 characters |
|
tags |
yes |
string
max 1000 characters |
|
title |
yes |
string
max 80 characters |
|
infill_start_s |
yes |
number
at least 0 |
|
infill_end_s |
yes |
number
at least 0 |
|
full_lyrics |
yes | string | The complete lyrics including the new section. |
negative_tags |
– |
string
max 500 characters |
|
model |
yes |
string (enum)
one of the six model ids, see the model table defaults to V5_5 |
Mashup two tracks mashup 12 credits
Blend two uploaded tracks into one mashup.
Needs 2 uploaded files via the upload field.
Rules the API enforces
- Both uploads, title and style are required. prompt is optional and capped at 500 characters.
| Field | R | Constraints | Notes |
|---|---|---|---|
upload |
yes | string (upload reference) | |
upload2 |
yes | string (upload reference) | |
title |
yes |
string
max 80 characters |
|
style |
yes |
string
max 1000 characters |
|
prompt |
– |
string
max 500 characters |
|
instrumental |
– |
boolean
true or false (JSON boolean) |
|
duration |
– |
number
10 to 360 |
|
model |
yes |
string (enum)
one of the six model ids, see the model table defaults to V5_5 |
|
vocal_gender |
– |
string (enum)
one of: m, f |
|
style_weight |
– |
number
0 to 1, step 0.01 |
How strictly to follow the style (0-1). |
weirdness_constraint |
– |
number
0 to 1, step 0.01 |
How experimental the result may be (0-1). |
audio_weight |
– |
number
0 to 1, step 0.01 |
Balance audio features against other input (0-1). |
Sound design and formats
Generate a sound sounds 3 credits
Create loops, ambience and sound effects, optionally with BPM and key.
Rules the API enforces
- prompt is required.
- sound_tempo runs 20-300 BPM. sound_key is 8 characters max, e.g. D#m.
| Field | R | Constraints | Notes |
|---|---|---|---|
prompt |
yes |
string
max 5000 characters |
|
model |
yes |
string (enum)
one of the six model ids, see the model table defaults to V5 |
|
sound_loop |
– |
boolean
true or false (JSON boolean) |
Suitable for seamless repeat playback. |
sound_tempo |
– |
number
20 to 300 |
|
sound_key |
– |
string
max 8 characters |
e.g. 'D#m'. |
grab_lyrics |
– |
boolean
true or false (JSON boolean) |
Separate stems stems from 10 credits
Split a track into vocals, instrumental or up to 12 instrument stems.
Rules the API enforces
- Provide exactly one input: source_id of a generated take, or upload. Not both.
- With a source, audio_id selects the take.
- split_stem_advanced requires stem_name, e.g. Drums, Bass or Vocals.
| Field | R | Constraints | Notes |
|---|---|---|---|
source |
– | string (job UUID) | One of your generated takes, or upload audio below. |
upload |
– | string (upload reference) | |
audio_id |
– | string | |
separation_type |
yes |
string (enum)
one of: separate_vocal, split_stem, split_stem_advanced defaults to separate_vocal |
|
stem_name |
– | string | Required for the single-stem mode, e.g. 'Drums', 'Bass', 'Vocals'. |
Convert to WAV wav 1 credits
Download one of your tracks as a lossless WAV file.
Builds on an eligible job via source_id.
Allowed source operations:
generate or extend or upload_cover or upload_extend or add_vocals or add_instrumental or replace_section or mashup or sounds
(see the state requirements below).
Rules the API enforces
- audio_id is required and must be one of the source job's takes.
| Field | R | Constraints | Notes |
|---|---|---|---|
audio_id |
yes | string |
Generate MIDI midi 0 credits
Turn a separated stem into editable MIDI note data.
Builds on an eligible job via source_id.
Allowed source operations:
stems
(see the state requirements below).
Rules the API enforces
- audio_id is optional. Pass a stem id from a stems result to convert exactly that stem.
| Field | R | Constraints | Notes |
|---|---|---|---|
audio_id |
– | string | Optional. Pick a specific stem from the separation result. |
Lyrics and timing
Write lyrics lyrics 1 credits
Generate song lyrics from a theme or idea.
Rules the API enforces
- prompt is required.
| Field | R | Constraints | Notes |
|---|---|---|---|
prompt |
yes |
string
max 5000 characters |
Timestamped lyrics timestamped_lyrics 1 credits may finish inline
Get word-by-word timings for one of your takes (karaoke sync).
Builds on an eligible job via source_id.
Allowed source operations:
generate or extend or upload_cover or upload_extend or add_vocals or add_instrumental or replace_section or mashup or sounds
(see the state requirements below).
Rules the API enforces
- source_id and audio_id are required.
| Field | R | Constraints | Notes |
|---|---|---|---|
audio_id |
yes | string |
Boost a style description style_boost 1 credits may finish inline
Expand a short style idea into a rich, detailed style prompt.
Rules the API enforces
- content is required, 500 characters max.
| Field | R | Constraints | Notes |
|---|---|---|---|
content |
yes |
string
max 500 characters |
Short description, e.g. 'pop, mysterious'. |
Artwork and video
Cover art cover_art 0 credits
Generate cover artwork for one of your takes.
Builds on an eligible job via source_id.
Allowed source operations:
generate or extend or upload_cover or upload_extend or add_vocals or add_instrumental or replace_section or mashup or sounds
(see the state requirements below).
Rules the API enforces
- source_id and audio_id are required. The source must have been created with an original prompt.
| Field | R | Constraints | Notes |
|---|---|---|---|
audio_id |
yes | string |
Make a music video music_video 2 credits
Turn one of your tracks into a simple music video.
Builds on an eligible job via source_id.
Allowed source operations:
generate or extend or upload_cover or upload_extend or add_vocals or add_instrumental or replace_section or mashup or sounds
(see the state requirements below).
Rules the API enforces
- source_id and audio_id are required.
| Field | R | Constraints | Notes |
|---|---|---|---|
audio_id |
yes | string |
Custom voices
Start custom voice voice_validate 0 credits
Create a custom singing voice from your own recording. You first get a phrase to read aloud and record.
Needs 1 uploaded file via the upload field.
Rules the API enforces
- upload, vocal_start_s and vocal_end_s are required. The end must come after the start.
| Field | R | Constraints | Notes |
|---|---|---|---|
upload |
yes | string (upload reference) | |
vocal_start_s |
yes |
number
at least 0 |
|
vocal_end_s |
yes |
number
at least 0 |
|
language |
– |
string
max 30 characters |
New phrase for existing attempt voice_regenerate 0 credits
Ask for a fresh verification phrase; requires the earlier attempt as source.
Builds on an eligible job via source_id.
Allowed source operations:
voice_validate or voice_regenerate
(see the state requirements below).
Rules the API enforces
- source_id points at your voice_validate or voice_regenerate attempt in awaiting_user or completed state.
| Field | R | Constraints | Notes |
|---|
Finish custom voice voice_generate 0 credits
Submit your recording of the verification phrase and create the reusable voice.
Builds on an eligible job via source_id.
Allowed source operations:
voice_validate or voice_regenerate
(see the state requirements below).
Needs 1 uploaded file via the upload field or the operation's source.
Rules the API enforces
- source_id and upload are required. The source voice_validate or voice_regenerate attempt must be awaiting_user or completed; upload your recording of its verification phrase.
| Field | R | Constraints | Notes |
|---|---|---|---|
upload |
yes | string (upload reference) | |
voice_name |
– |
string
max 80 characters |
|
style |
– |
string
max 1000 characters |
|
singer_skill_level |
– |
string
max 40 characters |
Check custom voice voice_check 0 credits may finish inline
Verify that a generated custom voice is ready to use.
Builds on an eligible job via source_id.
Allowed source operations:
voice_generate
(see the state requirements below).
Rules the API enforces
- source_id points at the voice_generate job.
| Field | R | Constraints | Notes |
|---|
Library helpers
Persona persona 0 credits may finish inline
Create a reusable persona (style or voice) from one of your takes.
Builds on an eligible job via source_id.
Allowed source operations:
generate or extend or upload_cover or upload_extend or add_vocals or add_instrumental or replace_section or mashup or sounds
(see the state requirements below).
Rules the API enforces
- source_id and audio_id select the take. name and description are required.
| Field | R | Constraints | Notes |
|---|---|---|---|
audio_id |
yes | string | |
name |
yes |
string
max 80 characters |
|
description |
yes |
string
max 1000 characters |
Recover lost audio recovery 0 credits
Regenerate fresh download links for an older generation whose links expired.
Builds on an eligible job via source_id.
Allowed source operations:
generate or extend or upload_cover or upload_extend or add_vocals or add_instrumental or replace_section or mashup or sounds
(see the state requirements below).
Rules the API enforces
- No fields. source_id points at the generation whose links expired.
| Field | R | Constraints | Notes |
|---|
Field limits at a glance
| Input | Limit |
|---|---|
style, tags | 1000 characters, 200 on V4 |
prompt | 5000 characters, 3000 in idea mode |
title | 80 characters (120 for PATCH renames) |
negative_tags | 500 characters |
duration | 10 to 360 seconds, V5.5 with custom mode only |
| Style, weirdness and audio weights | 0 to 1, step 0.01 |
sound_tempo | 20 to 300 BPM |
| Replace-section span | At least 10 seconds, at most half the track |
| Uploads | 50 MiB per file, MP3 / WAV / M4A / OGG / FLAC |
| Library pages | 25 jobs per page, cursor pagination |
| Free operations | 25 jobs per account per day, combined |
Errors you will actually meet
Errors come back as {"success": false, "error": "...", "code": "..."}, often
with the offending field. Authentication errors may instead use a detail field. For an ambiguous network or gateway error, retry the same request body and UUID; do not assume it was rejected or refunded.
| Code | HTTP | What it means |
|---|---|---|
invalid_request_id |
400 | request_id is missing or not a UUID. |
unknown_operation |
400 | The operation id is not in the catalog. |
invalid_field |
400 | A field failed validation. The error names the field. |
invalid_source |
400 | source_id is missing, not yours, not completed, or not allowed for this operation. |
invalid_audio_id |
400 | audio_id is not one of the source job's takes or assets. |
invalid_persona |
400 | persona_id is not a persona or custom voice in your library. |
invalid_upload |
400 | The upload reference is invalid, expired or not yours. |
prompt_blocked |
400 | Prompt moderation rejected the submission. No credits were charged. |
prompt_requires_review |
400 | Prompt moderation queued the text for review. No credits were charged. |
request_id_conflict |
409 | This request_id was already used with a different body. Use a new UUID. |
rate_limited |
429 | You hit the daily cap on free operations. Try again tomorrow. |
insufficient_credits |
402 | The balance of purchased credits is too low. Welcome credits do not work over the API. |
invalid_cursor |
400 | The library cursor is not a valid timestamp. |
missing_file |
400 | The upload call had no file field. |
unsupported_type |
400 | The upload is not an accepted audio type. |
file_too_large |
400 | Uploads are capped at 50 MiB. |
invalid_audio |
400 | The file does not contain playable audio. |
401 response |
401 | The key is missing, malformed, revoked or wrong. Check the Authorization header. |
404 response |
404 | No such job in your library. Job ids are UUIDs and library contents are private. |
502 response |
502 | A definitive API submission failure is refunded. A proxy or network error can be ambiguous: retry the same body and request_id to recover the original job. |
Is this Suno’s official API?
- UlazAI is not affiliated with, endorsed by or sponsored by Suno. The name describes the music models this API exposes; the API itself is UlazAI's.
- Check the UlazAI Terms of Use and the rights in any audio you upload before publishing or selling a result. API access alone is not a blanket rights clearance.
- Generation time depends on model, duration and provider load. Treat several minutes as normal, poll with a bound, and never assume a fixed latency.
- Model ids, prices and limits are current as of September 2026 and can change. The catalog route always reflects the live values; this page is generated from the same catalog.
- Vocal gender is a preference, not a guarantee, and results vary between runs. Two takes per generate job means two chances, not a specified outcome.