POST /v1/remove-vocals
Take the singing out of your recording — 1 credit
Return an upload's recording with the vocals removed: the same song, as an instrumental, as long as the upload's window (up to 3:00). Returns immediately with processing; the track arrives on your webhook or by polling GET /v1/generations/:id.
Costs 1 credit, refunded automatically if the job fails.
The track is private to your account — it can't be made public, shared or certified.
Request
POST https://pub.finetuning.ai/v1/remove-vocalsHeaders
| Header | Type | Required | Description |
|---|---|---|---|
X-API-Key | string | Yes | Your API key |
Content-Type | string | Yes | application/json |
Body parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
uploadId | string | Yes | — | From POST /v1/songs/uploads, normally with feature: "remove_vocals" |
webhook | string | No | — | HTTPS URL we POST the finished (or failed) track to. Max 2,048 characters. See Webhooks |
attestationVersion | string | Only for a Reimagine upload | — | The current rights statement version ("2026-09-30") |
attestationConfirmed | boolean | Only for a Reimagine upload | — | true — your end user saw the Remove vocals statement and confirmed it |
An upload made for Reimagine was confirmed for its lyrics and composition, not the recording. To strip the vocals from it, confirm the Remove vocals statement in this request; without it you get 400 ATTESTATION_REQUIRED and nothing is charged.
Example request
curl -X POST https://pub.finetuning.ai/v1/remove-vocals \
-H "X-API-Key: ft_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"uploadId": "6b232256-e036-42f5-8cc7-49049cea1da4",
"webhook": "https://your-app.com/finetuning-callback?token=YOUR_SECRET"
}'const res = await fetch('https://pub.finetuning.ai/v1/remove-vocals', {
method: 'POST',
headers: { 'X-API-Key': process.env.FINETUNING_API_KEY!, 'Content-Type': 'application/json' },
body: JSON.stringify({ uploadId: upload.id }),
})
if (res.status === 503) {
// FEATURE_COMING: switched off for now, nothing charged.
}
const { data } = await res.json()Response
{
"data": {
"id": "e270b495-8bd5-46e7-b792-bc6d358b3285",
"status": "processing",
"type": "remove_vocals",
"title": "my-song (No vocals)",
"uploadId": "6b232256-e036-42f5-8cc7-49049cea1da4",
"duration": 180,
"isPublic": false,
"webhook": "https://your-app.com/finetuning-callback?token=YOUR_SECRET",
"creditsCharged": 1,
"creditsRemaining": 2043,
"createdAt": "2026-09-30T13:55:09.877Z"
}
}Errors
| Code | Status | Description |
|---|---|---|
ATTESTATION_REQUIRED | 400 | A Reimagine upload without the Remove vocals confirmation. details has the version and statements |
VALIDATION_ERROR | 400 | uploadId missing; bad webhook |
INSUFFICIENT_CREDITS | 402 | No credits left |
PAID_PLAN_REQUIRED | 403 | Not on a paid plan |
NOT_FOUND | 404 | No upload with that id on your account |
CATALOGUE_MATCH | 409 | The upload was blocked |
CREDITS_CHANGED | 409 | Your balance changed mid-request; retry |
UPLOAD_EXPIRED | 410 | The upload's 24 hours are up |
QUEUE_FULL | 429 | Too many generations in progress |
GENERATION_RATE_LIMITED | 429 | More than 10 Reimagine / Remove vocals requests in a minute |
GENERATION_FAILED | 502 | We couldn't start it; your credit is back |
FEATURE_COMING | 503 | Remove vocals is switched off for now. Nothing was charged. GET /v1/songs/config shows removeVocalsAvailable |