Skip to main content

stemsplit-python

PyPI version Python versions License: MIT Downloads Tests GitHub stars

Official Python SDK for the StemSplit stem-separation API. Synchronous client, typed pydantic v2 models, automatic file uploads, job-completion polling, YouTube jobs, BPM / key detection, and a Stripe-style error hierarchy. Three runtime dependencies. No PyTorch, no numpy, no native code.

pip install stemsplit-python
from pathlib import Path
from stemsplit_python import StemSplit

client = StemSplit(api_key="sk_live_...")          # or set STEMSPLIT_API_KEY
job = client.jobs.create(audio=Path("song.mp3"), output_type="BOTH").wait()
job.download_all("./out/")                         # vocals.mp3 + instrumental.mp3

Quick links

Features

  • Sync client StemSplit with sub-resources for every public endpoint
  • Automatic file uploads — pass a Path, bytes, or file-like object and the SDK handles the presigned-URL dance for you
  • job.wait() / job.iter_progress() / job.download_all() ergonomic helpers — no more polling loops in your code
  • YouTube jobs — kick off a separation directly from a YouTube URL, no download step required
  • BPM and key detection typed and surfaced on Job.audio_metadata
  • Webhook signature verification in two lines, Stripe-style: webhooks.verify_and_parse(...)
  • Stripe-style error hierarchy with documented error codes, .retry_after, .required_seconds, .purchase_url where the API exposes them
  • Retries with Retry-After-aware backoff on 429 / 5xx for safe-to-replay verbs
  • Rate-limit introspection — every response's X-RateLimit-* headers parsed and exposed via client.last_rate_limit
  • Idempotency-Key passthrough so adopters can opt-in once the server lights up support
  • Forward-compatible models (extra="allow") — server adds a new field, your code keeps running
  • Fully typed — py.typed shipped, mypy --strict clean

When to use this SDK vs the alternatives

You want to … Use
Call the hosted StemSplit API from Python stemsplit-python (this package)
Run HT-Demucs locally with no API key, no GPU pool, no per-minute pricing demucs-onnx — same model family, runs in pure ONNX Runtime
Use the API from curl / your own HTTP client The API reference — every endpoint is straightforward JSON
Wire StemSplit into n8n / Zapier / Make The n8n node and no-code guides

Quick start

Install

pip install stemsplit-python                  # everything you need
pip install "stemsplit-python[dev]"           # adds pytest, ruff, mypy, respx for contributors

Python 3.10 / 3.11 / 3.12 / 3.13 supported on macOS, Linux, and Windows.

Authenticate

Get an API key from Settings → API Keys and either pass it explicitly or set the STEMSPLIT_API_KEY environment variable:

from stemsplit_python import StemSplit

client = StemSplit(api_key="sk_live_...")     # explicit
client = StemSplit()                          # reads STEMSPLIT_API_KEY

Separate stems from a local file

from pathlib import Path
from stemsplit_python import StemSplit

client = StemSplit()

job = client.jobs.create(
    audio=Path("song.mp3"),                   # Path | str | bytes | file-like
    stems=["vocals", "instrumental"],         # subset shorthand
    quality="BEST",                           # FAST | BALANCED | BEST
    output_format="MP3",                      # MP3 | WAV | FLAC
    output_type=None,                         # …or pass output_type directly
    metadata={"customer_id": 42},             # echoed back in webhooks
    idempotency_key=None,                     # passthrough
)

print(job.id, job.status)                     # job_abc123  PENDING
final = job.wait(timeout=300, poll_interval=5)
print(final.audio_metadata.bpm, final.audio_metadata.key)
job.download_all("./out/")                    # writes vocals.mp3 + instrumental.mp3

Stream progress instead of blocking

for snapshot in job.iter_progress(poll_interval=2.0, timeout=300):
    print(snapshot.status, snapshot.progress)

Separate from a public URL (no upload step)

job = client.jobs.create(
    source_url="https://example.com/song.mp3",
    output_type="FOUR_STEMS",
).wait()

Per-stem downloads

