Kolbo.AIKolbo.AI Docs
Developer API

Audio Separation

Split a soundtrack into Dialogue, Music, Ambience, Effects and a without-dialogue (M&E) track with Kolbo's stem separation pipeline.

Split any soundtrack into separate audio layers — Dialogue, Music, Ambience, Effects, and a without-dialogue (M&E) track. Built for dubbing and localisation, podcast cleanup, re-scoring, and handing an editor usable stems.

This is Kolbo's own multi-pass pipeline, not a single "vocal remover" call. The layers are pulled out one after another from the remainder of the previous pass — speech first, then music from what is left, then ambience from what is left after that — so the lanes stay disjoint instead of each one being extracted independently from the full mix. A second pass also lifts cinematic impacts and whooshes out of the Music lane and folds them into Effects.

These endpoints answer inline. There is no generation id to poll — the response body carries the finished layer URLs. A typical clip takes 20–90 seconds. Do not call /v1/generate/:id/status on the result.

On the audio_url path, all three endpoints accept an optional project_id body field that bills against and files the work into a specific project (otherwise your default API project is used). On the generation_id path, project_id is ignored — the generation's own project is billed. See Projects.

Endpoints

POST /api/v1/audio/separate         # the main split
POST /api/v1/audio/clean-dialogue   # remove voice leaking into the M&E track
POST /api/v1/audio/ambience         # pull room tone out of the Effects (or Music) bed

Pricing

Every stems action is priced by the length of the audio it processes: 20 credits per started 30 seconds. A 45-second clip costs 40 credits; a 3-minute clip costs 120. The duration is measured on the file that action actually processes — the source for separate, the M&E track for clean-dialogue, the Effects/Music bed for ambience. The exact amount charged comes back as credits_used.

Before any work starts, the request is refused if your balance is below 20 credits. The exact, duration-based price is then checked against your balance before anything is reserved.

Two ways to name the source

Every endpoint takes one of:

FieldTypeDescription
audio_urlstringAny public audio or video URL — a Kolbo CDN asset, an upload_media result, or any direct link. Aliases video_url, media_url, file_url and url are accepted and behave identically (the first non-empty one wins).
generation_idstringThe id of a Kolbo video generation you already created, and can edit.

If you send both, generation_id wins and the URL is ignored. The two paths differ in important ways:

audio_urlgeneration_id
Result storageLayer URLs returned to you; nothing is stored against the sourceLayers are also saved onto the generation
Repeat callsNot cached — separates again and bills againCached; a repeat call returns the same layers for 0 credits (cached: true). A full split also answers a later dialogue request, but a dialogue-only split is re-run (and billed) when you ask for full
Source limitsUp to 15 minutes and 2 GBWhatever the generation is
project_idHonouredIgnored — the generation's project is billed
Visible in the web appAs a stem-split entry in Audio Studio historyYes — the Shot Editor mixer reads the same layers
Extra response fieldshas_dialogue, has_music (separate); passes, margin_db, already_clean (clean-dialogue); skipped, levels (ambience)generation_id, cached

Prefer generation_id whenever the source is a Kolbo generation.

POST /v1/audio/separate

Splits the soundtrack.

Request

FieldTypeRequiredDescription
audio_urlstringNo*Public audio/video URL to separate (aliases: video_url, media_url, file_url, url)
generation_idstringNo*Kolbo video generation id to separate instead
modestringNo"full" (default) — every layer. "dialogue" — a single pass returning only dialogue and me (plus original). Any other value returns 400 INVALID_MODE
project_idstringNoProject to bill against and file into (audio_url path only)

*One of audio_url / generation_id is required.

curl -X POST https://api.kolbo.ai/api/v1/audio/separate \
  -H "X-API-Key: kolbo_live_..." \
  -H "Content-Type: application/json" \
  -d '{"audio_url": "https://media.kolbo.ai/uploads/interview.mp4", "mode": "full"}'

Response

audio_url path, mode: "full":

{
  "success": true,
  "source": "url",
  "layers": [
    { "type": "original", "label": "Original",          "url": "https://media.kolbo.ai/.../original.wav" },
    { "type": "dialogue", "label": "Dialogue",          "url": "https://media.kolbo.ai/.../dialogue.wav" },
    { "type": "music",    "label": "Music",             "url": "https://media.kolbo.ai/.../music.wav" },
    { "type": "ambience", "label": "Ambience",          "url": "https://media.kolbo.ai/.../ambience.wav" },
    { "type": "sfx",      "label": "Effects",           "url": "https://media.kolbo.ai/.../sfx.wav" },
    { "type": "me",       "label": "Without dialogue",  "url": "https://media.kolbo.ai/.../me.wav" }
  ],
  "has_dialogue": true,
  "has_music": true,
  "credits_used": 40,
  "credit_usage_id": "6650f1c2..."
}

generation_id path:

{
  "success": true,
  "source": "generation",
  "generation_id": "664f...",
  "layers": [ { "type": "original", "label": "Original", "url": "..." } ],
  "cached": false,
  "credits_used": 40,
  "credit_usage_id": "6650f1c2..."
}

All layers are WAV files on the Kolbo CDN. Do not assume the stems are stereo — the separation model can return mono even for a stereo source.

typeWhat it contains
originalThe untouched mix, for reference
dialogueSpeech only
musicScore / song bed (full mode only)
ambienceRoom tone / atmosphere (full mode only)
sfxEffects and Foley, including impacts lifted out of the music (full mode only)
meEverything except dialogue — the track you dub over

