---
title: "Separate the voices of a video | GPT-Video API reference"
description: "Starts splitting the sound of a video into its voices or everything else, and returns the tool run that makes the audio file."
url: "https://www.gpt-video.com/developers/api/separate-audio"
operation: "separate_audio"
method: "POST"
path: "/v1/audio-separations"
scope: "spend"
---

Do not assume endpoints have parameters other than the ones explicitly specified.

# Separate the voices of a video

Operation id `separate_audio`. Request `POST /v1/audio-separations`.

Starts splitting the sound of a video into its voices or everything else, and returns the tool run that makes the audio file.

Beta. This endpoint is supported and can gain new fields. If a change would break your code, we tell you before it takes effect and list it in the changelog.

## About

Call this to get the voices of a video without the music and noise (keep vocals), or the music and noise without the voices (keep instrumental). The result is an MP3 file. The video must have sound and be at most 60 minutes long, and a video whose file was deleted to free up storage cannot be separated: these are checked before anything is used. The call answers 202 at once with the run, queued, and a Location to follow; read it with get_tool_run (`GET /v1/tool-runs/{toolRunId}`) (with ?wait= to hold the request) until it is done, then download the file from its downloadUrl. A separation takes a few minutes. Always send an Idempotency-Key, the same one on every retry: a retry of the same request returns the same run and never uses a second tool run.

## Example use

A creator who wants to reuse the music of a video, without the voices, starts a separation with keep instrumental. It follows the run with get_tool_run (`GET /v1/tool-runs/{toolRunId}`) until the MP3 is ready to download.

## Endpoint

```text
POST https://api.gpt-video.com/v1/audio-separations
```

- Scope `spend`.
- Rate limit 10 requests a minute for each key and 20 for each account, shared by every `spend` call. See [rate limits](/developers/rate-limits.md).
- `Idempotency-Key` is required.
- It starts work that runs on its own. The answer comes back before the work is done.

## Parameters

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | Required | This operation spends, so the key is required. Create it once, before the request, and send the same key on every retry. From 1 to 255 visible characters, such as a UUID. See [idempotency](/developers/pagination-and-idempotency.md). |

### Body

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `sourceAssetId` | string | Required | The video whose sound to separate, as complete_upload (`POST /v1/uploads/{uploadId}/complete`) or list_assets (`GET /v1/assets`) returned it. It starts with ast_. Matches `^ast_n?[0-9a-f]{20}$`. |
| `keep` | string | Required | vocals keeps the voices, instrumental keeps everything else. One of `vocals`, `instrumental`. |

Send only the fields listed here. Any other field is refused with UNKNOWN_PARAMETER.

## Request

Keep the voices of a video.

```bash title="curl"
# Create the key once. Send this same key again if you retry this request.
IDEMPOTENCY_KEY=$(uuidgen)
curl -X POST "https://api.gpt-video.com/v1/audio-separations" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceAssetId": "ast_3f9a1c7e5b2d4086a1c4",
    "keep": "vocals"
  }'
```

```ts title="TypeScript"
// Create the key once. Send this same key again if you retry this request.
const idempotencyKey = crypto.randomUUID();
const res = await fetch("https://api.gpt-video.com/v1/audio-separations", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "sourceAssetId": "ast_3f9a1c7e5b2d4086a1c4",
    "keep": "vocals"
  }),
});
const data = await res.json();
console.log(data);
```

```python title="Python"
import os
import uuid

import requests

# Create the key once. Send this same key again if you retry this request.
idempotency_key = str(uuid.uuid4())
r = requests.post(
    "https://api.gpt-video.com/v1/audio-separations",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
        "Idempotency-Key": idempotency_key,
    },
    json={
        "sourceAssetId": "ast_3f9a1c7e5b2d4086a1c4",
        "keep": "vocals"
    },
)
data = r.json()
print(data)
```

```json title="MCP (Full)"
{
  "method": "tools/call",
  "params": {
    "name": "separate_audio",
    "arguments": {
      "sourceAssetId": "ast_3f9a1c7e5b2d4086a1c4",
      "keep": "vocals",
      "confirm": true,
      "idempotencyKey": "REPLACE-WITH-A-NEW-UUID-FOR-THIS-CALL"
    }
  }
}
```

## Response

