Quick start
Split a song into vocals and instrumental and download both stems, in cURL, JavaScript or Python.
Get a key
API keys work with bought credits only. The free daily credits apply to the web app. Buy a pack on Credits, then create a key on API keys. The key is shown once. Store it in an environment variable:
Shell
export DISBAND_KEY="disband_…"Split a song
The program uploads song.mp3, waits until the separation is ready and saves each stem as an MP3 file. It
requires curl and jq, Node.js 22 or later, or Python with requests.
id=$(curl -sf "https://api.disband.io/v1/separations" \
--url-query stems=vocals --url-query stems=instrumental \
-H "Authorization: Bearer $DISBAND_KEY" \
--data-binary @song.mp3 | jq -r .id)
while :; do
separation=$(curl -sf "https://api.disband.io/v1/separations/$id" \
-H "Authorization: Bearer $DISBAND_KEY" \
-H "Prefer: wait=60")
status=$(jq -r .status <<< "$separation")
[ "$status" = queued ] || [ "$status" = separating ] || break
done
for stem in $(jq -r '.stems | to_entries[] | select(.value.mp3) | .key' <<< "$separation"); do
curl -sf "https://api.disband.io/v1/separations/$id/stems/$stem/mp3" \
-H "Authorization: Bearer $DISBAND_KEY" -o "$stem.mp3"
doneimport { readFile, writeFile } from "node:fs/promises";
const api = "https://api.disband.io/v1";
const headers = { Authorization: `Bearer ${process.env.DISBAND_KEY}` };
let separation = await fetch(`${api}/separations?stems=vocals&stems=instrumental`, {
method: "POST",
headers,
body: await readFile("song.mp3"),
}).then((response) => response.json());
while (separation.status === "queued" || separation.status === "separating") {
separation = await fetch(`${api}/separations/${separation.id}`, {
headers: { ...headers, Prefer: "wait=60" },
}).then((response) => response.json());
}
if (separation.status === "failed") throw new Error(separation.problem.title);
for (const [stem, files] of Object.entries(separation.stems)) {
if (files.silent) continue;
const file = await fetch(files.mp3.url, { headers });
await writeFile(`${stem}.mp3`, Buffer.from(await file.arrayBuffer()));
}import os
import requests
api = "https://api.disband.io/v1"
headers = {"Authorization": f"Bearer {os.environ['DISBAND_KEY']}"}
with open("song.mp3", "rb") as song:
separation = requests.post(
f"{api}/separations",
params={"stems": ["vocals", "instrumental"]},
headers=headers,
data=song,
).json()
while separation["status"] in ("queued", "separating"):
separation = requests.get(
f"{api}/separations/{separation['id']}",
headers={**headers, "Prefer": "wait=60"},
).json()
if separation["status"] == "failed":
raise RuntimeError(separation["problem"]["title"])
for stem, files in separation["stems"].items():
if files.get("silent"):
continue
with open(f"{stem}.mp3", "wb") as out:
out.write(requests.get(files["mp3"]["url"], headers=headers).content)What happens
POST /separationsreturns202 Acceptedwith the separation'sid,status: "queued"and itschargein credits.- Each
GET /separations/{id}withPrefer: wait=60returns when the status changes, fromqueuedtoseparatingtoready, or after 60 seconds. - The ready separation lists the files of each stem with their URLs and sizes. Downloads require the same key. Files are deleted one hour after the separation is ready.
A stem with no audio in the song, for example vocals in an instrumental track, is returned as {"silent": true} without files. The program skips these.
Next steps
- Separations: all parameters, statuses and downloads.
- Credits: how the price is calculated.
- Errors: error responses and which ones to retry.