Music Generation
Generate music from text descriptions using the Kolbo API.
Generate music tracks with vocals or instrumentals using AI models like Suno. Browse the full model catalog for everything currently available, or fetch the live list at any time with GET /api/v1/models?type=music_gen.
All generation endpoints accept an optional
project_idbody field that routes the output into a specific project. See Projects.
Endpoint
POST /api/v1/generate/musicRelated music routes on this page: /generate/music/composition-plan, /generate/music/reference-audio and /generate/music/edit-section for ElevenLabs Music v2.5 structured songs, and /generate/music/extend, /generate/music/cover and /generate/music/import for working from existing audio.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes* | Description of the music (e.g., "upbeat pop song about summer"). Must be a non-empty string, otherwise 400 INVALID_PROMPT. *Optional when video_url is supplied (see Score an existing video), though if sent it must still be a string; ignored when composition_plan is supplied (see Structured songs) |
model | string | No | Model identifier from GET /api/v1/models?type=music_gen. Must be a string — an array returns 400 INVALID_MODEL. Omitted, blank, or any of auto / smart-select / smart_select / smartselect / auto-select resolves to the default suno-v6. An identifier that is not in the catalog is rejected with 400 MODEL_NOT_FOUND |
style | string | No | Music style / genre (e.g., "pop", "rock", "electronic", "jazz"). Fallback only — in custom mode the server generates a style from prompt (or from preset_id) and uses that; your style is used only if that generation fails. In simple mode (no lyrics, no duration_seconds) the style field is emptied and this value is never sent. See the custom-mode callout below |
instrumental | boolean | No | Instrumental only, no vocals (default: false) |
lyrics | string | No | Custom lyrics. Supplying it switches the request out of simple mode — see the callout below |
vocal_gender | string | No | "m" or "f"; "male" / "female" (any case) are normalised to them. On Suno the value is forwarded to the provider (which expects exactly m / f), and only in custom mode. On fal-backed music models there is no native parameter, so only m / f append a ", male vocals" / ", female vocals" hint to the prompt (both modes) — any other value is ignored there. Not gated on instrumental |
enhance_prompt | boolean | No | Rewrite the prompt with Kolbo's music prompt enhancer before it reaches the model (default: false — true or the string "true" / "1" enables it). Even when enabled, enhancement is skipped in custom mode for prompts longer than 400 characters |
title | string | No | Song title. Auto-generated from the prompt when omitted. Always used for the track/session title; reaches Suno itself only in custom mode |
negative_tags | string | No | Styles / sounds to exclude, comma-separated (e.g. "heavy metal, distortion"). Suno, custom mode only |
preset_id | string | No | Apply a saved music style preset (GET /api/v1/presets?type=music) |
duration_seconds | number | No | Target track length in seconds. Clamped to 5–600 by the API, then to the chosen model's own window — 10–360 on suno-v6 / suno-v6-wild, so a value under 10 is raised to 10 and one over 360 is lowered to 360. Omit it for Suno's own natural length (the web app calls this Auto). Sending it switches a Suno request into custom mode. See the length callout below for which models honour it |
project_id | string | No | Route the output into a specific project. Omit for the default "API Generations" project |
session_id | string | No | Add the generation to an existing music session (the session_id returned by an earlier music call). Takes precedence over project_id. An invalid id returns 400 SDK_SESSION_INVALID_ID; a session you cannot write to, or that is not a music session, returns an error (404 SDK_SESSION_NOT_FOUND when it does not exist as a music session) |
composition_plan | object | No | ElevenLabs Music v2.5 structured mode — { "chunks": [...] }. When present, every other field except seed, title, project_id and session_id is ignored. See Structured songs |
seed | number | No | Reproducibility seed. Only used together with composition_plan |
Suno fine-controls (optional)
Advanced controls honored by Suno models; ignored by others. Most reach the provider only in custom mode — see the callout below.
| Field | Type | Description |
|---|---|---|
style_weight | number | How strongly the style/genre is applied, 0–1. Custom mode only |
weirdness | number | Creativity / experimentation constraint, 0–1. Custom mode only |
audio_weight | number | Influence of an audio/persona reference, 0–1. Custom mode only |
persona_id | string | Reuse a saved Suno persona / singing voice. Custom mode only |
use_composition_plan | boolean | Accepted for backward compatibility, but no music provider currently receives it — it has no effect on the audio. For real structured songs use composition_plan (ElevenLabs Music v2.5) |
singing_dna_id | string | Visual DNA character whose cloned singing voice should sing the song. Must be owned by the caller and have a ready singing voice — otherwise the request fails with 400 ("This character has no ready singing voice yet."). Resolves to a Suno voice persona, so it takes effect in custom mode only; on a Suno model that cannot use personas the request is moved to Suno v6. Takes precedence over singing_voice_id and persona_id |
singing_voice_id | string | Your own cloned singing voice (a custom voice with voiceType: "singing"). Must be owned by the caller and ready — otherwise 400 ("This singing voice is not ready yet."). Same custom-mode and model rules as singing_dna_id; takes precedence over persona_id |
source_media | object | Provenance metadata stored on the generation record: { url, type, mimeType, originalFileName, fileSize }. Only persisted when url is present, and it does not influence the generated audio — to actually score a video use video_url below |
Model identifiers are Kolbo-specific — always fetch available models from GET /api/v1/models?type=music_gen first (the legacy alias type=music also works). Omitting model on this endpoint resolves to suno-v6.
The current Suno family is suno-v6 (the default), suno-v6-mini and suno-v6-wild — same credit cost as the older rows. suno-v5.5, suno-v5 and suno-v4.5plus still work, but Suno has discontinued those versions upstream. The catalog also carries non-Suno music models (ElevenLabs Music v2.5 as music-v1, Google Lyria, MiniMax, Sonilo and others); Suno-only controls are ignored on them.
Song length: duration_seconds is honoured on suno-v6 and suno-v6-wild, in custom mode only.
- The provider honours a length only in custom mode, so the API automatically switches a request carrying
duration_secondsinto custom mode (the same modelyricstriggers — see the callout below). In custom mode a vocal request withoutlyricsgets lyrics written from yourprompt, and a style is generated from it. If you do not want custom mode, do not send a length. suno-v6-miniignores it. Measured against the live provider: requests for 20s, 30s and 60s all returned tracks of roughly 200–220s. Do not build a length-sensitive flow on Mini.- Short targets are style-dependent. Suno resolves a musical phrase before it stops, so a sparse ambient prompt asked for 13s returns about 13s while a dense rock prompt asked for the same 13s returns about 42s. They converge by roughly 30s — write a sparse prompt when you need a genuinely short clip.
- Suno normally returns two tracks and does not always apply the length to both. One can land on target while the other runs long, so read
result.tracks[].durationper track rather than trusting the first one. - Other length-controllable models (ElevenLabs Music v2.5,
music-v1, up to 600s) default to a ~10s track withoutduration_seconds, so always pass it there. Some models (Google Lyria 3.5, for example) have no length control at all — length is prompt-driven. The catalog'smin_output_duration/max_output_durationfields are not populated for music models today; read the model'ssummaryinGET /api/v1/models?type=music_geninstead.
lyrics (or duration_seconds) is the switch between simple mode and custom mode, and custom mode is what unlocks the Suno controls.
Server-side, simple mode is used only when there are no lyrics and no duration_seconds — so passing either puts the request in custom mode, and omitting both leaves you in simple mode.
- Simple mode (no
lyrics, noduration_seconds): the Suno request carries onlyinstrumental, the model and the (optionally enhanced)prompt.style,title,negative_tags,vocal_gender,persona_id,singing_dna_id,singing_voice_id,style_weight,weirdnessandaudio_weightare not sent to Suno — they are still stored on the generation record, but they do not shape the audio. The non-Suno (fal-backed) music models take a different route and still honourvocal_genderandnegative_tagsin either mode;styleis unused there too in simple mode. - Custom mode (
lyricsorduration_secondspresent): all of the above are forwarded, andlyricsis treated as the exact lyric sheet rather than a brief. The server auto-generates a style (fromprompt, or uses the preset's style whenpreset_idis given), atitlewhen you omit it, and — for a vocal track sent withoutlyrics— lyrics from yourprompt. Withinstrumental: truein custom mode the prompt itself is not sent to Suno; only the style and title shape the track.
Leave lyrics out when you want the model to write the words; supply it (even a rough draft), or a duration_seconds, when you need the fine-controls to actually reach Suno.
Score an Existing Video
Supplying video_url routes the request through the Vision Music pipeline instead of the regular text-to-music flow. prompt becomes optional in this mode (it is forwarded as free-form guidance). The result is still an audio track — this endpoint always produces a music file, never a video muxed with the score.
Which pipeline runs depends on the model's requires_video_input flag in GET /api/v1/models?type=music_gen:
requires_video_input: true(todaysonilo/v1.1/video-to-music) — the model scores the clip natively.keep_speech_vocal,num_samples,start_offsetandduration_secondsapply. The URL must resolve to a video, otherwise the generation fails withThis model requires a video upload.- any other music model (including the default
suno-v6when you omitmodel) — the media is analysed first (prompt, style and lyrics are derived from it), then normal music generation runs. The four fields above are ignored on this path.
| Field | Type | Required | Description |
|---|---|---|---|
video_url | string | Yes | Public URL of the source media. Must be a string, and is SSRF-validated — private/internal hosts return 400 INVALID_VIDEO_URL. The server downloads it; max 50 MB, otherwise 400 FILE_TOO_LARGE |
keep_speech_vocal | boolean | No | requires_video_input models only. Preserve the existing speech / vocals from the source video (default: false; enabled by true or the string "true") |
num_samples | number | No | requires_video_input models only. Number of scored variations the provider generates. Clamped 1–4 (default: 1). Models with per-second billing are charged per sample, but only the first returned track is stored and surfaced in result.urls — raising this costs more without returning more. Leave it at 1 unless you have a reason |
start_offset | number | No | requires_video_input models only. Trim start, in seconds |
duration_seconds | number | No | Trim duration in seconds for requires_video_input models — sent unclamped. On the analyse-then-generate path it becomes the requested track length (clamped 5–600), which length-capable models such as ElevenLabs honour. When omitted, the credit pre-check estimates from the model's default duration (falling back to 60s) |
vocal_gender, enhance_prompt, preset_id, title, negative_tags, persona_id, use_composition_plan, singing_dna_id, singing_voice_id, source_media and the Suno fine-controls are not forwarded when video_url is present. The only fields that carry over are prompt (as free-form guidance), model, style, instrumental, lyrics, project_id, session_id and the four video-mode fields in the table above.
curl -X POST https://api.kolbo.ai/api/v1/generate/music \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"video_url": "https://example.com/clip.mp4",
"model": "sonilo/v1.1/video-to-music",
"prompt": "tense, driving percussion under the chase",
"start_offset": 0,
"duration_seconds": 30
}'Structured Songs (ElevenLabs Music v2.5)
Instead of a prompt, you can send ElevenLabs Music v2.5 an explicit composition plan: an ordered list of chunks, each one either a section to generate or a slice of an existing song to keep unchanged. Pass it as composition_plan on POST /api/v1/generate/music. The request always runs on music-v1 (your model is ignored), and every Suno-oriented field is ignored — only composition_plan, seed, title, project_id and session_id are read. The response and polling are the same as any music generation.
composition_plan must be an object with a chunks array — { "chunks": [ ... ] }. A bare array returns 400 MISSING_PLAN.
| Chunk field | Type | Applies to | Description |
|---|---|---|---|
duration_ms | integer | Generated chunk | Required. Whole milliseconds, 3000–120000, otherwise 400 INVALID_CHUNK_DURATION |
text | string | Generated chunk | Section label in brackets, lyric lines and inline directions, e.g. "[Chorus]\nWe rise tonight" |
positive_styles | string[] | Generated chunk | Styles / directions to include. The first chunk's styles set the tone of the whole song |
negative_styles | string[] | Generated chunk | Styles / directions to avoid |
context_adherence | string | Generated chunk | low, medium or high — how closely the chunk follows its neighbours |
conditioning_ref | object | Generated chunk | { "song_id": "...", "range": { "start_ms": 0, "end_ms": 20000 } } — steers this chunk's style off a stored song without splicing its audio in. The range must be whole milliseconds with start_ms < end_ms (400 INVALID_CONDITIONING_REF) and at most 30 seconds long (400 CONDITIONING_REF_TOO_LONG) |
song_id + range | string + object | Kept chunk | Splice range.start_ms–range.end_ms of a stored song in unchanged. Both values whole milliseconds, start_ms < end_ms, otherwise 400 INVALID_CHUNK_RANGE |
A plan holds 1–30 chunks (400 MISSING_PLAN / 400 TOO_MANY_CHUNKS); a chunk that is not an object returns 400 INVALID_CHUNK. Chunks are forwarded to ElevenLabs as written, so other ElevenLabs chunk fields pass through unvalidated. A song_id comes from result.song_id of an earlier ElevenLabs Music v2.5 generation, or from reference audio you uploaded.
Pricing: the total plan length (generated duration_ms plus kept range lengths) is billed per started minute at the music-v1 catalog rate. The credits are held when the job starts and released if it fails.
curl -X POST https://api.kolbo.ai/api/v1/generate/music \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"title": "Night Drive",
"seed": 42,
"composition_plan": {
"chunks": [
{ "text": "[Intro]", "duration_ms": 8000, "positive_styles": ["synthwave", "warm analog pads"] },
{ "text": "[Verse]\nCity lights in the rear view", "duration_ms": 30000, "positive_styles": ["female vocals"] },
{ "text": "[Chorus]\nWe drive until the morning", "duration_ms": 25000, "positive_styles": ["anthemic"], "context_adherence": "high" }
]
}
}'Get a starter plan
POST /api/v1/generate/music/composition-planFree — creates no generation, charges no credits and is not counted in the generation rate-limit bucket. ElevenLabs drafts a plan from a plain-text prompt for you to edit and submit as composition_plan.
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | Description of the song. Empty or missing returns 400 MISSING_PROMPT. Truncated to 5,000 characters |
duration_seconds | number | No | Target total length, clamped 3–600. Omit to let the model choose |
project_id / session_id | string | No | Validated like on /generate/music; nothing is stored |
The response is passed through from the music service unwrapped — it uses status, not success:
{ "status": true, "plan": { "chunks": [ { "text": "[Intro]", "duration_ms": 8000, "positive_styles": ["..."], "negative_styles": [] } ] } }A provider failure returns { "status": false, "message": "...", "code": "COMPOSITION_PLAN_FAILED" } with HTTP 422 or 500.
Reference audio
POST /api/v1/generate/music/reference-audioUploads an audio file from your media library to ElevenLabs and returns a song_id you can use in a conditioning_ref or a kept chunk. Billed like generating a track of the clip's length — per started minute at the music-v1 rate, measured from the file. ElevenLabs bills the upload itself, so this is charged even if the clip is later rejected for copyright.
| Field | Type | Required | Description |
|---|---|---|---|
media_id | string | Yes | Id of an audio media-library item you own (upload a file first with POST /v1/media/upload). Missing returns 400 MISSING_MEDIA_ID |
project_id / session_id | string | No | Where the credit usage is recorded |
The file must be on Kolbo storage (400 UNTRUSTED_MEDIA_URL) and at most 50 MB. Errors: 404 MEDIA_NOT_FOUND (not yours, not audio, or missing), 400 UNREADABLE_AUDIO (length could not be measured), 403 INSUFFICIENT_CREDITS, 503 MEDIA_PROCESSING_BUSY (retry after the Retry-After header), 422/500 REFERENCE_UPLOAD_FAILED. Like the plan route, the response is unwrapped and camelCase:
{ "status": true, "songId": "...", "durationSeconds": 42.6, "maxConditioningMs": 30000, "creditsCharged": 40 }Edit a section
POST /api/v1/generate/music/edit-sectionRegenerates one time range of an existing ElevenLabs Music v2.5 track and keeps the rest unchanged. The server builds a keep / regenerate / keep plan from the source track and creates a new generation; the source is untouched. Billed like a composition_plan of the full track length.
| Field | Type | Required | Description |
|---|---|---|---|
source_generation_id | string | Yes | The generation_id of the track to edit |
start_ms | number | Yes | Start of the region to regenerate, in milliseconds |
end_ms | number | Yes | End of the region. After clamping to the track length, end_ms - start_ms must be 3,000–120,000 ms, otherwise 400 FROM_PLAN_FAILED |
text | string | No | New section text (default "[Section]") |
positive_styles | string[] | No | Styles for the new section (default ["great production quality"]) |
negative_styles | string[] | No | Styles to avoid |
context_adherence | string | No | low, medium or high (default high) |
session_id | string | No* | *Effectively required: the source track must be in the session the edit runs in, so pass the session_id returned when the source was generated. Without it the API picks a project session that may not contain the source |
project_id | string | No | Used only when session_id is omitted |
Missing source_generation_id, start_ms or end_ms returns 400 MISSING_EDIT_PARAMS. A source not found in the session returns 404 SOURCE_NOT_FOUND; a track with no stored song reference (anything not made with ElevenLabs Music v2.5) returns 400 NOT_EDITABLE. The response is the standard Generation Started shape with model: "music-v1" — poll the new generation_id.
Extend or Cover Existing Audio
Two Suno operations work on audio you already have:
- Extend —
POST /api/v1/generate/music/extendcontinues a track from a point in the source. The source is auto-trimmed to 8 minutes. - Cover —
POST /api/v1/generate/music/coverre-records a track in a new style. The source is auto-trimmed to 2 minutes.
If the source cannot be trimmed, the request fails with 400 AUDIO_TRIM_FAILED. There is no length control on either route — duration_seconds is not read. Both always run in Suno custom mode: without lyrics and with instrumental off, lyrics are transcribed from the source or written from your prompt, and a style is generated when you omit one.
| Field | Type | Required | Description |
|---|---|---|---|
audio_url | string | One of* | Public URL of the source audio. Downloaded server-side (SSRF-validated, 400 INVALID_AUDIO_URL), max 100 MB (400 AUDIO_DOWNLOAD_FAILED) |
upload_id | string | One of* | An upload_id from /generate/music/import. Wins over audio_url when both are sent. Must be yours (404 UPLOAD_NOT_FOUND, 400 INVALID_UPLOAD_ID) and imported for the same operation (400 UPLOAD_TYPE_MISMATCH) |
rights_confirmed | boolean | With audio_url | Must be true (or "true") — you own or control all rights to the audio. Otherwise 400 RIGHTS_CONFIRMATION_REQUIRED |
confirmation_text | string | No | Free-text rights statement stored with the import (max 2,000 characters) |
prompt | string | No | What the new material should sound like. Must be a string if sent (400 INVALID_PROMPT) |
model | string | No | Suno model identifier. Same validation as /generate/music; omitted or an auto alias resolves to suno-v5.5 on these routes, and an unknown identifier returns 400 MODEL_NOT_FOUND — pass "suno-v6" explicitly if you want the current model |
style | string | No | Style / genre for the new material |
title | string | No | Generated when omitted |
lyrics | string | No | Custom lyrics |
instrumental | boolean | No | Instrumental only (default false; true or "true" enables it) |
continue_at | number | No | Extend only. Seconds into the source to continue from. Omit to continue about 10 seconds before the end |
project_id / session_id | string | No | Same as on /generate/music |
*Send audio_url (with rights_confirmed: true) or upload_id; neither returns 400 AUDIO_SOURCE_REQUIRED.
The response is the standard Generation Started shape, and the result is read the same way (Suno returns two tracks). These routes hold the connection open while the source downloads and converts, so allow a generous client timeout.
curl -X POST https://api.kolbo.ai/api/v1/generate/music/cover \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"audio_url": "https://example.com/my-song.mp3",
"rights_confirmed": true,
"model": "suno-v6",
"style": "acoustic folk, fingerpicked guitar",
"prompt": "intimate acoustic version"
}'Import once, reuse many times
POST /api/v1/generate/music/importDownloads and converts a public audio URL once and returns an upload_id to pass to extend or cover. Free — no credits, no generation.
| Field | Type | Required | Description |
|---|---|---|---|
upload_type | string | Yes | "extend" or "cover" — must match the route you will use it on. Anything else returns 400 INVALID_UPLOAD_TYPE |
audio_url | string | Yes | Public audio URL — same SSRF, 100 MB and download rules as above (400 AUDIO_SOURCE_REQUIRED when missing) |
rights_confirmed | boolean | Yes | Must be true |
confirmation_text | string | No | Free-text rights statement (max 2,000 characters) |
project_id / session_id | string | No | Where the import is attached |
{
"success": true,
"upload_id": "68f2c1a9b4e5d6f7a8b9c0e1",
"upload_type": "cover",
"audio_url": "https://...",
"duration": 184.2,
"file_size": 4420311,
"format": "mp3",
"session_id": "68f2c1a9b4e5d6f7a8b9c0d2",
"project_id": "68f2c1a9b4e5d6f7a8b9c0d3"
}Examples
Simple (Recommended)
Omit model to use the server default — the simplest way to get started:
curl -X POST https://api.kolbo.ai/api/v1/generate/music \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{"prompt": "Relaxing lo-fi hip hop beat for studying"}'With Specific Model
To choose a specific model, first fetch identifiers from GET /api/v1/models?type=music_gen, then pass the identifier value:
curl -X POST https://api.kolbo.ai/api/v1/generate/music \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"prompt": "Relaxing lo-fi hip hop beat for studying",
"model": "suno-v6"
}'With Style and Lyrics
curl -X POST https://api.kolbo.ai/api/v1/generate/music \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"prompt": "Emotional ballad",
"style": "pop",
"lyrics": "Walking through the rain\nThinking of you again",
"vocal_gender": "f"
}'Instrumental
curl -X POST https://api.kolbo.ai/api/v1/generate/music \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"prompt": "Epic orchestral trailer music",
"instrumental": true,
"style": "cinematic"
}'Response
This endpoint is asynchronous and fire-and-forget: the POST returns as soon as the job is queued and never contains audio.
Polling is the only completion mechanism. Kolbo never calls you back: there is no webhook, no callback_url field on this request body, and no route delivers outbound notifications. Socket.IO events are the web app's internal transport and are not part of the API contract — API-key generations are registered so the shared progress emitter drops their events, and the few emit sites that bypass that check are undocumented, unversioned and unsafe to build on. After the POST, loop against GET /api/v1/generate/{generation_id}/status until the job is terminal. Full contract: Polling & Cancellation.
Generation Started
{
"success": true,
"generation_id": "68f2c1a9b4e5d6f7a8b9c0d1",
"type": "music",
"model": "suno-v6",
"credits_charged": null,
"poll_url": "/v1/generate/68f2c1a9b4e5d6f7a8b9c0d1/status",
"poll_interval_hint": 8,
"session_id": "68f2c1a9b4e5d6f7a8b9c0d2",
"project_id": "68f2c1a9b4e5d6f7a8b9c0d3"
}| Field | Type | Notes |
|---|---|---|
generation_id | string | Mongo ObjectId. This is what you poll and cancel with. |
type | string | Always "music", including for the video_url scoring path |
model | string | The resolved identifier — what you sent, or suno-v6 when you omitted it or sent an unknown one. music-v1 for composition_plan requests and section edits |
credits_charged | null | Always null on this endpoint — music is priced from the finished track. Read credits_used from the completed status. |
poll_url | string | The status path without the /api prefix. Prepend https://api.kolbo.ai/api. |
poll_interval_hint | number | Suggested seconds between polls — 8 for music |
session_id / project_id | string | Where the generation lives in the Kolbo app |
Completed Status
GET /api/v1/generate/{generation_id}/status
{
"success": true,
"generation_id": "68f2c1a9b4e5d6f7a8b9c0d1",
"type": "music",
"state": "completed",
"progress": 100,
"result": {
"urls": [
"https://media.kolbo.ai/music/.../track-1.mp3",
"https://media.kolbo.ai/music/.../track-2.mp3"
],
"tracks": [
{ "title": "Summer Vibes", "duration": 187.2, "thumbnail_url": null, "model": "suno-v6" },
{ "title": "Summer Vibes", "duration": 191.0, "thumbnail_url": null, "model": "suno-v6" }
],
"title": "Summer Vibes",
"duration": 187.2,
"duration_seconds": 180,
"lyrics": "[Verse 1]\n...",
"prompt_used": "Relaxing lo-fi hip hop beat for studying",
"model": "suno-v6",
"created_at": "2026-07-20T14:20:00Z"
},
"credits_used": 24,
"credits_breakdown": [
{ "model": "suno-v6", "amount": 24, "base": 20, "final": 24, "duration_multiplier": 1, "pricing": null }
]
}The output lives in result.urls — an array of strings. Suno normally returns two variations per request, so do not assume a single track. result.tracks is index-aligned with urls and carries title, duration (seconds), thumbnail_url and model per track.
result.duration_seconds is the length you asked for (null when you sent none); result.duration is the length actually delivered. They routinely differ — asking for 45 delivered 47.32 — and result.tracks[].duration carries the real length of each returned track.
result (and credits_used / credits_breakdown) appear only under a completed state. tracks[].thumbnail_url carries the track's cover art when the provider returned one (Suno does), otherwise null. ElevenLabs Music v2.5 results also carry result.song_id — reuse it in a later composition_plan, or edit a section of that generation.
Music has an intermediate completed. Suno hands back temporary provider stream URLs before the permanent files are stored, and that intermediate status normalises to state: "completed" with progress: 70. Those URLs play, but they are not on the Kolbo CDN and they expire.
For /v1/generate/music only, treat the result as final when state === "completed" and progress === 100. Otherwise keep polling until the permanent media.kolbo.ai URLs arrive. Every genuine completion writes progress: 100, so progress is a reliable discriminator here.
Failure and cancellation
A failed generation is still an HTTP 200 with success: true — the failure is in state, with an error string carrying the provider message. cancelled carries neither result nor error, so treat all three terminal states explicitly. Music credits are deducted only after a track is delivered, so a failed generation costs nothing and POST /v1/generate/{id}/cancel reports credits_refunded: 0. See Polling & Cancellation.
JavaScript Example
const KOLBO_API_KEY = "kolbo_live_..."; // Replace with your API key
const BASE_URL = "https://api.kolbo.ai/api";
async function generateMusic() {
const response = await fetch(`${BASE_URL}/v1/generate/music`, {
method: "POST",
headers: {
"X-API-Key": KOLBO_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "Upbeat pop song about summer adventures",
style: "pop",
instrumental: false,
}),
});
const started = await response.json();
if (!started.success) throw new Error(started.error);
// Minimal loop. A production client should also handle transient HTTP errors and
// an overall timeout — see /docs/developer-api/polling-and-cancellation.
while (true) {
await new Promise((r) => setTimeout(r, started.poll_interval_hint * 1000));
const status = await fetch(
`${BASE_URL}/v1/generate/${started.generation_id}/status`,
{ headers: { "X-API-Key": KOLBO_API_KEY } }
).then((r) => r.json());
// Music only: "completed" at progress 70 means temporary streaming URLs.
// Keep polling until progress is 100 for the permanent CDN files.
if (status.state === "completed" && status.progress === 100) {
console.log("Music URLs:", status.result.urls); // usually two tracks
return status.result;
}
if (status.state === "failed") throw new Error(status.error || "Generation failed");
if (status.state === "cancelled") throw new Error("Generation was cancelled");
}
}
generateMusic().catch(console.error);Python Example
import requests
import time
KOLBO_API_KEY = "kolbo_live_..." # Replace with your API key
BASE_URL = "https://api.kolbo.ai/api"
response = requests.post(
f"{BASE_URL}/v1/generate/music",
headers={"X-API-Key": KOLBO_API_KEY},
json={
"prompt": "Cinematic orchestral theme",
"instrumental": True,
"style": "cinematic",
},
)
started = response.json()
if not started.get("success"):
raise Exception(started.get("error", "Request failed"))
# Minimal loop. Add transient-error retries and an overall timeout for production
# — see /docs/developer-api/polling-and-cancellation.
while True:
time.sleep(started["poll_interval_hint"])
status = requests.get(
f"{BASE_URL}/v1/generate/{started['generation_id']}/status",
headers={"X-API-Key": KOLBO_API_KEY},
).json()
# Music only: "completed" at progress 70 means temporary streaming URLs.
# Keep polling until progress is 100 for the permanent CDN files.
if status["state"] == "completed" and status["progress"] == 100:
print("URLs:", status["result"]["urls"]) # usually two tracks
break
if status["state"] == "failed":
raise Exception(status.get("error", "Generation failed"))
if status["state"] == "cancelled":
raise Exception("Generation was cancelled")Tips
- Use descriptive prompts for better results: include genre, mood, tempo, and instruments
- Need a specific track length? Pass
duration_seconds(clamped 5–600, then to the model's window). On Suno it works onsuno-v6andsuno-v6-wildonly, puts the request into custom mode, and does not work onsuno-v6-mini; on ElevenLabs Music and the other length-controllable models it is required or you get a ~10s track.min_output_duration/max_output_durationarenullfor music models inGET /api/v1/models?type=music_gen, so read each model'ssummarythere instead - Poll at the cadence in
poll_interval_hint(8 seconds for music) POST /v1/generate/musicand the extend / cover / import / reference-audio / edit-section routes share the standard SDK generation bucket of 30 accepted requests/minute per account (the same bucket as video, Elements and chat). Rejected requests do not count against it.POST /v1/generate/music/composition-planis outside that bucket
Related
Music Library
Search, browse, and import stock music tracks
Speech and Sound
Text-to-speech, sound effects, and custom voices
Models and Pricing
The music_gen catalog, credit costs, and duration limits
Projects
Route generations into a project with project_id
Polling and Cancellation
The state machine, the reference poll loop, failures and cancel