Audio Separation
Split a soundtrack into Dialogue, Music, Effects and a without-dialogue (M&E) track with Kolbo's own stem separation pipeline.
Split any soundtrack into separate audio layers — Dialogue, Music, 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 pipeline, not a passthrough to a single vendor. Two things make it different from a generic "vocal remover":
- It masks rather than resynthesises. The returned layers sum back to the original at −30 to −37 dB, so the audio you get back is the audio you put in, filtered — not a reconstruction of it.
- A speech classifier decides what "dialogue" means. A vocal mask is not the same thing as speech: engines, impacts and centred ambience land in it at full volume. When a clip has no spoken words, that mask is folded back into Effects instead of being handed to you labelled as Dialogue.
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.
All three endpoints accept an optional
project_idbody field that bills against and files the work into a specific project. See Projects.
Endpoints
POST /api/v1/audio/separate # 5 credits — the main split
POST /api/v1/audio/clean-dialogue # 17 credits — remove voice leaking into the M&E track
POST /api/v1/audio/ambience # 17 credits — pull room tone out of the Effects bedTwo ways to name the source
Every endpoint takes exactly one of:
| Field | Type | Description |
|---|---|---|
audio_url | string | Any 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. |
generation_id | string | The id of a Kolbo video generation you already created. |
Sending both returns 400. The two paths differ in one important way:
audio_url | generation_id | |
|---|---|---|
| Result storage | Layer URLs returned to you; nothing is stored against the source | Layers are also saved onto the generation |
| Repeat calls | Not cached — separates again and bills again | Cached; a repeat call returns the same layers for 0 credits |
| Source length | Up to 15 minutes | Whatever the generation is |
| Visible in the web app | No | Yes — the Shot Editor mixer reads the same layers |
Prefer generation_id whenever the source is a Kolbo generation.
POST /v1/audio/separate
Splits the soundtrack. 5 credits.
Request
| Field | Type | Required | Description |
|---|---|---|---|
audio_url | string | No* | Public audio/video URL to separate |
generation_id | string | No* | Kolbo video generation id to separate instead |
project_id | string | No | Project to bill against and file into |
*Exactly one of audio_url / generation_id.
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"}'Response
{
"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": "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": 5
}All layers are 48 kHz stereo WAV on the Kolbo CDN.
type | What it contains |
|---|---|
original | The untouched mix, for reference |
dialogue | Speech only |
music | Score / song bed |
sfx | Effects and Foley |
me | Everything except dialogue — the track you dub over |
Layers are omitted when the material doesn't contain them. A talking-head clip with no
score returns no music layer, and a clip with no spoken words returns no dialogue layer.
This is deliberate: shipping an empty Music row plus an sfx track byte-identical to me
would be two misleading lanes instead of one honest pair. Read has_dialogue / has_music
and branch on the type values actually present rather than assuming a fixed five.
POST /v1/audio/clean-dialogue
Strips voices still faintly audible in an M&E track. 17 credits.
Kolbo's separation leaves roughly −33 to −40 dB of speech in the M&E on clean dialogue (inaudible) and about −19 dB on a dense crowd scene (faint). Use this only when that residue actually matters to you — it trades fidelity to do its job, reconstructing the bed at about −12 dB against the −30 dB of the masking split that produced it. It is an escalation, not a step in the normal flow.
| Field | Type | Required | Description |
|---|---|---|---|
audio_url | string | No* | The me layer URL returned by /v1/audio/separate |
generation_id | string | No* | Kolbo video whose stems were already separated — cleans its M&E layer in place |
project_id | string | No | Project to bill against |
*Exactly one.
{
"success": true,
"source": "url",
"layers": [{ "type": "me", "label": "Without dialogue", "url": "https://media.kolbo.ai/.../me-....wav" }],
"passes": 2,
"margin_db": -18.4,
"already_clean": false,
"credits_used": 17
}passes is how many removal passes ran before the result stopped improving. already_clean: true
means the analysis found nothing worth removing and url is the track you sent.
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. 17 credits.
| Field | Type | Required | Description |
|---|---|---|---|
audio_url | string | No* | The sfx (or music) layer URL returned by /v1/audio/separate |
source_type | string | No | Which lane audio_url came from — "sfx" (default), "music" or "me". Labels the leftover correctly |
generation_id | string | No* | Kolbo video whose stems were already separated — picks the source lane automatically |
project_id | string | No | Project to bill against |
*Exactly one of audio_url / generation_id.
{
"success": true,
"source": "url",
"layers": [
{ "type": "sfx", "label": "Effects", "url": "https://media.kolbo.ai/.../sfx-....wav" },
{ "type": "ambience", "label": "Ambience", "url": "https://media.kolbo.ai/.../ambience-....wav" }
],
"skipped": false,
"levels": { "target": -24.1, "residual": -31.7 },
"credits_used": 17
}skipped: true means the bed had no separable ambience and layers is empty. As with
clean-dialogue, the pass ran and is billed.
Errors
| Code | Status | Meaning |
|---|---|---|
NO_AUDIO | 400 | The file has no audio stream, or its audio is digital silence. Checked before anything is charged |
SOURCE_TOO_LONG | 400 | The audio_url source is longer than 15 minutes. Trim it or split it into sections. Also checked before charging |
MISSING_SOURCE | 400 | Neither audio_url nor generation_id was provided |
INSUFFICIENT_CREDITS | 403 | Balance too low. Includes required and available |
STEMS_IN_PROGRESS | 409 | (generation_id only) A separation on that generation is already running |
RATE_LIMITED | 429 | 10 requests/minute per account, shared with the in-app surface |
Rate limits and timeouts
- 10 requests/minute per account, shared with the Kolbo web app's own separation surface, so switching between them does not double your allowance.
- 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_urlform has no cache. Two identical calls separate twice and bill twice.
Related
- Transcription — if you want the words as text, not the audio layers
- Video Editing —
generate_audioadds an AI-generated audio layer; it is not the inverse of this endpoint - Media Library —
POST /v1/media/uploadto get a public URL for a local file first