White-Label API Integration
B2BBuild stem separation into your product
Who This Is For
You're building a product and want to add AI stem separation as a feature - without building the ML infrastructure yourself.
Why Use StemSplit API?
No ML Infrastructure
Skip the $50k+ in GPU costs and months of ML engineering
Production Ready
Battle-tested with millions of audio files processed
Pay Per Use
No minimums. Scale from 100 to 1M files seamlessly
Webhooks & Async
Real-time notifications when jobs complete
Integration Architecture
Your users never see StemSplit - it's completely white-label
Set Up Webhooks
For production apps, use webhooks instead of polling. Get notified instantly when jobs complete.
curl -X POST https://stemsplit.io/api/v1/webhooks \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/stemsplit",
"events": ["job.completed", "job.failed"]
}'{
"event": "job.completed",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"jobId": "job_abc123",
"status": "COMPLETED",
"outputs": {
"vocals": { "url": "https://...", "size": 4521984 },
"instrumental": { "url": "https://...", "size": 5832192 }
}
}
}Backend Integration
Here's a complete Node.js/Express example for handling the full flow:
const express = require('express');
const crypto = require('crypto');
const axios = require('axios');
const app = express();
app.use(express.json());
const STEMSPLIT_API_KEY = process.env.STEMSPLIT_API_KEY;
const WEBHOOK_SECRET = process.env.STEMSPLIT_WEBHOOK_SECRET;
// Store job -> user mapping (use your database in production)
const jobUserMap = new Map();
// 1. Your user requests stem separation
app.post('/api/separate', async (req, res) => {
const { userId, audioUrl, outputType } = req.body;
try {
// Create job with StemSplit
const response = await axios.post(
'https://stemsplit.io/api/v1/jobs',
{
sourceUrl: audioUrl,
outputType: outputType || 'ALL_STEMS',
quality: 'BEST',
outputFormat: 'MP3'
},
{ headers: { 'Authorization': `Bearer ${STEMSPLIT_API_KEY}` }}
);
const jobId = response.data.id;
// Map job to user for webhook handling
jobUserMap.set(jobId, userId);
// Return job ID to your frontend
res.json({
jobId,
status: 'processing',
message: 'Your audio is being processed'
});
} catch (error) {
res.status(500).json({ error: 'Failed to start processing' });
}
});
// 2. Handle webhooks from StemSplit
app.post('/webhooks/stemsplit', (req, res) => {
// Verify webhook signature
const signature = req.headers['x-webhook-signature'];
const expectedSig = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
if (signature !== expectedSig) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { event, data } = req.body;
const userId = jobUserMap.get(data.jobId);
if (event === 'job.completed') {
// Notify your user (websocket, email, push notification, etc.)
notifyUser(userId, {
type: 'stem_separation_complete',
outputs: data.outputs
});
// Clean up
jobUserMap.delete(data.jobId);
}
if (event === 'job.failed') {
notifyUser(userId, {
type: 'stem_separation_failed',
error: data.error
});
jobUserMap.delete(data.jobId);
}
res.json({ received: true });
});
// 3. Let users check job status
app.get('/api/jobs/:jobId', async (req, res) => {
const { jobId } = req.params;
const response = await axios.get(
`https://stemsplit.io/api/v1/jobs/${jobId}`,
{ headers: { 'Authorization': `Bearer ${STEMSPLIT_API_KEY}` }}
);
res.json(response.data);
});
function notifyUser(userId, data) {
// Implement your notification logic
// WebSocket, email, push notification, etc.
console.log(`Notifying user ${userId}:`, data);
}
app.listen(3000, () => console.log('Server running on port 3000'));Handle Billing
Track usage and implement your own billing model:
Option 1: Pass-Through Pricing
Charge your users based on audio duration. Our API returns duration in seconds.
Option 2: Subscription Tiers
Include stem separation as part of a subscription plan with usage limits.
- Free: 5 minutes/month
- Pro: 60 minutes/month
- Enterprise: Unlimited
Option 3: Credits System
Sell credit packs. 1 credit = 1 second of audio processed.
// After job completes, update user's usage
async function trackUsage(userId, jobData) {
const durationSeconds = jobData.durationSeconds;
await db.users.update({
where: { id: userId },
data: {
stemMinutesUsed: { increment: durationSeconds / 60 },
lastStemJob: new Date()
}
});
// Check if user exceeded their plan limits
const user = await db.users.findUnique({ where: { id: userId }});
if (user.stemMinutesUsed > user.planLimit) {
await notifyUserOverLimit(userId);
}
}Frontend UX Best Practices
Show Progress
Processing takes 30-90 seconds. Show a progress indicator with estimated time.
Preview Before Download
Let users preview stems in the browser before downloading.
Batch Processing
For power users, support uploading multiple files and processing them in parallel.
Error Handling
If processing fails, show a clear error message and offer to retry. Common errors: file too large, unsupported format.
📊 Volume Pricing
Building something big? Contact us for volume discounts:
| Monthly Volume | Use Case | Action |
|---|---|---|
| < 100 hours | Indie apps, MVPs | Standard pricing → |
| 100-1,000 hours | Growing SaaS | Contact for 20% off → |
| 1,000+ hours | Enterprise, agencies | Custom pricing → |
Building Something Cool?
We love seeing what developers build with our API. Reach out and tell us about your project - we might feature it, provide free credits for testing, or help with technical integration.
Contact Developer Relations