> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gnani.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Text-to-Speech (Realtime)

> Real-time text-to-speech with streaming audio via WebSocket.

## Overview

Stream audio in real-time with the lowest latency. Perfect for interactive assistants and live applications.

| Use case                                      | Recommended endpoint                    |
| :-------------------------------------------- | :-------------------------------------- |
| Lowest-latency interactive / live apps        | **This endpoint**                       |
| Full audio in one response (downloads, batch) | [TTS REST](/api/TTS/tts-inference)      |
| Chunked playback as audio is generated        | [TTS Streaming (SSE)](/api/TTS/tts-sse) |

<Note>
  Passing numbers, IDs, dates, or currency as raw strings causes mispronunciations. See the [Input Formatting Guide](/api/TTS/tts-input-formating) for correct formatting of phone numbers, account numbers, PINs, Aadhaar, vehicle registration numbers, GSTIN, currency, and more.
</Note>

***

## Models

| Model       | `model` value | Voices              | Extra parameters    |
| :---------- | :------------ | :------------------ | :------------------ |
| Timbre v2.0 | `timbre-v2.0` | English, Hindi      | —                   |
| Timbre v2.5 | `timbre-v2.5` | 10 Indian languages | `language`, `speed` |

<Tip>
  Use **Timbre v2.5** for multilingual support, and a much wider voice catalog.
</Tip>

***

## Endpoint

```text theme={null}
WSS wss://api.vachana.ai/api/v1/tts
```

***

## Connection Headers

Pass all configuration as WebSocket upgrade headers. Headers cannot be changed mid-session — reconnect with new headers to change settings.

| Header         | Required | Description        |
| :------------- | :------- | :----------------- |
| `X-API-Key-ID` | ✅        | Your Gnani API key |

***

## Connection Flow

1. **Client connects** — opens a WebSocket to `/api/v1/tts` with all required headers.
2. **Client sends a synthesis message** — JSON with `text`, `voice`, `model`, and `audio_config`.
3. **Server streams audio** — returns base64-encoded audio chunks as JSON messages.
4. **Server completes** — sends a final message with `is_final: true`. The client may send another request or close.
5. **Either side closes** — client or server may close the connection at any time.

<Note>
  Send one synthesis request at a time and consume audio chunks as they arrive for the lowest end-to-end latency.
</Note>

***

## Request Parameters

