# Voice cloning

> Create custom Arabic voices from audio samples. Preview a voice first, then commit the clone — the returned voice_id works everywhere a stock voice does.

## What voice cloning gives you

Voice cloning lets you create lifelike custom voices from short audio samples, clone voices across different Arabic dialects, use them in text-to-speech generation, and manage your own voice library — all with the same API key.

| Capability | What it means |
| --- | --- |
| **High-quality cloning** | Create lifelike voice clones from short audio samples. |
| **Multi-dialect support** | Clone voices across different Arabic dialects. |
| **Voice library** | Manage and organize your custom voices alongside stock ones. |
| **Easy integration** | Use cloned voices seamlessly with the TTS API — just pass the `voice_id`. |

## The preview-first flow

Cloning is a two-step process. You first generate a preview with the [Voice Preview API](/text-to-speech/voice-preview), listen to it, and only then commit the clone. The clone request needs **both** files: the preview audio and the original sample it was built from.

| Step | What happens |
| --- | --- |
| **1 · Upload a sample** | Provide a high-quality audio sample of the voice you want to clone. |
| **2 · Preview** | Munsit processes the sample and returns a generated preview audio file. |
| **3 · Clone** | Send the preview file, the original file and the preview text to `/voices/clone`. A unique `voice_id` is assigned automatically. |
| **4 · Use in TTS** | Pass the `voice_id` to any [text-to-speech](/text-to-speech/synthesize) request. The voice is available immediately after creation. |

> Sample quality matters. Use high-quality audio (minimum 1 minute recommended) with clear, natural speech, recorded in a quiet environment with minimal background noise. Multiple samples produce better voice quality.

## Endpoint

`POST /api/v1/voices/clone`

Authenticated with the `x-api-key` header. The body is `multipart/form-data`; the content type is set automatically when you use FormData or file uploads.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `voice_file` | File | **Yes** | The generated preview audio file from the preview API. |
| `reference_audio_file` | File | **Yes** | The original audio file used for the preview. |
| `text` | string | **Yes** | The text used in preview generation — must match what you sent to the preview API. |
| `stability` | number | **Yes** | Voice stability (0.0 to 1.0). Higher values produce more consistent output. |
| `name` | string | **Yes** | Name for the cloned voice. |
| `model` | string | **Yes** | Model identifier to use for voice cloning. |
| `description` | string | No | Description of the voice. |
| `gender` | string | No | Gender of the voice (e.g., `male`, `female`). |
| `age` | string | No | Age category of the voice (e.g., `middle`, `elderly`). |
| `languages` | string | No | Comma-separated list of language codes (e.g., `ar,en`). |
| `dialects` | string | No | Comma-separated list of dialects (e.g., `najdi,hijazi`). |
| `avatar_url` | string | No | URL to an avatar image for the voice. |

## Clone a voice

Same request in three languages. `voice_file` is the preview output; `reference_audio_file` is the original recording; `text` matches the preview text.

```bash
curl -X POST "https://api.munsit.com/api/v1/voices/clone" \
  -H "x-api-key: YOUR_API_KEY" \
  -F "voice_file=@voice_sample.wav" \
  -F "reference_audio_file=@reference_audio.wav" \
  -F "text=مرحبا بك في فصيح، هذا صوتي المستنسخ" \
  -F "stability=0.8" \
  -F "name=My Cloned Voice" \
  -F "model=faseeh-v1-preview" \
  -F "description=A custom cloned voice" \
  -F "gender=male" \
  -F "languages=ar,en" \
  -F "dialects=najdi"
```

```python
import requests

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

files = {
    "voice_file": open("voice_sample.wav", "rb"),
    "reference_audio_file": open("reference_audio.wav", "rb")
}

data = {
    "text": "مرحبا بك في فصيح، هذا صوتي المستنسخ",
    "stability": "0.8",
    "name": "My Cloned Voice",
    "model": "faseeh-v1-preview",
    "description": "A custom cloned voice",
    "gender": "male",
    "languages": "ar,en",
    "dialects": "najdi"
}

response = requests.post(url, files=files, data=data, headers=headers)

if response.status_code == 200:
    voice = response.json()
    print(f"Voice created: {voice['voice_id']}")
else:
    print(f"Error: {response.status_code} - {response.text}")
```

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

const formData = new FormData();
const voiceFile = new Blob([fs.readFileSync("voice_sample.wav")]);
const referenceAudioFile = new Blob([fs.readFileSync("reference_audio.wav")]);

