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

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 only when video_url is supplied (see Score an existing video)
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-v5.5
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) 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". On Suno the value is forwarded verbatim to the provider (which expects exactly those two), 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_promptbooleanNoEnhance the prompt (default: true — only an explicit false, boolean or the string "false", disables it)
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–300 by the API, and the Suno provider itself accepts 10–360, so a value under 10 is raised to 10. Omit it for Suno's own natural length (the web app calls this Auto). 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

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_planbooleanEnable structured composition planning (verse/chorus). Forwarded in both modes
singing_dna_idstringVisual DNA character whose singing voice to use. Must be owned by the caller, and the DNA must have a singing voice attached — otherwise it is silently ignored. Resolves to persona_id, so it takes effect in custom mode only
singing_voice_idstringCustom cloned singing-voice id. Must be owned by the caller and have voiceType: "singing" — otherwise silently ignored. Resolves to persona_id, so it takes effect in custom mode only
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 still resolves to suno-v5.5.

The current Suno family is suno-v6 (the default music model in the Kolbo app), 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; pass "model": "suno-v6" explicitly for new integrations.

Song length: duration_seconds is honoured on suno-v6 and suno-v6-wild, in custom mode only.

  • The provider rejects a length in simple mode, so the API automatically switches a request carrying duration_seconds into custom mode (the same mode lyrics triggers — see the callout above). 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, music-v1) default to a ~10s track without duration_seconds, so always pass it there. Check min_output_duration / max_output_duration on the model in GET /api/v1/models?type=music_gen.

lyrics is the switch between simple mode and custom mode, and custom mode is what unlocks the Suno controls.

Server-side, useSimpleMode is !lyrics and customMode is its inverse — so passing lyrics puts the request in custom mode, and omitting it leaves you in simple mode.

  • Simple mode (no lyrics): the Suno request carries only instrumental, the model, the enhanced prompt, plus use_composition_plan and the duration_seconds-derived length. 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 present): all of the above are forwarded, and your text is treated as the exact lyric sheet rather than a brief for generated lyrics. The server also auto-generates a style (from prompt, or from preset_id when given) and a title when you omit them.

Leave lyrics out when you want the model to write the words; supply it (even a rough draft) 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 — 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 — 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_secondsnumberNorequires_video_input models only. Trim duration, in seconds — not clamped to 5–300 here. 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 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",
    "prompt": "tense, driving percussion under the chase",
    "num_samples": 2,
    "duration_seconds": 30
  }'

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-v5.5"
  }'

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-v5.5",
  "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 identifier you sent, or the default suno-v5.5
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-v5.5" },
      { "title": "Summer Vibes", "duration": 191.0, "thumbnail_url": null, "model": "suno-v5.5" }
    ],
    "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-v5.5",
    "created_at": "2026-07-20T14:20:00Z"
  },
  "credits_used": 24,
  "credits_breakdown": [
    { "model": "suno-v5.5", "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 is always null today — cover art is stored under a field the API does not surface, so do not build on it.

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–300). 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. Read min_output_duration / max_output_duration from GET /api/v1/models?type=music_gen
  • Poll at the cadence in poll_interval_hint (8 seconds for music)
  • POST /v1/generate/music is rate-limited to the standard SDK generation bucket of 10 requests/minute per account