# Synthesize

> Generate speech from Arabic text and receive a complete WAV audio file. The entire audio is generated before being returned, ensuring complete audio quality.

## Endpoint

`POST /api/v1/text-to-speech/{model_id}`

Requires API key authentication via the `x-api-key` header. Get a `model_id` from [Models](/text-to-speech/models) and a `voice_id` from [Voices](/text-to-speech/voices).

## Request

**Path parameters**

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `model_id` | string | **Yes** | The model identifier to use for generation |

**Request body** — `Content-Type: application/json`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `voice_id` | string | **Yes** | The voice ID to use for synthesis |
| `text` | string | **Yes** | The Arabic text to convert to speech |
| `stability` | number | **Yes** | Voice stability (0.0 to 1.0). Higher values produce more consistent output |
| `speed` | number | No | Speech speed (0.7 to 1.2, default 1.0). Values below 1.0 slow down speech, above 1.0 speed it up |
| `streaming` | boolean | No | Omit or set `false` (default) for a complete WAV file response. Set `true` to receive PCM16 chunks as they're generated — see [Audio streaming output](/text-to-speech/audio-streaming-output). |
| `sample_rate` | number | No | Output sample rate in Hz, `8000`–`48000` (default `24000`). **Use `48000` — it's the engine's native rate**, so the audio skips downsampling and needs no client-side resampling for WebRTC. |
| `dialect` | string | No | Dialect hint for synthesis: `auto` (default), `emirati`, or `fusha`. |

## Pauses

Insert a silence of a fixed length anywhere in `text` with a break tag. It works on every text-to-speech endpoint, including [streaming](/text-to-speech/audio-streaming-output) and [word timestamps](/text-to-speech/word-timestamps).

```json
"text": "مرحبا بكم <break time=\"3s\"/> في فصيح"
"text": "أهلا <break time=\"500ms\"/> وسهلا"
```

| Property | Value |
| --- | --- |
| **Syntax** | `<break time="<duration>"/>` |
| **Units** | **Required.** Seconds (`3s`, `1.5s`) or milliseconds (`500ms`) |
| **Max per tag** | `3s` — longer values are clamped down to 3 s |
| **Max per request** | **20** break tags, and about **30 s** of pause in total |
| **Placement** | Anywhere in the text, repeated as needed, within the limits above |

> The unit is mandatory, and a tag without one is silently ignored. <break time="3"/> produces no pause, no error, and is not spoken — the request succeeds and the tag simply disappears. Always write 3s or 500ms.

Every one of these limits degrades quietly rather than returning an error. A `10s` tag yields roughly 3 s of silence; past 20 tags or about 30 s of accumulated pause, further breaks add little or nothing. Nothing in the response tells you a limit was hit, so treat the numbers above as a budget you stay inside rather than something the API will enforce for you.

The pause is rendered by the model as part of the audio, so it lands inside the returned waveform and counts toward the clip's duration.

> Multi-speaker. Break tags are the way to space out speaker turns — put one at the end of a segment's text. There are no separate pause fields on the speakers array.

## Example request

Leave `streaming` unset (or `false`) and save the response body as a WAV file. For chunked output, see [Audio streaming output](/text-to-speech/audio-streaming-output).

```bash
curl -X POST "https://api.munsit.com/api/v1/text-to-speech/faseeh-v1-preview" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "voice_id": "ar-najdi-male-2",
    "text": "مرحبا بك في فصيح كيف يمكنني مساعدتك اليوم",
    "stability": 0.5,
    "speed": 1.0,
    "streaming": false
  }' \
  --output output.wav
```

```python
import requests

url = "https://api.munsit.com/api/v1/text-to-speech/faseeh-v1-preview"
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "voice_id": "ar-najdi-male-2",
    "text": "مرحبا بك في فصيح كيف يمكنني مساعدتك اليوم",
    "stability": 0.5,
    "speed": 1.0,
    "streaming": False
}

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

if response.status_code == 200:
    with open("output.wav", "wb") as f:
        f.write(response.content)
else:
    print(f"Error: {response.status_code} - {response.text}")
```

```javascript
const response = await fetch('https://api.munsit.com/api/v1/text-to-speech/faseeh-v1-preview', {
  method: 'POST',
  headers: {
    'x-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    voice_id: 'ar-najdi-male-2',
    text: 'مرحبا بك في فصيح كيف يمكنني مساعدتك اليوم',
    stability: 0.5,
    speed: 1.0,
    streaming: false,
  }),
});

const audioBlob = await response.blob();
const audioUrl = URL.createObjectURL(audioBlob);
// Use audioUrl to play or download the audio
```

```go
package main

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

func main() {
    url := "https://api.munsit.com/api/v1/text-to-speech/faseeh-v1-preview"
    payload, _ := json.Marshal(map[string]any{
        "voice_id":  "ar-najdi-male-2",
        "text":      "مرحبا بك في فصيح كيف يمكنني مساعدتك اليوم",
        "stability": 0.5,
        "speed":     1.0,
        "streaming": false,
    })

    req, _ := http.NewRequest("POST", url, bytes.NewReader(payload))
    req.Header.Set("x-api-key", os.Getenv("MUNSIT_API_KEY"))
    req.Header.Set("Content-Type", "application/json")

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

    out, _ := os.Create("output.wav")
    defer out.Close()
    io.Copy(out, resp.Body)
}
```

## Response

**Status code:** `200 OK`. The body is a complete WAV audio file.

| Header | Value |
| --- | --- |
| `Content-Type` | `audio/wav` |
| `Cache-Control` | `no-cache` |
| `Content-Length` | `<file_size>` |

## Error responses

Errors come back as JSON with an `errorCode` and `errorMessage`.

| Status | Error code | Example message |
| --- | --- | --- |
| **400** Bad Request | `400xx` | `Model not found: invalid_model_id` |
| **402** Payment Required | `402xx` | `Insufficient wallet balance. Required: $0.05, Available: $0.02` |

## Cost calculation

The cost is calculated from the **text length** (number of characters) and the **model cost per character**. Cost is deducted from your wallet balance upon successful generation.

> Wallet balance. Ensure your wallet has sufficient balance before making requests. Check your balance in the Munsit dashboard.

## Go further

- [Audio streaming output](/text-to-speech/audio-streaming-output) — Same endpoint with streaming: true — PCM16 chunks as they're generated. — `POST /text-to-speech/{model_id}`

- [Voices](/text-to-speech/voices) — Pick a voice_id from the voice library. — `GET /voices`

- [Models](/text-to-speech/models) — Pick a model_id — quality vs latency. — `GET /models`

- [Voice cloning](/text-to-speech/voice-cloning) — Create a custom voice from your own audio. — `POST /voices/clone`
