Kolbo.AIKolbo.AI Docs
Developer API

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_id body field that routes the output into a specific project. See Projects.

Endpoint

POST /api/v1/generate/music

Related 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

FieldTypeRequiredDescription
promptstringYes*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)
modelstringNoModel 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
stylestringNoMusic 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
instrumentalbooleanNoInstrumental only, no vocals (default: false)
lyricsstringNoCustom lyrics. Supplying it switches the request out of simple mode — see the callout below
vocal_genderstringNo"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_promptbooleanNoRewrite 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
titlestringNoSong title. Auto-generated from the prompt when omitted. Always used for the track/session title; reaches Suno itself only in custom mode
negative_tagsstringNoStyles / sounds to exclude, comma-separated (e.g. "heavy metal, distortion"). Suno, custom mode only
preset_idstringNoApply a saved music style preset (GET /api/v1/presets?type=music)
duration_secondsnumberNoTarget 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_idstringNoRoute the output into a specific project. Omit for the default "API Generations" project
session_idstringNoAdd 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_planobjectNoElevenLabs Music v2.5 structured mode — { "chunks": [...] }. When present, every other field except seed, title, project_id and session_id is ignored. See Structured songs
seednumberNoReproducibility 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.

FieldTypeDescription
style_weightnumberHow strongly the style/genre is applied, 0–1. Custom mode only
weirdnessnumberCreativity / experimentation constraint, 0–1. Custom mode only
audio_weightnumberInfluence of an audio/persona reference, 0–1. Custom mode only
persona_idstringReuse a saved Suno persona / singing voice. Custom mode only
use_composition_planbooleanAccepted 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_idstringVisual 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_idstringYour 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_mediaobjectProvenance 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_seconds into custom mode (the same mode lyrics triggers — see the callout below). In custom mode a vocal request without lyrics gets lyrics written from your prompt, and a style is generated from it. If you do not want custom mode, do not send a length.
  • suno-v6-mini ignores 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[].duration per 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 without duration_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's min_output_duration / max_output_duration fields are not populated for music models today; read the model's summary in GET /api/v1/models?type=music_gen instead.

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, no duration_seconds): the Suno request carries only instrumental, the model and the (optionally enhanced) prompt. style, title, negative_tags, vocal_gender, persona_id, singing_dna_id, singing_voice_id, style_weight, weirdness and audio_weight are 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 honour vocal_gender and negative_tags in either mode; style is unused there too in simple mode.
  • Custom mode (lyrics or duration_seconds present): all of the above are forwarded, and lyrics is treated as the exact lyric sheet rather than a brief. The server auto-generates a style (from prompt, or uses the preset's style when preset_id is given), a title when you omit it, and — for a vocal track sent without lyrics — lyrics from your prompt. With instrumental: true in 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 (today sonilo/v1.1/video-to-music) — the model scores the clip natively. keep_speech_vocal, num_samples, start_offset and duration_seconds apply. The URL must resolve to a video, otherwise the generation fails with This model requires a video upload.
  • any other music model (including the default suno-v6 when you omit model) — 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.
FieldTypeRequiredDescription
video_urlstringYesPublic 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_vocalbooleanNorequires_video_input models only. Preserve the existing speech / vocals from the source video (default: false; enabled by true or the string "true")
num_samplesnumberNorequires_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_offsetnumberNorequires_video_input models only. Trim start, in seconds
duration_secondsnumberNoTrim 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 fieldTypeApplies toDescription
duration_msintegerGenerated chunkRequired. Whole milliseconds, 3000–120000, otherwise 400 INVALID_CHUNK_DURATION
textstringGenerated chunkSection label in brackets, lyric lines and inline directions, e.g. "[Chorus]\nWe rise tonight"
positive_stylesstring[]Generated chunkStyles / directions to include. The first chunk's styles set the tone of the whole song
negative_stylesstring[]Generated chunkStyles / directions to avoid
context_adherencestringGenerated chunklow, medium or high — how closely the chunk follows its neighbours
conditioning_refobjectGenerated 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 + rangestring + objectKept chunkSplice 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-plan

Free — 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.

FieldTypeRequiredDescription
promptstringYesDescription of the song. Empty or missing returns 400 MISSING_PROMPT. Truncated to 5,000 characters
duration_secondsnumberNoTarget total length, clamped 3–600. Omit to let the model choose
project_id / session_idstringNoValidated 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-audio

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

FieldTypeRequiredDescription
media_idstringYesId 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_idstringNoWhere 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-section

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

FieldTypeRequiredDescription
source_generation_idstringYesThe generation_id of the track to edit
start_msnumberYesStart of the region to regenerate, in milliseconds
end_msnumberYesEnd 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
textstringNoNew section text (default "[Section]")
positive_stylesstring[]NoStyles for the new section (default ["great production quality"])
negative_stylesstring[]NoStyles to avoid
context_adherencestringNolow, medium or high (default high)
session_idstringNo**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_idstringNoUsed 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/extend continues a track from a point in the source. The source is auto-trimmed to 8 minutes.
  • Cover — POST /api/v1/generate/music/cover re-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.

FieldTypeRequiredDescription
audio_urlstringOne of*Public URL of the source audio. Downloaded server-side (SSRF-validated, 400 INVALID_AUDIO_URL), max 100 MB (400 AUDIO_DOWNLOAD_FAILED)
upload_idstringOne 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_confirmedbooleanWith audio_urlMust be true (or "true") — you own or control all rights to the audio. Otherwise 400 RIGHTS_CONFIRMATION_REQUIRED
confirmation_textstringNoFree-text rights statement stored with the import (max 2,000 characters)
promptstringNoWhat the new material should sound like. Must be a string if sent (400 INVALID_PROMPT)
modelstringNoSuno 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
stylestringNoStyle / genre for the new material
titlestringNoGenerated when omitted
lyricsstringNoCustom lyrics
instrumentalbooleanNoInstrumental only (default false; true or "true" enables it)
continue_atnumberNoExtend only. Seconds into the source to continue from. Omit to continue about 10 seconds before the end
project_id / session_idstringNoSame 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/import

Downloads and converts a public audio URL once and returns an upload_id to pass to extend or cover. Free — no credits, no generation.

FieldTypeRequiredDescription
upload_typestringYes"extend" or "cover" — must match the route you will use it on. Anything else returns 400 INVALID_UPLOAD_TYPE
audio_urlstringYesPublic audio URL — same SSRF, 100 MB and download rules as above (400 AUDIO_SOURCE_REQUIRED when missing)
rights_confirmedbooleanYesMust be true
confirmation_textstringNoFree-text rights statement (max 2,000 characters)
project_id / session_idstringNoWhere 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

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"
}
FieldTypeNotes
generation_idstringMongo ObjectId. This is what you poll and cancel with.
typestringAlways "music", including for the video_url scoring path
modelstringThe 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_chargednullAlways null on this endpoint — music is priced from the finished track. Read credits_used from the completed status.
poll_urlstringThe status path without the /api prefix. Prepend https://api.kolbo.ai/api.
poll_interval_hintnumberSuggested seconds between polls — 8 for music
session_id / project_idstringWhere 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 on suno-v6 and suno-v6-wild only, puts the request into custom mode, and does not work on suno-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_duration are null for music models in GET /api/v1/models?type=music_gen, so read each model's summary there instead
  • Poll at the cadence in poll_interval_hint (8 seconds for music)
  • POST /v1/generate/music and 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-plan is outside that bucket