formData.append("voice_file", voiceFile, "voice_sample.wav");
formData.append("reference_audio_file", referenceAudioFile, "voice_sample.wav");
formData.append("text", "مرحبا بك في فصيح، هذا صوتي المستنسخ");
formData.append("stability", "0.8");
formData.append("name", "My Cloned Voice");
formData.append("model", "faseeh-v1-preview");
formData.append("description", "A custom cloned voice");
formData.append("gender", "male");
formData.append("languages", "ar,en");
formData.append("dialects", "najdi");

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

if (response.ok) {
  const voice = await response.json();
  console.log('Voice created:', voice.voice_id);
} else {
  const error = await response.json();
  console.error('Error:', error);
}
```

```go
package main

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

func main() {
    url := "https://api.munsit.com/api/v1/voices/clone"

    var body bytes.Buffer
    writer := multipart.NewWriter(&body)

    for field, path := range map[string]string{
        "voice_file":           "voice_sample.wav",
        "reference_audio_file": "reference_audio.wav",
    } {
        file, _ := os.Open(path)
        part, _ := writer.CreateFormFile(field, path)
        io.Copy(part, file)
        file.Close()
    }

    fields := map[string]string{
        "text":        "مرحبا بك في فصيح، هذا صوتي المستنسخ",
        "stability":   "0.8",
        "name":        "My Cloned Voice",
        "model":       "faseeh-v1-preview",
        "description": "A custom cloned voice",
        "gender":      "male",
        "languages":   "ar,en",
        "dialects":    "najdi",
    }
    for k, v := range fields {
        writer.WriteField(k, v)
    }
    writer.Close()

    req, _ := http.NewRequest("POST", url, &body)
    req.Header.Set("x-api-key", os.Getenv("MUNSIT_API_KEY"))
    req.Header.Set("Content-Type", writer.FormDataContentType())

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    var voice map[string]any
    json.NewDecoder(resp.Body).Decode(&voice)
    fmt.Println("Voice created:", voice["voice_id"])
}
```

## Response

**200 OK**, `application/json`. The clone is available immediately — use `voice_id` in any TTS request.

```json
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "voice_id": "ar-cloned-voice-1",
  "name": "My Cloned Voice",
  "description": "A custom cloned voice",
  "gender": "male",
  "age": "middle",
  "languages": ["ar", "en"],
  "dialect": ["najdi"],
  "type": "neural",
  "sample_url": "https://example.com/voices/user123/ar-cloned-voice-1.wav",
  "avatar_url": null,
  "stability": 0.8
}
```

| Field | Type | Description |
| --- | --- | --- |
| `id` | string (UUID) | Unique identifier for the voice record. |
| `voice_id` | string | Voice identifier used in API calls. |
| `name` | string | Name of the cloned voice. |
| `description` | string | null | Description of the voice. |
| `gender` | string | null | Gender of the voice. |
| `age` | string | null | Age category of the voice. |
| `languages` | string\[\] | List of language codes supported by the voice. |
| `dialect` | string\[\] | List of dialects supported by the voice. |
| `type` | string | null | Voice type. |
| `sample_url` | string | URL to the sample audio file. |
| `avatar_url` | string | null | URL to the avatar image. |
| `stability` | number | Voice stability value. |

## Errors

Validation failures come back as **400** with a specific `errorMessage`; auth and processing failures use the shared error shape.

| Status | errorCode | errorMessage |
| --- | --- | --- |
| **400** | `400xx` | `voice_file is required and must be a file` |
| **400** | `400xx` | `reference_audio_file is required and must be a file` |
| **400** | `400xx` | `name is required` · `text is required` · `model is required` |
| **400** | `400xx` | `stability must be a number between 0 and 1` |
| **401** | `40101` | `Invalid or missing API key` |
| **500** | `50001` | `Failed to process voice file` · `Failed to upload voice file` |

## Go further

The clone is just a `voice_id`. Everything downstream is regular TTS.

- [Preview a voice](/text-to-speech/voice-preview) — Generate the preview file this endpoint requires — step one of cloning. — `POST /voices/preview`

- [Synthesize speech](/text-to-speech/synthesize) — Use your new voice_id in a text-to-speech request. — `POST /text-to-speech/{model_id}`

- [List voices](/text-to-speech/voices) — See stock voices and your cloned ones in a single library. — `GET /voices`

- [Stream audio out](/text-to-speech/audio-streaming-output) — Cloned voices work over the streaming WebSocket too. — `WSS /websocket/text-to-speech`
