New: Nano Banana 2.1Try the model
API Stock

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.

8 min read14 sections

Create a song, instrumental background or sound effect with suno. Its action field selects the operation: generation, extension, cover, stem separation, timestamped lyrics or a reusable voice persona. Below are prompt examples and the settings for each workflow.


What people build with it

What you are makingActionNotes
Background track for a videomusicask for instrumental, match the length to the cut
Full song with vocals and lyricsmusic with custom: trueyou supply the lyrics and the style
A brand jingle or audio logomusicshort, one hook, name the instrumentation
A podcast intro and outro pairmusic, then crop / fade-outgenerate once, trim twice
UI and game sound effectssoundnot music — one-shot audio from a description
A longer version of a track you likeextendcontinues from continueAt
The same song in a different genrecoverkeeps the melody, changes the dress
A karaoke or remix-ready mixstems / stems-allvocals and instrumental split out
Subtitles synced to a tracktimestamped-lyricsreturns data, not audio
One vocalist across a whole albumvoicecapture 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 3000 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.

text
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 instrumental if you mean instrumental. Otherwise you will get vocals, because most training data has them. In simple mode, put the word in gptDescriptionPrompt; 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:

text
Organic downtempo psybient, 95 BPM, didgeridoo and tabla loops, soft analog
pads, forest field recordings, fretless bass, kalimba textures, meditative
dorian A minor, 8 minute slow development, warm earthy production, no vocals
instrumental

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.

A Suno v5.5 instrumental generated from the exact prompt above: organic percussion and field recordings build over a slow 95 BPM pulse.

Brand song with your words — custom: true, tags and prompt:

json
{
"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:

text
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":

text
A heavy wooden door closing in a stone corridor, with a short natural reverb
tail

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:

json
{
"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.

ActionRequiresResult
musicgptDescriptionPrompt, or prompt with custom: trueaudio
extendcontinueAt + a sourceaudio
covera sourceaudio
stemsaudioId or sourceTaskIdaudio
stems-allaudioId or sourceTaskIdaudio
soundpromptaudio
lyricspromptdata in output
timestamped-lyricssourceTaskIddata in output
voicevoiceAudioUrl or sourceTaskIdpersona 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 3000 characters, and the model writes both lyrics and arrangement.

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

json
{
"model": "suno",
"input": {
"action": "music",
"custom": true,
"mv": "v6",
"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
}
}

mv picks the V6 engine: v6 (the default), v6-wild or v6-mini. The retired v3.5 … v5.5 values are still accepted and run on v6. In custom mode prompt allows 5000 characters and tags 1000.

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 requires prompt, unless makeInstrumental is true. Setting custom: true and sending only gptDescriptionPrompt fails validation with 400 invalid_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.

json
{
"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.

json
{
"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.

json
{
"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.

json
{
"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:

json
{
"model": "suno",
"input": {
"action": "lyrics",
"prompt": "A song about leaving a harbour town at dawn",
"mv": "remi-v1"
}
}

The finished task carries the variations:

json
{
"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 files on a data action. A lyrics task that has reached finished is complete even though files is undefined. Branch on status, then on the presence of files, 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.

json
{
"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.

ts
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: "v6",
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); // → 2
const 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:

json
{
"code": 400,
"error": {
"message": "action \"extend\" requires continueAt",
"type": "BadRequest",
"code": "invalid_input"
}
}

Frequent ones:

  • action "music" simple mode requires gptDescriptionPrompt — you omitted custom: true and sent only a prompt.
  • action "cover" requires audioId, audioUrl or sourceTaskId.
  • action "lyrics" mv must be one of: [remi-v1, default] — the audio engine versions do not apply to lyrics.
  • prompt exceeds the 5000 character limit — custom-mode lyrics are capped at 5000 characters.

Generation-time failures arrive as status: "failed" with an errorMessage, and the debit is refunded automatically.


Next steps

Run this with your own key

Choose a model in the API Stock catalog, check its price and try it in your browser or through the API.

Keep reading