$ -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