Music
Music generation
Writing songs, jingles and sound effects with Suno — simple versus custom mode, style prompts that work, extending and covering tracks, stems and voice personas.
Music is the one place where generative models still feel like magic to people who have stopped being impressed by images. You type four lines about a half-remembered summer, and two minutes later there is a finished song with a vocalist, a chorus and a bridge.
It is also, quietly, the most practical of the three media here. Licensing a single background track for a product video costs more than a month of generating them, and the result is not exclusive to you. A generated track is.
Everything runs through one public model, suno, whose action field selects
the operation: write a song, extend one, cover one, split it into stems, get
timestamped lyrics, or capture a voice as a reusable persona.
What people build with it
| What you are making | Action | Notes |
|---|---|---|
| Background track for a video | music | ask for instrumental, match the length to the cut |
| Full song with vocals and lyrics | music with custom: true | you supply the lyrics and the style |
| A brand jingle or audio logo | music | short, one hook, name the instrumentation |
| A podcast intro and outro pair | music, then crop / fade-out | generate once, trim twice |
| UI and game sound effects | sound | not music — one-shot audio from a description |
| A longer version of a track you like | extend | continues from continueAt |
| The same song in a different genre | cover | keeps the melody, changes the dress |
| A karaoke or remix-ready mix | stems / stems-all | vocals and instrumental split out |
| Subtitles synced to a track | timestamped-lyrics | returns data, not audio |
| One vocalist across a whole album | voice | capture a persona, reuse it |
Simple mode or custom mode
This is the only structural decision in the whole guide, and custom makes it.
Simple mode (custom omitted or false) takes one field,
gptDescriptionPrompt, capped at 500 characters. The model writes the lyrics
and the arrangement. Use it when you want a track and do not care about the
words — background music, mood pieces, anything instrumental.
Custom mode (custom: true) takes prompt as the lyrics, tags as the
genre and production direction, and title. Use it when the words matter: a
brand song, a birthday track with real names in it, anything where a generated
lyric about "chasing the light" would be embarrassing.
The single most common mistake is putting a style description in custom
mode's prompt — it belongs in tags. In custom mode prompt is the lyric
sheet, and whatever you put there is what gets sung.
Writing a style prompt
Style prompts are not sentences. They are a list of production decisions, and models respond best to four categories, in roughly this order:
genre → instrumentation → vocal → tempo and mood.
indie folk, fingerpicked acoustic guitar, brushed drums, warm upright bass,soft female vocal, 90 bpm, nostalgic and unhurried
- Name instruments, not adjectives. "Brushed drums, upright bass, Rhodes" beats "organic and warm" every time.
- Say
instrumentalif you mean instrumental. Otherwise you will get vocals, because most training data has them. In simple mode, put the word ingptDescriptionPrompt; the model honours it. - Give a tempo. A bpm number is respected surprisingly closely, and it is the difference between a track that cuts to your footage and one that does not.
- Two references, not five. "Somewhere between Nick Drake and early Bon Iver" works. A list of eight artists averages into mush.
- Structure tags work in custom lyrics.
[Verse],[Chorus],[Bridge],[Outro]on their own lines shape the arrangement — a chorus marked as one actually comes back as a hook.
Recipes
Organic background bed — action: "music", simple mode:
Organic downtempo psybient, 95 BPM, didgeridoo and tabla loops, soft analogpads, forest field recordings, fretless bass, kalimba textures, meditativedorian A minor, 8 minute slow development, warm earthy production, no vocalsinstrumental
The prompt pins the tempo, instruments, mode, production texture and vocal constraint. The published Suno v5.5 result runs just under five minutes and develops slowly enough to sit under a long-form product or travel edit.
Brand song with your words — custom: true, tags and prompt:
{"custom": true,"tags": "upbeat indie pop, jangly electric guitar, handclaps, group vocals, 128 bpm, bright and cheerful","title": "Ship It Friday","prompt": "[Verse]\nWe wrote it down on Monday morning\nHalf a plan and too much coffee\n\n[Chorus]\nShip it Friday, ship it Friday\nNothing's perfect anyway"}
prompt is the lyric sheet. Keep lines short — long ones get crammed into the
bar and the phrasing suffers.
Audio logo, three seconds of it — action: "music", simple mode:
Three-second audio logo: a single bright marimba motif of four notes rising,one soft synth swell underneath, clean modern, no vocals, ends cleanly
Generate the short idea, then use crop and fade-out to land the exact
length. Asking a model for "exactly 3 seconds" is less reliable than trimming.
A sound effect, not a song — action: "sound":
A heavy wooden door closing in a stone corridor, with a short natural reverbtail
sound is a different generator: one-shot audio from a description, no
musical structure. This is the action for UI clicks, whooshes and foley.
The same song, different genre — action: "cover" with a sourceTaskId:
{"action": "cover","sourceTaskId": "019ca881-9503-7270-a560-f00fc2b15785","tags": "slow piano ballad, string section, intimate male vocal, 70 bpm","title": "Ship It Friday (Ballad)"}
The melody survives, the production changes. This is how you get the energetic version and the emotional version of one brand track without writing it twice.
About these samples. Ready-made examples are copied to our own storage only when their source permits reuse. The exact prompt appears above each result, and the original source and license are linked below the media.
action is the mode selector
action defaults to music. Every action shares the one SunoInputDto, so
the field set is a superset and the requirements are enforced per action — an
input that is valid for music is usually invalid for extend.
| Action | Requires | Result |
|---|---|---|
music | gptDescriptionPrompt, or prompt with custom: true | audio |
extend | continueAt + a source | audio |
cover | a source | audio |
stems | audioId or sourceTaskId | audio |
stems-all | audioId or sourceTaskId | audio |
sound | prompt | audio |
lyrics | prompt | data in output |
timestamped-lyrics | sourceTaskId | data in output |
voice | voiceAudioUrl or sourceTaskId | persona in output |
A "source" is one of three things: audioId (a track id from an earlier
generation), audioUrl (a public audio file) or sourceTaskId (the taskId
of one of your earlier suno generations). sourceTaskId also pins the
request to the provider that produced the source, which is what makes stateful
chains work.
The full action list — inspo, wav, remaster, crop, fade-in,
fade-out, adjust-speed, mashup, boost-style, music-video, midi,
bpm and more — is in Music models.
Sending the request
There are two modes, and custom decides which validation applies.
Simple mode is the default. You describe the song in one field,
gptDescriptionPrompt, capped at 500 characters, and the model writes both
lyrics and arrangement.
curl -X POST https://api.api-stock.com/api/v1/generation/create \-H "Authorization: Bearer sk-your-key" \-H "Content-Type: application/json" \-d '{"model": "suno","input": {"action": "music","gptDescriptionPrompt": "A slow indie folk song about leaving a harbour town at dawn, brushed drums, female vocal"}}'
Custom mode hands you the lyrics. Set custom: true and put the lyrics in
prompt, the genre in tags and the name in title.
{"model": "suno","input": {"action": "music","custom": true,"mv": "v5.0","title": "Harbour Lights","tags": "indie folk, brushed drums, warm analog","prompt": "[Verse]\nThe gulls come down before the sun does\n[Chorus]\nAnd the harbour lights go out one by one","makeInstrumental": false}}
Field limits depend on mv. On v4.5 and newer, prompt allows 5000
characters and tags 1000; on the legacy v3.5 and v4.0 engines those drop
to 3000 and 200. mv defaults to v4.5+.
makeInstrumental: true produces a track with no vocals, and in custom mode it
also satisfies the "needs a prompt" rule on its own.
The two modes read different fields. Simple mode requires
gptDescriptionPrompt; custom mode requiresprompt, unlessmakeInstrumentalistrue. Settingcustom: trueand sending onlygptDescriptionPromptfails validation with 400invalid_input.
Reading the result
A music action typically produces more than one track. Each one is an
entry in files with fileType: "music", and the per-track metadata lands in
output.tracks, in the same order.
{"code": 200,"data": {"taskId": "019ca8d7-6b02-7f31-a1c4-8e5d90b7f2aa","status": "finished","files": [{"fileUrl": "https://storage.api-stock.com/generated/suno-4c81-a.mp3","fileType": "music"},{"fileUrl": "https://storage.api-stock.com/generated/suno-4c81-b.mp3","fileType": "music"}],"output": {"tracks": [{"audioId": "b3d1f0a2-77ac-4e19-9c0e-1d2b3f4a5c6d","title": "Harbour Lights","duration": 184.6,"tags": "indie folk, brushed drums","imageUrl": "https://cdn.example.com/cover-a.jpeg"},{"audioId": "c7e2a941-08bd-4f52-b3aa-9e01c2d3e4f5","title": "Harbour Lights","duration": 191.2,"tags": "indie folk, brushed drums","imageUrl": "https://cdn.example.com/cover-b.jpeg"}]},"createdTime": "2026-02-28T13:52:19.000Z"}}
output.tracks[].audioId is the handle for follow-up actions. Keep it
alongside the taskId; a later extend or stems uses it to say which of
the two tracks to operate on. Omit audioId on a follow-up and the first track
is used.
URLs inside output, such as imageUrl, point at the provider's own CDN and
are not mirrored into storage — only the entries in files are.
Extending a track
extend continues a song from a given second. continueAt is required, and
the source is normally the earlier generation's taskId.
{"model": "suno","input": {"action": "extend","sourceTaskId": "019ca8d7-6b02-7f31-a1c4-8e5d90b7f2aa","audioId": "b3d1f0a2-77ac-4e19-9c0e-1d2b3f4a5c6d","continueAt": 120,"custom": true,"prompt": "[Bridge]\nThe tide turns over in the dark","tags": "indie folk, brushed drums"}}
A sourceTaskId extend does not need a simple-mode prompt — the source track
supplies the context. To extend an uploaded file instead, swap sourceTaskId
and audioId for an audioUrl pointing at the public audio.
Covering a track
cover re-records an existing song in a new style. It takes the same three
source forms, and with a sourceTaskId at least one of prompt,
gptDescriptionPrompt, tags or title must describe the new style.
{"model": "suno","input": {"action": "cover","sourceTaskId": "019ca8d7-6b02-7f31-a1c4-8e5d90b7f2aa","audioId": "b3d1f0a2-77ac-4e19-9c0e-1d2b3f4a5c6d","tags": "synthwave, gated reverb, 1984","title": "Harbour Lights (Synth)"}}
Splitting into stems
stems extracts a single stem; stems-all separates the track into twelve
stem categories. Both take audioId or sourceTaskId. stemType selects
which stem stems returns — lead_vocal (the default), backing_vocals,
drum_kit, bass, piano, electric_guitar and so on.
{"model": "suno","input": {"action": "stems","sourceTaskId": "019ca8d7-6b02-7f31-a1c4-8e5d90b7f2aa","audioId": "b3d1f0a2-77ac-4e19-9c0e-1d2b3f4a5c6d","stemType": "drum_kit"}}
Stems come back as ordinary music files, one entry in files per stem.
Actions that return data, not media
lyrics, timestamped-lyrics, boost-style, midi and bpm produce no
audio. Their tasks reach finished with files absent and the whole result
in output.
Generating lyrics needs only a prompt, and its mv comes from a different
family — remi-v1 or default:
{"model": "suno","input": {"action": "lyrics","prompt": "A song about leaving a harbour town at dawn","mv": "remi-v1"}}
The finished task carries the variations:
{"code": 200,"data": {"taskId": "019ca8e0-91c3-7d48-b6f0-2a7c4e19d503","status": "finished","output": {"a": {"title": "Harbour Lights","text": "[Verse]\nThe gulls come down before the sun does","tags": ["indie folk", "folk"]},"b": {"title": "Leaving Line","text": "[Verse]\nSalt on the rope and the morning still cold","tags": ["indie folk", "acoustic"]}},"createdTime": "2026-02-28T14:02:07.000Z"}}
The exact keys inside output follow the upstream provider's own result shape,
so read it defensively. timestamped-lyrics works the same way but requires a
sourceTaskId and returns word-level timing for that track.
Do not wait for
fileson a data action. Alyricstask that has reachedfinishedis complete even thoughfilesis undefined. Branch onstatus, then on the presence offiles, in that order.
Voices and personas
voice creates a reusable persona. Two routes into it: a public recording
through voiceAudioUrl, or one of your earlier tracks through sourceTaskId.
The track route needs only a name; the verification flow on the recording
route needs verificationAudioUrl, phraseId and name together.
{"model": "suno","input": {"action": "voice","sourceTaskId": "019ca8d7-6b02-7f31-a1c4-8e5d90b7f2aa","name": "Harbour Alto"}}
The persona lands in output. Feed its id back into a later music request
through personaId to have that voice sing the new song.
A complete run
Generate a song, then extend the first track it returns.
const BASE = "https://api.api-stock.com/api/v1";const KEY = process.env.API_STOCK_KEY!;async function submit(input: Record<string, unknown>): Promise<string> {const res = await fetch(`${BASE}/generation/create`, {method: "POST",headers: {Authorization: `Bearer ${KEY}`,"Content-Type": "application/json",},body: JSON.stringify({ model: "suno", input }),});const body = await res.json();if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);return body.data.taskId;}async function wait(taskId: string) {const deadline = Date.now() + 15 * 60_000;while (Date.now() < deadline) {await new Promise((r) => setTimeout(r, 8_000));const res = await fetch(`${BASE}/task/status/${taskId}`, {headers: { Authorization: `Bearer ${KEY}` },});const body = await res.json().catch(() => null);if (!res.ok) {if (res.status === 429) {const retryAfter = Number(res.headers.get("Retry-After") ?? 8);await new Promise((r) => setTimeout(r, retryAfter * 1000));continue;}const message = body?.error?.message ?? `${res.status} ${res.statusText}`;throw new Error(message);}const { data } = body;if (data.status === "finished") return data;if (data.status === "failed") throw new Error(data.errorMessage);}throw new Error(`timed out waiting for ${taskId}`);}const songId = await submit({action: "music",custom: true,mv: "v5.0",title: "Harbour Lights",tags: "indie folk, brushed drums, warm analog",prompt: "[Verse]\nThe gulls come down before the sun does",});const song = await wait(songId);console.log(song.files.length); // → 2const tracks = (song.output?.tracks ?? []) as { audioId: string }[];const extendedId = await submit({action: "extend",sourceTaskId: songId,audioId: tracks[0]?.audioId,continueAt: 120,custom: true,prompt: "[Bridge]\nThe tide turns over in the dark",tags: "indie folk, brushed drums",});const extended = await wait(extendedId);console.log(extended.files[0].fileUrl); // → https://storage.api-stock.com/...
Songs take a minute or two, so an 8-second poll interval is fine; for long
chains prefer a webhook on create — see Webhooks.
When it fails
Per-action rules are checked before dispatch, and the message names the action:
{"code": 400,"error": {"message": "action \"extend\" requires continueAt","type": "BadRequest","code": "invalid_input"}}
Frequent ones:
action "music" simple mode requires gptDescriptionPrompt— you omittedcustom: trueand sent only aprompt.action "cover" requires audioId, audioUrl or sourceTaskId.action "lyrics" mv must be one of: [remi-v1, default]— the audio engine versions do not apply tolyrics.prompt exceeds the 3000 character limit for mv "v4.0"— the legacy engines have smaller limits.
Generation-time failures arrive as status: "failed" with an errorMessage,
and the debit is refunded automatically.
Next steps
- Music models — every action and every field.
- Polling task status —
filesversusoutput. - Webhooks — the delivered payload matches this envelope.
- Files & storage, Errors & error codes.
Run this with your own key
Every model in this guide is live in the API Stock catalog — one API key, one prepaid balance, no separate signup per provider.