# Voice isolation

> Separate clean speech from background noise. Upload once, track the job as it runs, and get back a URL to the denoised audio — ready for transcription, voice cloning, or anything downstream.

## What it does

Voice isolation is AI-powered noise removal that preserves natural speech characteristics. It accepts both audio and video files — video uploads have their audio track extracted automatically. Typical jobs:

| Use case | Why isolate first |
| --- | --- |
| **Pre-processing for transcription** | Clean audio before sending it to [speech-to-text](/speech-to-text/transcribe) for better accuracy. |
| **Voice cloning preparation** | Isolate clean speech for better [voice cloning](/text-to-speech/voice-cloning) results. |
| **Podcast production** | Remove background noise from podcast recordings. |
| **Call quality enhancement** | Improve audio quality in telephony applications. |

## How it works

Voice isolation is an asynchronous, job-based pipeline in three moves:

| Step | Call | What happens |
| --- | --- | --- |
| **1 · Submit the file** | POST `/denoise` | Send a `multipart/form-data` body with the `audio` field. The response returns a `jobId` and a `denoiseId`. |
| **2 · Track progress** | GET `/denoise/{denoiseId}/progress` | Open an SSE connection to receive `processing`, `done`, and `error` events in real time. See [Job status](/audio/denoise-progress). |
| **3 · Retrieve the result** | GET `/denoise` | The `done` event carries the `url` of the denoised audio. You can also list past jobs and their final URLs. |

## Limits & formats

One upload field, hard caps on size and duration.

| Limit | Value |
| --- | --- |
| **Maximum file size** | 200 MB |
| **Maximum duration** | 15 minutes |
| **Audio formats** | `WAV`, `MP3`, `M4A`, `FLAC`, `OGG` |
| **Video containers** | `mp4`, `mov`, `mkv`, `webm`, `avi`, `m4v` — treated as video; the audio track is extracted before denoising. All other extensions are treated as audio. |

## Submit a denoise job

Submit denoise jobPOST/denoise

Queues an audio (or video) file for voice isolation. The endpoint accepts the upload, persists the original, creates a pending record, and enqueues a background job. Authenticate with your API key in the `x-api-key` header. Content type is `multipart/form-data`.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `audio` | File | **Yes** | Audio or video file to denoise (max 200 MB, max 15 minutes duration). |

```bash
curl -X POST "https://api.munsit.com/api/v1/denoise" \
  -H "x-api-key: YOUR_API_KEY" \
  -F "audio=@input_audio.wav"
```

```
import requests

url = "https://api.munsit.com/api/v1/denoise"
headers = {
    "x-api-key": "YOUR_API_KEY"
}

with open("input_audio.wav", "rb") as audio_file:
    files = {"audio": audio_file}
    response = requests.post(url, files=files, headers=headers)

if response.status_code == 200:
    data = response.json()
    print(f"Queued denoising: denoiseId={data['denoiseId']}")
else:
    print(f"Error: {response.status_code} - {response.text}")
```

```javascript
import fs from "node:fs";

const formData = new FormData();
const audioFile = new Blob([fs.readFileSync("input_audio.wav")]);
formData.append("audio", audioFile);

const response = await fetch('https://api.munsit.com/api/v1/denoise', {
  method: 'POST',
  headers: {
    'x-api-key': 'YOUR_API_KEY',
    // Note: Content-Type is set automatically by FormData
  },
  body: formData,
});

if (response.ok) {
  const { jobId, denoiseId } = await response.json();
  console.log('Queued denoising', { jobId, denoiseId });
  // Next: subscribe to /denoise/{denoiseId}/progress for live updates
} else {
  const error = await response.json();
  console.error('Error:', error);
}
```

```go
package main

import (
    "bytes"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "os"
)

func main() {
    file, _ := os.Open("input_audio.wav")
    defer file.Close()

    var buf bytes.Buffer
    writer := multipart.NewWriter(&buf)
    part, _ := writer.CreateFormFile("audio", "input_audio.wav")
    io.Copy(part, file)
    writer.Close()

    req, _ := http.NewRequest("POST", "https://api.munsit.com/api/v1/denoise", &buf)
    req.Header.Set("x-api-key", os.Getenv("MUNSIT_API_KEY"))
    req.Header.Set("Content-Type", writer.FormDataContentType())

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Printf("Queued denoising: %s\n", body)
}
```

Response · 200 OK