<Tabs>
  <Tab title="Timbre v2.5">
    ```json theme={null}
    {
      "text": "नमस्ते, आप कैसे हैं?",
      "voice": "Nalini",
      "model": "timbre-v2.5",
      "language": "hi-IN",
      "speed": 1.0,
      "audio_config": {
        "sample_rate": 48000,
        "num_channels": 1,
        "sample_width": 2,
        "encoding": "linear_pcm",
        "container": "wav"
      }
    }
    ```

    <ParamField body="text" type="string" required>
      The text to synthesize into speech. Pass numbers, dates, and currency as spoken words to avoid mispronunciations — see [Input Formatting Guide](/api/TTS/tts-input-formating).
    </ParamField>

    <ParamField body="model" type="string" required>
      Set to `timbre-v2.5`.
    </ParamField>

    <ParamField body="voice" type="string" required>
      Voice name from the Timbre v2.5 catalog. See the [Voice Catalog](/api/TTS/available-voices). Example: `Nalini`, `Kaveri`, `Deepak`.
    </ParamField>

    <ParamField body="language" type="string" required>
      Language of the input text. Controls text normalization and routing.

      | Value   | Language                                                |
      | :------ | :------------------------------------------------------ |
      | `auto`  | Auto-detect from script (recommended for mixed content) |
      | `hi-IN` | Hindi                                                   |
      | `en-IN` | English                                                 |
      | `ta-IN` | Tamil                                                   |
      | `te-IN` | Telugu                                                  |
      | `kn-IN` | Kannada                                                 |
      | `ml-IN` | Malayalam                                               |
      | `mr-IN` | Marathi                                                 |
      | `pa-IN` | Punjabi                                                 |
      | `bn-IN` | Bengali                                                 |
      | `gu-IN` | Gujarati                                                |
    </ParamField>

    <ParamField body="speed" type="number | string" default="1.0">
      Playback speed multiplier. Numeric range: `0.85` (slowest) to `1.15` (fastest). String shortcuts: `"slow"` = 0.85, `"medium"` = 1.0, `"fast"` = 1.15.
    </ParamField>

    <ParamField body="audio_config" type="object">
      Controls the format of the streamed audio chunks.

      <Expandable title="audio_config fields">
        <ParamField body="audio_config.sample_rate" type="integer" default="48000">
          Sample rate in Hz. Supported: `8000`, `16000`, `22050`, `24000`, `44100`, `48000`.
        </ParamField>

        <ParamField body="audio_config.num_channels" type="integer" default="1">
          Number of audio channels. `1` for mono.
        </ParamField>

        <ParamField body="audio_config.sample_width" type="integer" default="2">
          Sample width in bytes. `2` for 16-bit audio.
        </ParamField>

        <ParamField body="audio_config.encoding" type="string" default="linear_pcm">
          Audio encoding format. Options: `linear_pcm`, `pcm_s16le`, `pcm_mulaw`, `pcm_alaw`, `oggopus`. For telephony, prefer `container=mulaw` or `container=alaw` over this field — both produce the same output. Use `oggopus` (or `container=ogg`) for a playable OGG Opus file. Not required when `container` is `mp3`.
        </ParamField>

        <ParamField body="audio_config.container" type="string" default="wav">
          Output container format. Options: `wav`, `raw`, `mp3`, `ogg`, `mulaw`, `alaw`. Use `ogg` for OGG Opus. Use `mulaw` or `alaw` for G.711 telephony (forces 8000 Hz).
        </ParamField>

        <ParamField body="audio_config.bitrate" type="string" default="128k">
          MP3 bitrate. Only used when `container` is `mp3`. Supported: `32k`, `64k`, `96k`, `128k`, `192k`.
        </ParamField>
      </Expandable>
    </ParamField>
  </Tab>

  <Tab title="Timbre v2.0">
    ```json theme={null}
    {
      "text": "नमस्ते, आप कैसे हैं?",
      "voice": "Pranav",
      "model": "timbre-v2.0",
      "language": "IND-IN",
      "audio_config": {
        "sample_rate": 48000,
        "num_channels": 1,
        "sample_width": 2,
        "encoding": "linear_pcm",
        "container": "wav"
      }
    }
    ```

    <ParamField body="text" type="string" required>
      The text to synthesize into speech. Pass numbers, dates, and currency as spoken words to avoid mispronunciations — see [Input Formatting Guide](/api/TTS/tts-input-formating).
    </ParamField>

    <ParamField body="model" type="string" required>
      Set to `timbre-v2.0`.
    </ParamField>

    <ParamField body="voice" type="string" required>
      Voice name. One of: `Pranav`, `Kaveri`, `Shubhra`, `Deepak`.
    </ParamField>

    <ParamField body="language" type="string" default="IND-IN">
      Language of the input text. Use `IND-IN` for this model.
    </ParamField>

    <ParamField body="audio_config" type="object">
      Controls the format of the streamed audio chunks.

      <Expandable title="audio_config fields">
        <ParamField body="audio_config.sample_rate" type="integer" default="48000">
          Sample rate in Hz. Supported: `8000`, `16000`, `22050`, `24000`, `44100`, `48000`.
        </ParamField>

        <ParamField body="audio_config.num_channels" type="integer" default="1">
          Number of audio channels. `1` for mono, `2` for stereo.
        </ParamField>

        <ParamField body="audio_config.sample_width" type="integer" default="2">
          Sample width in bytes. `2` for 16-bit audio.
        </ParamField>

        <ParamField body="audio_config.encoding" type="string" default="linear_pcm">
          Audio encoding format. Options: `linear_pcm`, `pcm_s16le`, `pcm_mulaw`, `pcm_alaw`, `oggopus`. For telephony, prefer `container=mulaw` or `container=alaw` over this field — both produce the same output. Use `oggopus` (or `container=ogg`) for a playable OGG Opus file. Not required when `container` is `mp3`.
        </ParamField>

        <ParamField body="audio_config.container" type="string" default="wav">
          Output container format. Options: `wav`, `raw`, `mp3`, `ogg`, `mulaw`, `alaw`. Use `ogg` for OGG Opus. Use `mulaw` or `alaw` for G.711 telephony (forces 8000 Hz).
        </ParamField>

        <ParamField body="audio_config.bitrate" type="string" default="128k">
          MP3 bitrate. Only used when `container` is `mp3`. Supported: `32k`, `64k`, `96k`, `128k`, `192k`.
        </ParamField>
      </Expandable>
    </ParamField>
  </Tab>
</Tabs>

***

## Audio Format Reference

