Tools: Announcing n8n-nodes-stemsplit: Stem Separation as a Native n8n Node (2026) - Complete Guide

Tools: Announcing n8n-nodes-stemsplit: Stem Separation as a Native n8n Node (2026) - Complete Guide

What You'll Learn

Prerequisites

Installing the community node

Setting up the StemSplit credential

The five operations

Three-node vocal remover

Why this beats raw HTTP + Wait + IF

Fire-and-forget for batch jobs

Supported formats and limits

Where this fits in the StemSplit ecosystem

Wrapping up If you've ever wired an audio job into n8n with raw HTTP nodes, you know the shape: an HTTP Request to submit, a Wait node, an HTTP Request to poll, an IF to check status, a loop back to Wait, and finally a Set node to flatten the result. Five nodes, two branches, one fragile timeout you'll only discover at 2am. n8n-nodes-stemsplit collapses all of that into one node. The community node is now live on npm. It exposes the StemSplit API — vocal removal, full 4-stem and 6-stem separation, job status, and balance — as five typed operations with first-class binary support, presigned download URLs, and a built-in polling loop. If you already have an HTTP Request + Wait pipeline using the StemSplit REST API, this node is a drop-in replacement — same auth, same job semantics, fewer nodes. In n8n, open Settings → Community Nodes → Install a community node, paste n8n-nodes-stemsplit, accept the risk prompt, and hit Install. n8n will install it from npm and register the new node automatically — no restart needed on most setups. For self-hosted Docker users who prefer the CLI: The package publishes with npm provenance, which is what n8n's verified-community-node program checks against. The credential type is StemSplit API. It has one required field — your API key — and the node attaches Authorization: Bearer <key> to every request against https://stemsplit.io/api/v1. n8n's built-in credential test calls GET /balance and surfaces the result, so you'll know immediately if the key is wrong before you wire it into a workflow. Both Separate Stems variants accept input as a Binary File (any upstream binary-capable node) or a public URL (StemSplit fetches it server-side, so you don't need to hold the file in n8n memory). The Output Type parameter controls which stems you get back: Each presigned URL is valid for 1 hour after job completion. Output files are deleted 14 days after creation, so this is not a long-term storage layer — pipe the URLs into S3, Drive, or your own bucket if you want them around longer. The simplest workflow that does something useful: Concretely, here's the workflow as JSON — paste it into Workflows → Import from File / URL → Paste: That's it. The Separate Stems (Wait) operation handles the polling internally — default 5-second interval, 600-second timeout, both configurable. Job fails or times out → the node throws and your workflow's error branch picks it up. Before this node, the canonical n8n recipe for any async API looked roughly like this: That works, but it has three problems that hit you in production: Separate Stems (Wait) collapses all five nodes into one. Timeout and poll interval are typed parameters. Output fields are flat — vocalsUrl, drumsUrl, bassUrl, vocalsExpiresAt, etc. — so downstream nodes can use n8n expressions directly: {{$json.vocalsUrl}}. If you've already built the HTTP Request + Wait + IF version and you're worried about migration: the underlying API is identical. The node just calls POST /jobs and GET /jobs/{id} on your behalf, with the same Authorization: Bearer header, against the same https://stemsplit.io/api/v1 base URL. Anything you've logged or persisted by id keeps working. For batch processing where you don't want to block per-item, use Separate Stems (no wait) plus Get Job later: Same polling shape as before — but the n8n state is now in your database, not in a long-running workflow execution, which is the right boundary for anything that runs longer than a couple of minutes. If you submit a job and your balance is short, the node throws a 402 INSUFFICIENT_CREDITS error — wire that to a Slack or email alert and you'll never silently drop a batch. All four ship the same HTDemucs FT weights end-to-end, so the separation quality you get in n8n matches what you'd get running the model locally. Source code, issue tracker, and version history live at github.com/StemSplit/n8n-stemsplit. The package is MIT-licensed and published with provenance, so it should pass any community node policy that checks for it. If you build something interesting with the node — a Telegram-to-stems bot, a YouTube karaoke pipeline, a Spotify-trigger acapella extractor — drop a link in the comments. I'll add the best ones to the README's example workflows section. Bug reports and feature requests are welcome on the GitHub repo. Pull requests are even more welcome. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ -weight: 500;">npm -weight: 500;">install n8n-nodes-stemsplit # then -weight: 500;">restart your n8n container -weight: 500;">npm -weight: 500;">install n8n-nodes-stemsplit # then -weight: 500;">restart your n8n container -weight: 500;">npm -weight: 500;">install n8n-nodes-stemsplit # then -weight: 500;">restart your n8n container [Read Binary File: song.mp3] → [StemSplit: Separate Stems (Wait), Output Type: VOCALS] → [HTTP Request: GET vocalsUrl → Write Binary File] [Read Binary File: song.mp3] → [StemSplit: Separate Stems (Wait), Output Type: VOCALS] → [HTTP Request: GET vocalsUrl → Write Binary File] [Read Binary File: song.mp3] → [StemSplit: Separate Stems (Wait), Output Type: VOCALS] → [HTTP Request: GET vocalsUrl → Write Binary File] { "name": "StemSplit Vocal Remover", "nodes": [ { "parameters": { "filePath": "/data/input/song.mp3" }, "name": "Read Binary File", "type": "n8n-nodes-base.readBinaryFile", "typeVersion": 1, "position": [240, 300] }, { "parameters": { "operation": "separateStemsWait", "inputSource": "binary", "binaryPropertyName": "data", "outputType": "VOCALS", "quality": "BEST", "outputFormat": "MP3", "timeoutSeconds": 600, "pollIntervalSeconds": 5 }, "name": "StemSplit", "type": "n8n-nodes-stemsplit.stemSplit", "typeVersion": 1, "position": [480, 300], "credentials": { "stemSplitApi": "StemSplit account" } }, { "parameters": { "url": "={{$json.vocalsUrl}}", "options": { "response": { "response": { "responseFormat": "file" } } } }, "name": "Download Vocals", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4, "position": [720, 300] } ], "connections": { "Read Binary File": { "main": [[{ "node": "StemSplit", "type": "main", "index": 0 }]] }, "StemSplit": { "main": [[{ "node": "Download Vocals", "type": "main", "index": 0 }]] } } } { "name": "StemSplit Vocal Remover", "nodes": [ { "parameters": { "filePath": "/data/input/song.mp3" }, "name": "Read Binary File", "type": "n8n-nodes-base.readBinaryFile", "typeVersion": 1, "position": [240, 300] }, { "parameters": { "operation": "separateStemsWait", "inputSource": "binary", "binaryPropertyName": "data", "outputType": "VOCALS", "quality": "BEST", "outputFormat": "MP3", "timeoutSeconds": 600, "pollIntervalSeconds": 5 }, "name": "StemSplit", "type": "n8n-nodes-stemsplit.stemSplit", "typeVersion": 1, "position": [480, 300], "credentials": { "stemSplitApi": "StemSplit account" } }, { "parameters": { "url": "={{$json.vocalsUrl}}", "options": { "response": { "response": { "responseFormat": "file" } } } }, "name": "Download Vocals", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4, "position": [720, 300] } ], "connections": { "Read Binary File": { "main": [[{ "node": "StemSplit", "type": "main", "index": 0 }]] }, "StemSplit": { "main": [[{ "node": "Download Vocals", "type": "main", "index": 0 }]] } } } { "name": "StemSplit Vocal Remover", "nodes": [ { "parameters": { "filePath": "/data/input/song.mp3" }, "name": "Read Binary File", "type": "n8n-nodes-base.readBinaryFile", "typeVersion": 1, "position": [240, 300] }, { "parameters": { "operation": "separateStemsWait", "inputSource": "binary", "binaryPropertyName": "data", "outputType": "VOCALS", "quality": "BEST", "outputFormat": "MP3", "timeoutSeconds": 600, "pollIntervalSeconds": 5 }, "name": "StemSplit", "type": "n8n-nodes-stemsplit.stemSplit", "typeVersion": 1, "position": [480, 300], "credentials": { "stemSplitApi": "StemSplit account" } }, { "parameters": { "url": "={{$json.vocalsUrl}}", "options": { "response": { "response": { "responseFormat": "file" } } } }, "name": "Download Vocals", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4, "position": [720, 300] } ], "connections": { "Read Binary File": { "main": [[{ "node": "StemSplit", "type": "main", "index": 0 }]] }, "StemSplit": { "main": [[{ "node": "Download Vocals", "type": "main", "index": 0 }]] } } } [Schedule Trigger: every 1 min] → [Postgres: SELECT pending_song_urls] → [StemSplit: Separate Stems, Input: URL] ← returns job ID instantly → [Postgres: UPDATE songs SET job_id, -weight: 500;">status='SUBMITTED'] # separate workflow [Schedule Trigger: every 30s] → [Postgres: SELECT job_id WHERE -weight: 500;">status='SUBMITTED'] → [StemSplit: Get Job] → [IF: -weight: 500;">status === 'COMPLETED'] → [HTTP Request: download URLs to S3] → [Postgres: UPDATE songs SET -weight: 500;">status='DONE'] [Schedule Trigger: every 1 min] → [Postgres: SELECT pending_song_urls] → [StemSplit: Separate Stems, Input: URL] ← returns job ID instantly → [Postgres: UPDATE songs SET job_id, -weight: 500;">status='SUBMITTED'] # separate workflow [Schedule Trigger: every 30s] → [Postgres: SELECT job_id WHERE -weight: 500;">status='SUBMITTED'] → [StemSplit: Get Job] → [IF: -weight: 500;">status === 'COMPLETED'] → [HTTP Request: download URLs to S3] → [Postgres: UPDATE songs SET -weight: 500;">status='DONE'] [Schedule Trigger: every 1 min] → [Postgres: SELECT pending_song_urls] → [StemSplit: Separate Stems, Input: URL] ← returns job ID instantly → [Postgres: UPDATE songs SET job_id, -weight: 500;">status='SUBMITTED'] # separate workflow [Schedule Trigger: every 30s] → [Postgres: SELECT job_id WHERE -weight: 500;">status='SUBMITTED'] → [StemSplit: Get Job] → [IF: -weight: 500;">status === 'COMPLETED'] → [HTTP Request: download URLs to S3] → [Postgres: UPDATE songs SET -weight: 500;">status='DONE'] - ✅ How to -weight: 500;">install n8n-nodes-stemsplit from the n8n Community Nodes UI - ✅ How to set up the StemSplit API credential (single field, sk_live_ key) - ✅ The five operations the node exposes and when to use each one - ✅ How to build a vocal remover in three nodes — Read Binary File → Separate Stems (Wait) → HTTP Request - ✅ Why Separate Stems (Wait) removes the HTTP + Wait + IF polling pattern entirely - ✅ A workflow JSON snippet you can paste into n8n today - n8n v0.200 or later (self-hosted or n8n.cloud with community nodes enabled) - A StemSplit API key — sign up at stemsplit.io; the free tier is enough to run the workflow below - Add a new credential of type StemSplit API - Generate a key at stemsplit.io/app/settings/api (format: sk_live_...) - Paste it in and save - HTTP Request → submit job, capture id - Wait → 5 seconds - HTTP Request → poll GET /jobs/{id} - IF → -weight: 500;">status === "COMPLETED"? branch out, else loop back to step 2 - Set → flatten the response - Loop-back execution semantics in n8n are subtle. A wait-loop counts as separate executions if you use a sub-workflow trigger, fine if you use the in-node loop, and "depends on your version" if you mix paradigms. You'll debug this once and never want to again. - Timeout handling is manual. You have to track wall-clock yourself and break the loop. Easy to get wrong, easy to leave running for hours on a stuck job. - The output is a polling response, not a result. You still need a Set node to extract data.stems.vocals, data.stems.drums, etc., and you have to remember which API version returned which shape. - The community node (this post) — for n8n workflow builders - The REST API directly — if you'd rather call it from Python, Node, Go, or anything else - The hosted StemSplit vocal remover — same model, browser UI, no plumbing - Self-hosted via HT-Demucs FT ONNX — if you want the model on your own GPU and zero API dependency