job.outputs.vocals.url           # presigned URL, valid ~1h
job.outputs.vocals.expires_at    # datetime
# downloaded one-by-one if you want different paths:
client = StemSplit()
job = client.jobs.get("job_abc123").wait()
for stem, output in job.outputs.as_dict().items():
    print(stem, output.url, output.expires_at)

YouTube jobs

yt = client.youtube_jobs.create(
    url="https://youtube.com/watch?v=dQw4w9WgXcQ",
).wait(timeout=900)

print(yt.video_title, yt.video_duration)
print(yt.audio_metadata.bpm, yt.audio_metadata.key)
yt.download_all("./out/")        # full_audio + vocals + instrumental

List + iterate jobs

client.jobs.list(limit=20, status="COMPLETED")    # one page
for job in client.jobs.iter_all(status="COMPLETED"):
    print(job.id, job.progress)

Account balance + rate limits

balance = client.account.get()
print(balance.balance_formatted)                  # "5 minutes"

client.jobs.list(limit=1)                         # any call updates this
print(client.last_rate_limit.remaining)           # 59

Webhooks

Register a subscription, capture the secret (only shown once), and verify deliveries on your endpoint.

Register

from stemsplit_python import StemSplit

client = StemSplit()
hook = client.webhooks.create(
    url="https://your-server.com/stemsplit-webhook",
    events=["job.completed", "job.failed"],       # default if omitted
)
print(hook.secret)                                # whsec_… — save this now
client.webhooks.list()
client.webhooks.delete(hook.id)

FastAPI handler

import os
from fastapi import FastAPI, Request, Response
from stemsplit_python import SignatureVerificationError, webhooks

app = FastAPI()
SECRET = os.environ["STEMSPLIT_WEBHOOK_SECRET"]

@app.post("/stemsplit-webhook")
async def handle(request: Request) -> Response:
    raw = await request.body()
    try:
        event = webhooks.verify_and_parse(
            payload=raw,
            signature=request.headers["X-Webhook-Signature"],
            secret=SECRET,
        )
    except SignatureVerificationError:
        return Response(status_code=401)

    if event.event == "job.completed":
        outputs = event.data.outputs
        # download outputs.vocals.url etc. here, ideally in a background task —
        # webhook delivery has a 10-second budget.
    return Response(status_code=200)

Flask handler

import os
from flask import Flask, request, abort
from stemsplit_python import SignatureVerificationError, webhooks

app = Flask(__name__)
SECRET = os.environ["STEMSPLIT_WEBHOOK_SECRET"]

@app.post("/stemsplit-webhook")
def handle():
    try:
        event = webhooks.verify_and_parse(
            payload=request.data,
            signature=request.headers["X-Webhook-Signature"],
            secret=SECRET,
        )
    except SignatureVerificationError:
        abort(401)
    print(event.event, event.data.job_id)
    return ("", 200)

The signing scheme is HMAC-SHA256 of the raw request body. The library takes care of constant-time comparison and accepts both sha256=<hex> and bare-hex signatures.

Error handling

Every non-2xx response from the API maps to a typed exception. The base class is StemSplitError so a single except is enough for callers that don't care about the distinction.

from stemsplit_python import (
    StemSplit,
    AuthenticationError,
    InsufficientCreditsError,
    JobFailedError,
    RateLimitError,
)

client = StemSplit()
try:
    job = client.jobs.create(source_url="https://example.com/song.mp3").wait()
except AuthenticationError:
    raise SystemExit("API key invalid or missing — check STEMSPLIT_API_KEY")
except InsufficientCreditsError as e:
    print(f"Need {e.required_seconds}s of credit. Buy more at {e.purchase_url}")
except RateLimitError as e:
    print(f"Slow down — retry after {e.retry_after}s")
except JobFailedError as e:
    print(f"Job {e.job_id} failed: {e.error_message}")