| `container` | `encoding`   | Output                 | `sample_rate`      | `bitrate`    | Content-Type               |
| ----------- | ------------ | ---------------------- | ------------------ | ------------ | -------------------------- |
| `wav`       | `linear_pcm` | WAV file (with header) | 8000–48000 Hz      | —            | `audio/wav`                |
| `raw`       | `linear_pcm` | Raw 16-bit PCM         | 8000–48000 Hz      | —            | `application/octet-stream` |
| `mp3`       | —            | MP3 file               | 8000–48000 Hz      | `32k`–`192k` | `audio/mpeg`               |
| `ogg`       | —            | OGG Opus file          | 8000–48000 Hz      | —            | `audio/ogg`                |
| `mulaw`     | —            | Raw G.711 µ-law        | **forced 8000 Hz** | —            | `audio/basic`              |
| `alaw`      | —            | Raw G.711 A-law        | **forced 8000 Hz** | —            | `audio/alaw`               |

**Encoding aliases** — produce identical output to the `container` rows above:

| `container`    | `encoding`  | Equivalent to     |
| -------------- | ----------- | ----------------- |
| `raw`          | `pcm_mulaw` | `container=mulaw` |
| `raw`          | `pcm_alaw`  | `container=alaw`  |
| `raw` or `ogg` | `oggopus`   | `container=ogg`   |

`bitrate` only applies when `container=mp3`. `container=mulaw`/`alaw` override `sample_rate` to 8000 Hz.

<Note>
  When `container=ogg` or `encoding=oggopus` is requested, all audio is encoded into a single OGG Opus file and delivered as one chunk after synthesis completes.
</Note>

***

## Server Messages

### `start`

Sent when the server begins synthesis for the current request.

```json theme={null}
{ "type": "start", "message": "Streaming started", "request_id": "req_abc123" }
```

### Audio chunk

Each audio chunk arrives as a JSON message. Decode `audio` from base64 and append chunks in order.

```json theme={null}
{
  "type": "audio",
  "data": {
    "chunk_index": 1,
    "audio": "<base64-encoded audio bytes>",
    "is_final": false
  }
}
```

### `complete`

Sent when all audio for the current request has been streamed. `is_final` is `true` and `audio` is empty.

```json theme={null}
{
  "type": "complete",
  "data": {
    "chunk_index": 12,
    "audio": "",
    "is_final": true
  }
}
```

### `error`

Sent when the server encounters an error. The connection remains open after recoverable errors.

```json theme={null}
{ "type": "error", "message": "Invalid text or audio configuration." }
```

***

## Code Example

