# Generate speech

> POST https://api.nabrah.ai/api/ext/tts/generations

Group: Text to speech. Page: https://www.nabrah.ai/en/docs/api-reference/text-to-speech/generate-speech

Generate speech from text using the OpenAI-compatible TTS endpoint at POST /api/ext/tts/generations. Put the text in `input` and the target voice id in `voice` (see “Choosing the voice”). `response_format` picks the encoding — `mp3`, `wav` or `pcm` — and `stream` picks the delivery: false returns one complete file, true streams the audio as it is produced. Every format works in both modes. Switch the scenario tab in “Try it” to load a matching body.

## Formats

All three encodings are available whether or not you stream. `model` is always `nabrah_tts_v1`.

- **mp3 — `audio/mpeg`**: Compressed audio at 192 kbps. The smallest payload and the easiest to play in a browser. This is the default when `stream` is false or omitted. Fields: `response_format`.
- **wav — `audio/wav`**: Uncompressed 16-bit PCM in a RIFF container, 48 kHz mono. Lossless and self-describing — the header carries the sample rate and channel count. This is the default when `stream` is true. Fields: `response_format`.
- **pcm — `audio/L16; rate=48000; channels=1`**: Raw signed 16-bit little-endian mono samples at 48 kHz, with no container and no header at all. Use it when you are feeding an audio pipeline that wants frames directly; you must already know the sample rate, since nothing in the payload states it. Fields: `response_format`.

Omit `response_format` and you get `mp3` for a buffered request or `wav` for a streamed one.

## Delivery modes

`stream` controls when the bytes reach you, not what they contain.

- **stream false (default) — complete file**: The response arrives once synthesis has finished. A `wav` response carries correct length fields, so it can be written straight to disk. The same rules apply to POST /api/ext/tts/quick_generate. Fields: `stream`, `response_format`, `use_cache`.
- **stream true — progressive**: Bytes are forwarded as the engine produces them, so playback can start before synthesis finishes. `speed` is forced to 1.0 while streaming, and `apply_enhancements` must be `none`. Fields: `stream`, `response_format`.

Generated audio is cached for a short period. A cache hit is marked with the `X-Cache: HIT` response header; send `use_cache: false` to force a fresh generation.

## Reading a streamed response

Set `stream: true` and send a matching `Accept` header — `audio/wav`, `audio/mpeg` or `audio/L16`. Do not await the whole payload; read the response body as a stream while the connection stays open.

- **Read chunks as they arrive**: In a browser read them with `response.body.getReader()`; on a server pipe the response stream straight to a file or audio sink. The first bytes typically arrive within a few hundred milliseconds. Fields: `model`, `input`, `voice`, `stream`, `response_format`.
- **The streamed WAV header**: A streamed clip cannot know its own length up front, so the first 44 bytes are a WAV header whose two size fields are set to the conventional `0xFFFFFFFF` unknown-length marker. Players read until the connection closes. If you write the stream to a file and need exact sizes, rewrite bytes 4–7 and 40–43 once you know the total, or request `stream: false` instead. `mp3` and `pcm` have no such header and stream through untouched.
- **Completion & download**: When the stream ends you hold the full clip; keep the accumulated bytes to offer a download. For a file with correct length fields, request the same `response_format` with `stream: false`.

WAV is not appendable to a MediaSource SourceBuffer, so browsers generally buffer the streamed chunks before playback; `mp3` is the friendlier choice for progressive playback in a browser.

## Choosing the voice (the `voice` field)

The `voice` field in the request body is a voice ID, not a name. Get a valid ID before calling this endpoint.

- **List available voices**: Call GET /api/ext/tts/voices. It returns a `voices` array containing the public platform voices plus your project’s cloned voices. Copy the `id` of the voice you want and pass it as `voice` here. Fields: `voice`.
- **Voice must be ready**: The chosen voice must have `is_ready: true`. A freshly cloned voice stays `is_ready: false` until its background job finishes; passing a not-ready voice will fail.
- **Use your own cloned voice**: To use a custom voice, create one with POST /api/ext/tts/clone_voice, then poll GET /api/ext/tts/voices (or GET /api/ext/tts/voices/{voice_id}) until it is ready, and use the returned id here.

These are the same voice IDs used to configure an agent’s voice.

## Authorization

- `X-API-Key` (string, header, required): your workspace API key.

## Body (application/json)

- `input` (string, required): The text to synthesize. 1–4096 characters after normalization. Text that normalizes to nothing is rejected with 422.
- `voice` (string (uuid), required): The voice ID to speak with — an `id` from GET /api/ext/tts/voices. Must belong to your project (or be a public voice) and have `is_ready: true`.
- `model` ("nabrah_tts_v1", optional): The synthesis model. `nabrah_tts_v1` is the only accepted value. Defaults to `nabrah_tts_v1`; any other value is rejected with 422.
- `response_format` ("mp3" | "wav" | "pcm", optional): Audio encoding. All three work with or without `stream`. Defaults to `mp3` when `stream` is false and `wav` when it is true. `mp3` returns `audio/mpeg`; `wav` returns `audio/wav` (16-bit PCM in a RIFF container, 48 kHz mono); `pcm` returns `audio/L16; rate=48000; channels=1` — raw signed 16-bit little-endian mono frames with no container.
- `stream` (boolean, optional): Deliver the audio progressively instead of as one finished file. Defaults to `false`. When `true`, bytes are forwarded as the engine produces them, `speed` is forced to 1.0, `apply_enhancements` must be `none`, and a streamed `wav` carries an unknown-length header (both size fields set to `0xFFFFFFFF`).
- `use_cache` (boolean, optional): Reuse a recently generated clip for the same text, voice and format. Defaults to `true`. A cache hit is marked with the `X-Cache: HIT` response header; set to `false` to force a fresh generation.
- `apply_enhancements` ("none" | "simple" | "ultra", optional): Post-processing enhancement level. Defaults to `none`. Must be `none` when `stream` is true, otherwise the request is rejected with 422. Accepted for compatibility on buffered requests but not currently applied to the generated audio.
- `speed` (number, optional): Playback speed multiplier. Accepted between 0.01 and 4.0 and forced to 1.0 while streaming. Not currently applied to the generated audio.
- `expressiveness` (number, optional): How much inflection the voice uses. Accepted between 0.1 and 1.0. Not currently applied to the generated audio.
- `speech_smoothness` (number, optional): Blend duration between synthesized segments. Accepted between 0.1 and 1.0. Not currently applied to the generated audio.
- `remove_silence` (boolean, optional): Trim long pauses from the start and end of the clip. Defaults to `false`. Not currently applied to the generated audio.