Exception HTTP Documented codes
BadRequestError 400 FILE_TOO_LARGE, AUDIO_TOO_LONG, AUDIO_TOO_SHORT, UNSUPPORTED_FORMAT
AuthenticationError 401 MISSING_API_KEY, INVALID_API_KEY
InsufficientCreditsError 402 INSUFFICIENT_CREDITS (exposes .required_seconds, .purchase_url)
PermissionDeniedError 403 API_KEY_REVOKED
NotFoundError 404 JOB_NOT_FOUND
ConflictError 409 reserved
UnprocessableEntityError 422 reserved
RateLimitError 429 RATE_LIMIT_EXCEEDED (exposes .retry_after, .limit, .remaining, .reset_at)
InternalServerError 5xx server bugs / outages
JobFailedError — logical: surfaced by .wait() when status is FAILED
JobExpiredError — logical: surfaced by .wait() when status is EXPIRED
SignatureVerificationError — webhook signature mismatch
APITimeoutError — client-side timeout
APIError — transport-level failure (DNS, parse, …)

Async support

The async surface ships in v0.2. Tracking issue: github.com/StemSplit/stemsplit-python/issues. Until then, AsyncStemSplit is exported as a placeholder that raises NotImplementedError; use the sync StemSplit client and offload to a thread if you're inside an async app.

Configuration reference

StemSplit(
    api_key=None,                            # str | None — env STEMSPLIT_API_KEY
    base_url=None,                           # str | None — defaults to https://stemsplit.io/api/v1
    timeout=None,                            # float | None — read 30s, connect 10s defaults
    max_retries=3,                           # int — 429 + 5xx + safe-to-replay verbs only
    http_client=None,                        # httpx.Client | None — bring your own transport
)

The SDK always sends User-Agent: stemsplit-python/<version> python/<v> httpx/<v> (<os>).

Companion package — run separation locally

Want to run inference without an API key (no quotas, no per-minute pricing, no network)? Install demucs-onnx — same model family that powers the StemSplit production stack, exported to ONNX, runs on CPU / CoreML / CUDA / DirectML.

pip install demucs-onnx
demucs-onnx separate song.mp3 stems/ --karaoke --mp3

The two packages are designed to coexist: prototype against the local-inference path with demucs-onnx, then swap in stemsplit-python when you need shared GPU capacity, the YouTube ingest, BPM / key detection, or webhook delivery.

Limits, billing, and supported formats

The hosted API caps audio at 60 minutes and 50 MB per upload, with a 60 req/min rate limit. Pricing is per second of audio processed; new accounts get 5 free minutes. See stemsplit.io/pricing for current rates and the developer docs for the full limits table.

Supported input formats: MP3, WAV, FLAC, M4A, AAC, OGG, Opus, WebM. Output formats: MP3, WAV, FLAC.

Contributing

git clone https://github.com/StemSplit/stemsplit-python
cd stemsplit-python
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"

ruff check src tests && ruff format --check src tests
mypy src/stemsplit_python
pytest

Issues, PRs, and questions are welcome at github.com/StemSplit/stemsplit-python.

License

MIT — see LICENSE.

Metadata

Release files for stemsplit-python 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for stemsplit-python 0.1.0
File Size Uploaded
stemsplit_python-0.1.0.tar.gz 28.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for stemsplit-python 0.1.0
File Interpreter ABI Platform
stemsplit_python-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 63.0 kB

Release files / stemsplit_python-0.1.0.tar.gz

Download URL stemsplit_python-0.1.0.tar.gz
Size 28.7 kB
Tags Source
SHA-256 checksum
How to use checksums
c5fc0f13b4f7647d351981284537cc5559f970c7a175afc6ac8b011cbc8e29f6
BLAKE2b-256 checksum
How to use checksums
79efde98c8a99009a41d13c3d9c3cac5a9d305dd8b43661841f22e3d159b3347
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / stemsplit_python-0.1.0-py3-none-any.whl

Download URL stemsplit_python-0.1.0-py3-none-any.whl
Size 34.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
99433055b7e7b45a36ecf8832acf53c4c1799b18cc3d19171476f36c578d7c41
BLAKE2b-256 checksum
How to use checksums
0ffb9fdd4ed75ceba15b8c317f9a802d089217e4b269b9c27f1f387303952db1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page