<Tabs>
  <Tab title="Timbre V2.5">
    <CodeGroup>
      ```javascript JavaScript theme={null}
      const ws = new WebSocket("wss://api.vachana.ai/api/v1/tts", {
        headers: {
          "Content-Type": "application/json",
          "X-API-Key-ID": "<your-api-key>",
        },
      });

      ws.on("open", () => {
        ws.send(JSON.stringify({
          text: "नमस्ते, आप कैसे हैं?",
          voice: "Nalini",
          model: "timbre-v2.5",
          language: "hi-IN",
          speed: 1.0,
          audio_config: {
            sample_rate: 48000,
            num_channels: 1,
            sample_width: 2,
            encoding: "linear_pcm",
            container: "wav",
          },
        }));
      });

      ws.on("message", (data) => {
        const msg = JSON.parse(data);
        if (msg.type === "audio") {
          const chunk = Buffer.from(msg.data.audio, "base64");
          // append chunk to your audio buffer
        }
      });

      ws.on("error", (error) => console.error("WebSocket error:", error));
      ws.on("close", () => console.log("Connection closed"));
      ```

      ```python Python theme={null}
      import websocket, json, base64

      def on_message(ws, message):
          msg = json.loads(message)
          if msg.get("type") == "audio":
              chunk = base64.b64decode(msg["data"]["audio"])
              # append chunk to your audio buffer

      def on_error(ws, error):
          print(f"Error: {error}")

      def on_close(ws, close_status_code, close_msg):
          print("Connection closed")

      def on_open(ws):
          ws.send(json.dumps({
              "text": "नमस्ते, आप कैसे हैं?",
              "voice": "Nalini",
              "model": "timbre-v2.5",
              "language": "hi-IN",
              "speed": 1.0,
              "audio_config": {
                  "sample_rate": 48000,
                  "num_channels": 1,
                  "sample_width": 2,
                  "encoding": "linear_pcm",
                  "container": "wav",
              },
          }))

      ws = websocket.WebSocketApp(
          "wss://api.vachana.ai/api/v1/tts",
          header={
              "Content-Type": "application/json",
              "X-API-Key-ID": "<your-api-key>",
          },
          on_open=on_open,
          on_message=on_message,
          on_error=on_error,
          on_close=on_close,
      )

      ws.run_forever()
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Timbre V2.0">
    <CodeGroup>
      ```javascript JavaScript theme={null}
      const ws = new WebSocket("wss://api.vachana.ai/api/v1/tts", {
        headers: {
          "Content-Type": "application/json",
          "X-API-Key-ID": "<your-api-key>",
        },
      });

      ws.on("open", () => {
        ws.send(JSON.stringify({
          text: "नमस्ते, आप कैसे हैं?",
          voice: "Pranav",
          model: "timbre-v2.0",
          language: "IND-IN",
          audio_config: {
            sample_rate: 48000,
            num_channels: 1,
            sample_width: 2,
            encoding: "linear_pcm",
            container: "wav",
          },
        }));
      });

      ws.on("message", (data) => {
        const msg = JSON.parse(data);
        if (msg.type === "audio") {
          const chunk = Buffer.from(msg.data.audio, "base64");
          // append chunk to your audio buffer
        }
      });

      ws.on("error", (error) => console.error("WebSocket error:", error));
      ws.on("close", () => console.log("Connection closed"));
      ```

      ```python Python theme={null}
      import websocket, json, base64

      def on_message(ws, message):
          msg = json.loads(message)
          if msg.get("type") == "audio":
              chunk = base64.b64decode(msg["data"]["audio"])
              # append chunk to your audio buffer

      def on_error(ws, error):
          print(f"Error: {error}")

      def on_close(ws, close_status_code, close_msg):
          print("Connection closed")

      def on_open(ws):
          ws.send(json.dumps({
              "text": "नमस्ते, आप कैसे हैं?",
              "voice": "Pranav",
              "model": "timbre-v2.0",
              "language": "IND-IN",
              "audio_config": {
                  "sample_rate": 48000,
                  "num_channels": 1,
                  "sample_width": 2,
                  "encoding": "linear_pcm",
                  "container": "wav",
              },
          }))

      ws = websocket.WebSocketApp(
          "wss://api.vachana.ai/api/v1/tts",
          header={
              "Content-Type": "application/json",
              "X-API-Key-ID": "<your-api-key>",
          },
          on_open=on_open,
          on_message=on_message,
          on_error=on_error,
          on_close=on_close,
      )

      ws.run_forever()
      ```
    </CodeGroup>
  </Tab>
</Tabs>

***

## Available Voices

<Tabs>
  <Tab title="Timbre  v2.5">
    44 high-quality voices across 9 Indian languages and English. See the full [Voice Catalog](/api/TTS/available-voices) for all voice names, languages, and descriptions.

    Popular examples: `Nalini` (Hindi, Female), `Kaveri` (English, Female), `Deepak` (Hindi, Male), `Girish` (English, Male).
  </Tab>

  <Tab title="Timbre  v2.0">
    4 voices for Hindi and English. See the full [Voice Catalog](/api/TTS/available-voices) for all voice names, languages, and descriptions. Also supports Voice cloning Refer to [Voice CLoning](/api/VC/voice-clone-embeddings)
  </Tab>
</Tabs>

***

## Python SDK

The SDK's realtime client manages the WebSocket lifecycle and async iteration so you can focus on your application logic.

### Installation

```bash theme={null}
pip install gnani-vachana
```

Requires **Python 3.10+**.

### Authentication

<CodeGroup>
  ```python Constructor argument theme={null}
  from gnani.tts import GnaniTTSRealtimeClient

  client = GnaniTTSRealtimeClient(api_key="your-api-key")
  ```

  ```bash Environment variable theme={null}
  export GNANI_API_KEY="your-api-key"
  ```

  ```python Environment variable (usage) theme={null}
  from gnani.tts import GnaniTTSRealtimeClient

  client = GnaniTTSRealtimeClient()
  ```
</CodeGroup>

### Stream Audio Chunks in Real-Time

```python theme={null}
import asyncio
from gnani.tts import GnaniTTSRealtimeClient

async def main():
    async with GnaniTTSRealtimeClient(api_key="your-api-key") as client:
        with open("output.wav", "wb") as f:
            async for chunk in client.synthesize(
                "नमस्ते, आप कैसे हैं?",
                voice="Nalini",
                model="timbre-v2.5",
                language="hi-IN",
            ):
                f.write(chunk)

asyncio.run(main())
```

### Collect All Audio at Once

```python theme={null}
import asyncio
from gnani.tts import GnaniTTSRealtimeClient

async def main():
    async with GnaniTTSRealtimeClient(api_key="your-api-key") as client:
        audio = await client.synthesize_and_collect(
            "नमस्ते, आप कैसे हैं?",
            voice="Nalini",
            model="timbre-v2.5",
            language="hi-IN",
        )
        with open("output.wav", "wb") as f:
            f.write(audio)

asyncio.run(main())
```

***