### Example: File — MP3 (default)

One finished MP3. This is what you get when `stream` and `response_format` are both omitted.

```json
{
  "model": "nabrah_tts_v1",
  "input": "Hello from Nabrah!",
  "voice": "00000000-0000-4000-8000-000000000000",
  "response_format": "mp3",
  "stream": false,
  "use_cache": true
}
```

### Example: File — WAV

One finished WAV, 48 kHz mono 16-bit, with correct length fields. Lossless and larger than MP3.

```json
{
  "model": "nabrah_tts_v1",
  "input": "Hello from Nabrah!",
  "voice": "00000000-0000-4000-8000-000000000000",
  "response_format": "wav"
}
```

### Example: File — raw PCM

The complete clip as raw signed 16-bit little-endian mono samples at 48 kHz, with no container. Returned as `audio/L16; rate=48000; channels=1`.

```json
{
  "model": "nabrah_tts_v1",
  "input": "Hello from Nabrah!",
  "voice": "00000000-0000-4000-8000-000000000000",
  "response_format": "pcm"
}
```

### Example: Streaming — WAV (default)

Progressive `audio/wav`. This is what streaming gives you when `response_format` is omitted. The header carries the unknown-length marker; `speed` is ignored.

```json
{
  "model": "nabrah_tts_v1",
  "input": "Hello from Nabrah!",
  "voice": "00000000-0000-4000-8000-000000000000",
  "response_format": "wav",
  "stream": true
}
```

### Example: Streaming — MP3

Progressive `audio/mpeg`. The friendliest option for browser playback, since MP3 frames can be appended to a MediaSource buffer as they arrive.

```json
{
  "model": "nabrah_tts_v1",
  "input": "Hello from Nabrah!",
  "voice": "00000000-0000-4000-8000-000000000000",
  "response_format": "mp3",
  "stream": true
}
```

### Example: Streaming — raw PCM

Progressive raw 16-bit little-endian mono frames at 48 kHz, no container and no header. Best for feeding a live audio pipeline.

```json
{
  "model": "nabrah_tts_v1",
  "input": "Hello from Nabrah!",
  "voice": "00000000-0000-4000-8000-000000000000",
  "response_format": "pcm",
  "stream": true
}
```

## Response

### 200 Successful Response

### 422 Validation Error

Content type: `application/json`

```json
{
  "detail": [
    {
      "loc": [
        "string"
      ],
      "msg": "string",
      "type": "string"
    }
  ]
}
```

## Request examples

### cURL

```bash
curl --request POST \
  --url 'https://api.nabrah.ai/api/ext/tts/generations' \
  --header 'X-API-Key: <your-api-key>' \
  --header 'Content-Type: application/json' \
  --data '{
  "model": "nabrah_tts_v1",
  "input": "Hello from Nabrah!",
  "voice": "00000000-0000-4000-8000-000000000000",
  "response_format": "mp3",
  "stream": false,
  "use_cache": true
}'
```

### Python

```python
import requests

url = "https://api.nabrah.ai/api/ext/tts/generations"
headers = {
    "X-API-Key": "<your-api-key>",
}
payload = {
    "model": "nabrah_tts_v1",
    "input": "Hello from Nabrah!",
    "voice": "00000000-0000-4000-8000-000000000000",
    "response_format": "mp3",
    "stream": False,
    "use_cache": True
}

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()
print(response.json())
```

### JavaScript

```javascript
const response = await fetch("https://api.nabrah.ai/api/ext/tts/generations", {
  method: "POST",
  headers: {
    "X-API-Key": "<your-api-key>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "model": "nabrah_tts_v1",
    "input": "Hello from Nabrah!",
    "voice": "00000000-0000-4000-8000-000000000000",
    "response_format": "mp3",
    "stream": false,
    "use_cache": true
  }),
});

const data = await response.json();
console.log(data);
```

### Go

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

func main() {
	payload := strings.NewReader(`{
  "model": "nabrah_tts_v1",
  "input": "Hello from Nabrah!",
  "voice": "00000000-0000-4000-8000-000000000000",
  "response_format": "mp3",
  "stream": false,
  "use_cache": true
}`)

	req, _ := http.NewRequest("POST", "https://api.nabrah.ai/api/ext/tts/generations", payload)
	req.Header.Set("X-API-Key", "<your-api-key>")
	req.Header.Set("Content-Type", "application/json")

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

	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```