```
{
  "jobId": "5b1d2f7e-3f81-4c2a-9c5d-7a2e91b4c7a1",
  "denoiseId": "5b1d2f7e-3f81-4c2a-9c5d-7a2e91b4c7a1"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `jobId` | string | Identifier of the queued background job. Mirrors `denoiseId`. |
| `denoiseId` | string (UUID) | Unique identifier of the denoising record. Use this with the progress and list endpoints. |

> Async workflow. This endpoint only enqueues the job. The denoised audio URL is delivered via the progress SSE endpoint (done event) or can be fetched from the list endpoint below once the record's status is success.

## List denoise history

List denoise historyGET/denoise

Returns the authenticated user's denoising records ordered by most recently created first. Use this to retrieve the final audio URL of completed jobs, monitor pending jobs, or paginate through historical results.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `offset` | integer (≥ 0) | No | Number of records to skip from the start of the result set. |
| `limit` | integer (≥ 1) | No | Maximum number of records to return. Omit to return all records. |

```bash
curl -X GET "https://api.munsit.com/api/v1/denoise?offset=0&limit=20" \
  -H "x-api-key: YOUR_API_KEY"
```

```
import requests

url = "https://api.munsit.com/api/v1/denoise"
headers = {"x-api-key": "YOUR_API_KEY"}
params = {"offset": 0, "limit": 20}

response = requests.get(url, headers=headers, params=params)
for record in response.json():
    print(record["id"], record["status"], record["audio_url"])
```

```javascript
const response = await fetch('https://api.munsit.com/api/v1/denoise?offset=0&limit=20', {
  headers: {
    'x-api-key': 'YOUR_API_KEY',
  },
});

const records = await response.json();
records.forEach((r) => {
  console.log(r.id, r.status, r.audio_url);
});
```

```go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
)

func main() {
    req, _ := http.NewRequest("GET", "https://api.munsit.com/api/v1/denoise?offset=0&limit=20", nil)
    req.Header.Set("x-api-key", os.Getenv("MUNSIT_API_KEY"))

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    var records []map[string]any
    json.NewDecoder(resp.Body).Decode(&records)
    for _, r := range records {
        fmt.Println(r["id"], r["status"], r["audio_url"])
    }
}
```

Response · 200 OK

```
[
  {
    "id": "5b1d2f7e-3f81-4c2a-9c5d-7a2e91b4c7a1",
    "user_id": "user_123",
    "transaction_id": "txn_abc",
    "audio_url": "https://cdn.munsit.com/denoising/user_123/5b1d2f7e_denoised.wav",
    "original_audio_url": "https://cdn.munsit.com/denoising/user_123/5b1d2f7e_original.wav",
    "file_name": "meeting.wav",
    "audio_duration": 312.4,
    "audio_size": 10485760,
    "audio_format": "wav",
    "source_type": "audio",
    "status": "success",
    "error": null,
    "created_at": "2026-05-12T10:14:02.000Z",
    "updated_at": "2026-05-12T10:14:55.000Z"
  }
]
```

| Field | Type | Description |
| --- | --- | --- |
| `id` | string (UUID) | Denoising record id (same value as `denoiseId` returned by `POST /denoise`). |
| `user_id` | string | Owner of the record. |
| `transaction_id` | string | null | Wallet transaction id associated with billing for this job, if any. |
| `audio_url` | string | null | URL of the denoised audio. `null` until the job reaches `success`. |
| `original_audio_url` | string | URL of the originally uploaded file. |
| `file_name` | string | Original file name as submitted by the client. |
| `audio_duration` | number | Duration of the source audio in seconds. |
| `audio_size` | number | Size of the source file in bytes. |
| `audio_format` | string | Lower-cased file extension of the source file (e.g. `wav`, `mp4`). |
| `source_type` | `"audio"` | `"video"` | Whether the upload was an audio file or a video container. |
| `status` | `"pending"` | `"success"` | `"failed"` | Current job status. |
| `error` | string | null | Error message if `status` is `failed`. |
| `created_at` | string (ISO 8601) | Creation timestamp. |
| `updated_at` | string (ISO 8601) | Last update timestamp. |

## Errors

Errors come back as JSON with an `errorCode` and a human-readable `errorMessage`.

| Status | Code | Message |
| --- | --- | --- |
| **400** Bad Request | `400xx` | `audio is required and must be a file` |
| **400** Bad Request | `400xx` | `File size exceeds the maximum limit of 200MB. File size: <n>MB` |
| **400** Bad Request | `400xx` | `Duration exceeds the maximum limit of 15 minutes. Duration: <n> minutes` |
| **400** Bad Request | `400xx` | `offset must be a non-negative number` · `limit must be a positive number` (list endpoint) |
| **401** Unauthorized | `40101` | `Invalid or missing API key` |
| **402** Payment Required | `40201` | `Insufficient wallet balance` |

## Next steps

The job is queued — now watch it finish, then put the clean audio to work.

- [Job status](/audio/denoise-progress) — Stream live progress events over SSE until the denoised URL arrives. — `GET /denoise/{denoiseId}/progress`

- [Transcribe](/speech-to-text/transcribe) — Send the clean audio to the most accurate Arabic ASR. — `POST /speech-to-text`

- [Voice cloning](/text-to-speech/voice-cloning) — Denoised samples make noticeably better clones. — `POST /voices/clone`

- [Errors](/errors) — The full error code catalogue across the API. — `guide`