Every layer of the chosen mode is returned, even when it is near-silent. A talking-head clip with no score still returns a music layer. Read has_dialogue / has_music (audio_url path) to tell whether those lanes actually carry sound before presenting them to a user, and branch on the type values present rather than on array positions.

POST /v1/audio/clean-dialogue

Strips voices still faintly audible in an M&E track. Billed at the stems rate on the length of the M&E track.

Use this only when residual speech in the M&E actually matters to you — it trades fidelity to do its job, reconstructing the bed less cleanly than the split that produced it. It runs up to three removal passes and stops as soon as a pass pulls nothing meaningful. It is an escalation, not a step in the normal flow.

FieldTypeRequiredDescription
audio_urlstringNo*The me layer URL returned by /v1/audio/separate (same aliases as above)
generation_idstringNo*Kolbo video whose stems were already separated — cleans its M&E layer in place. Returns 400 if it has not been split yet
project_idstringNoProject to bill against (audio_url path only)

*One of audio_url / generation_id is required.

{
  "success": true,
  "source": "url",
  "layers": [{ "type": "me", "label": "Without dialogue", "url": "https://media.kolbo.ai/.../me.wav" }],
  "passes": 2,
  "margin_db": -38.2,
  "already_clean": false,
  "credits_used": 40,
  "credit_usage_id": "6650f1c2..."
}

passes is how many removal passes were kept (0–3). margin_db is the level of the last attempted pull relative to the track it was pulled from. already_clean: true (passes: 0) means the analysis found nothing worth removing and url is the track you sent.

On the generation_id path the response is the generation shape (source: "generation", generation_id, the full updated layers list, cached). A generation whose M&E was already cleaned returns cached: true for 0 credits.

The analysis pass runs — and is billed — even when the track turns out to be already clean. Check already_clean before telling a user something was removed.

POST /v1/audio/ambience

Pulls room tone / atmosphere out of an Effects or Music bed, leaving hard effects behind. Billed at the stems rate on the length of the bed.

A full-mode separate already returns an ambience layer, so this endpoint is mainly for beds you did not split with Kolbo, or for a music lane you want to pull further.

FieldTypeRequiredDescription
audio_urlstringNo*The sfx (or music) layer URL returned by /v1/audio/separate (same aliases as above)
source_typestringNoaudio_url path only. Which lane audio_url came from — "sfx" (default), "music" or "me". Sets the type of the leftover layer; unknown values fall back to "sfx"
generation_idstringNo*Kolbo video whose stems were already separated — picks the source lane automatically (sfx, then music, then me)
project_idstringNoProject to bill against (audio_url path only)

*One of audio_url / generation_id is required.

{
  "success": true,
  "source": "url",
  "layers": [
    { "type": "ambience", "label": "Ambience", "url": "https://media.kolbo.ai/.../ambience.wav" },
    { "type": "sfx",      "label": "Effects",  "url": "https://media.kolbo.ai/.../sfx.wav" }
  ],
  "skipped": false,
  "levels": { "target": -24.1, "residual": -31.7 },
  "credits_used": 20,
  "credit_usage_id": "6650f1c2..."
}

levels are the mean levels (dBFS) of the pulled ambience (target) and what was left (residual). The leftover layer is only returned when it is audible — if the whole bed was ambience, you get the ambience layer alone. skipped: true means the bed had no separable ambience and layers is empty. As with clean-dialogue, the pass ran and is billed.

On the generation_id path the response is the generation shape with the full updated layers list. A generation split in full mode already includes ambience, so the call returns it as cached: true.

Errors

Error bodies carry success: false, a human-readable error (some codes use message instead), and a code where one applies.

CodeStatusMeaning
INVALID_MODE400mode is not full or dialogue
MISSING_SOURCE400Neither audio_url (or an alias) nor generation_id was provided
NO_AUDIO400The file has no audio stream, or its audio is digital silence. Checked before anything is charged
SOURCE_TOO_LONG400The audio_url source is longer than 15 minutes. Trim it or split it into sections. Also checked before charging
STEMS_REQUIRED / NO_PRO_SOURCE400(ambience + generation_id) The generation has not been split with the current pipeline, or has no Effects/Music/M&E lane
INSUFFICIENT_CREDITS403Balance too low. The 20-credit pre-check includes required and balance; the duration-based check includes required and available on the audio_url path only
SUBSCRIPTION_INACTIVE403The paying account cannot spend credits
—404(generation_id only) Generation not found or you lack edit access
STEMS_IN_PROGRESS / STEMS_REFINE_IN_PROGRESS / STEMS_PRO_IN_PROGRESS409(generation_id only) The same operation on that generation is already running
STEMS_BILLING_PENDING / STEMS_BILLING_IN_PROGRESS409(generation_id only) A previous run's billing is still settling — retry shortly
—429More than 10 accepted requests per minute. This body uses status: false (not success) and includes error, message, limit, window_seconds and retryAfter, plus a Retry-After header

Rate limits and timeouts

  • 10 requests/minute per account across the three endpoints. Only accepted requests count; a rejected call does not spend the budget. The web app's separation surface has its own, equal 10/minute ceiling.
  • The route holds its connection for the whole run and gives up at 10 minutes. Set your HTTP client's timeout above that; a shorter client timeout aborts your side while the server keeps working, and you lose the result.
  • Concurrent calls are independent — but each one spends credits, and the audio_url form has no cache. Two identical calls separate twice and bill twice.
  • Transcription — if you want the words as text, not the audio layers
  • Video Editing — generate_audio adds an AI-generated audio layer; it is not the inverse of this endpoint
  • Media Library — POST /v1/media/upload to get a public URL for a local file first