A successful call answers HTTP 202. Returns a [Tool run object](/developers/objects.md#tool-run) in `toolRun`.

### Fields to check

| Name | Type | Description |
| --- | --- | --- |
| `toolRun.id` | string | The tool run id. It starts with run_. |
| `toolRun.status` | string | queued and running are still working; done and failed are final. |
| `toolRun.file.downloadUrl` | string or null | A link to download the file, on a read of a finished run; null otherwise. |
| `toolRun.file.downloadUrlExpiresAt` | string or null | An ISO 8601 time in UTC. |
| `toolRun.jobId` | string or null | The job that carries out the run, to follow with get_job (`GET /v1/jobs/{jobId}`); null once it is gone. |
| `paidWith` | string or null | Who paid for this request: plan is the credits or the daily allowance of the account's plan, api_bonus is the API bonus. null when this request paid nothing, because the same work was already running. A retry with the same Idempotency-Key says who paid the first time. |

### Response example

- `Location: https://api.gpt-video.com/v1/tool-runs/run_5e8a1c3f7d2b4096a1e4`

```json title="HTTP 202"
{
  "toolRun": {
    "id": "run_5e8a1c3f7d2b4096a1e4",
    "tool": "audio_separation",
    "status": "queued",
    "progress": 0,
    "errorMessage": null,
    "file": null,
    "ideas": null,
    "jobId": "job_2c7f9e1a4d6b48035f9e",
    "createdAt": "2026-09-01T11:00:00.000Z",
    "finishedAt": null,
    "voiceover": null,
    "separation": {
      "sourceAssetId": "ast_3f9a1c7e5b2d4086a1c4",
      "keep": "vocals"
    },
    "brainstorm": null
  },
  "paidWith": "plan"
}
```

**All response fields**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `toolRun` | Tool run object | Required | Described in [Tool run object](/developers/objects.md#tool-run). |
| `paidWith` | string or null | Required | Who paid for this request: plan is the credits or the daily allowance of the account's plan, api_bonus is the API bonus. null when this request paid nothing, because the same work was already running. A retry with the same Idempotency-Key says who paid the first time. One of `plan`, `api_bonus`. |

**More examples**

#### A video with no sound

`POST /v1/audio-separations`

```json title="Request body"
{
  "sourceAssetId": "ast_3f9a1c7e5b2d4086a1c4",
  "keep": "vocals"
}
```

```json title="HTTP 409"
{
  "error": {
    "code": "CONFLICT",
    "message": "This video has no sound to separate.",
    "requestId": "req_8d1f4b7e0a3c6e9b2d5f8a17"
  }
}
```

#### A video that is too long

`POST /v1/audio-separations`

```json title="Request body"
{
  "sourceAssetId": "ast_3f9a1c7e5b2d4086a1c4",
  "keep": "instrumental"
}
```

```json title="HTTP 409"
{
  "error": {
    "code": "CONFLICT",
    "message": "Audio separation works on videos up to 60 minutes long. Cut the video first, or upload a shorter one.",
    "requestId": "req_1b5e9c3a7f0d2e6b8a4c1f59"
  }
}
```

#### A video that does not exist

`POST /v1/audio-separations`

```json title="Request body"
{
  "sourceAssetId": "ast_0b1e5c9d3a7f40628e2d",
  "keep": "vocals"
}
```

```json title="HTTP 404"
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Asset ast_0b1e5c9d3a7f40628e2d not found",
    "hint": "Check the id. An id that belongs to another account is not found either.",
    "requestId": "req_5c9a3e7b1d4f8a2c6e0b3d71"
  }
}
```

## Errors

| Code | Status | When | What to do |
| --- | --- | --- | --- |
| [`INSUFFICIENT_CREDITS`](/developers/errors.md#INSUFFICIENT_CREDITS) | 402 | See the error reference. | Stop and tell the user. |
| [`PLAN_REQUIRED`](/developers/errors.md#PLAN_REQUIRED) | 402 | See the error reference. | Stop and tell the user. |
| [`NOT_FOUND`](/developers/errors.md#NOT_FOUND) | 404 | A video that does not exist | Change the request, then send it again. |
| [`CONFLICT`](/developers/errors.md#CONFLICT) | 409 | A video with no sound | Read the resource again, then decide. Do not send the same request again. |
| [`DAILY_LIMIT_REACHED`](/developers/errors.md#DAILY_LIMIT_REACHED) | 429 | See the error reference. | Stop until the limit resets at 00:00 UTC. |

Every call can also answer these errors. [`VALIDATION_FAILED`](/developers/errors.md#VALIDATION_FAILED), [`UNKNOWN_PARAMETER`](/developers/errors.md#UNKNOWN_PARAMETER), [`IDEMPOTENCY_KEY_REQUIRED`](/developers/errors.md#IDEMPOTENCY_KEY_REQUIRED), [`UNAUTHORIZED`](/developers/errors.md#UNAUTHORIZED), [`SCOPE_MISSING`](/developers/errors.md#SCOPE_MISSING), [`IDEMPOTENCY_IN_PROGRESS`](/developers/errors.md#IDEMPOTENCY_IN_PROGRESS), [`PAYLOAD_TOO_LARGE`](/developers/errors.md#PAYLOAD_TOO_LARGE), [`IDEMPOTENCY_KEY_REUSED`](/developers/errors.md#IDEMPOTENCY_KEY_REUSED), [`RATE_LIMITED`](/developers/errors.md#RATE_LIMITED), [`INTERNAL`](/developers/errors.md#INTERNAL), [`UNAVAILABLE`](/developers/errors.md#UNAVAILABLE).

## Cost

Uses one tool run of the plan's daily allowance, which resets at 00:00 UTC. When the API bonus pays, it also uses one bonus credit. Nothing is returned if the run fails.

- Paid by the API bonus, it uses 1 bonus credit.
- When the API bonus pays, an account can run 10 tools a day.
- See [API pricing](/developers/pricing.md) for who pays first.

## Related operations

- [`get_tool_run`](/developers/api/get-tool-run.md) (`GET /v1/tool-runs/{toolRunId}`) Get a tool run
- [`list_tool_runs`](/developers/api/list-tool-runs.md) (`GET /v1/tool-runs`) List the tool runs
- [`list_assets`](/developers/api/list-assets.md) (`GET /v1/assets`) List your videos
- [`get_asset`](/developers/api/get-asset.md) (`GET /v1/assets/{assetId}`) Get an asset

## Questions

### What happens if my request times out and I send it again?

Send it again with the same Idempotency-Key. You get the same run back and no second tool run is used from the daily allowance. A new key starts a new run.

### Is anything used when the video cannot be separated?

No. A video with no sound, longer than 60 minutes or whose file was deleted answers CONFLICT before the run starts, and no tool run is used.
