> ## Documentation Index
> Fetch the complete documentation index at: https://60db.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# TTS WebSocket

> Real-time Text-to-Speech WebSocket API for streaming audio synthesis

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# TTS WebSocket API

Real-time Text-to-Speech synthesis via WebSocket streaming with full-duplex bidirectional communication.

## 🚀 Quick Start (Copy & Paste)

```javascript theme={null}
const WebSocket = require('ws');
const fs = require('fs');

// 1. Your API key
const API_KEY = 'sk_live_your_api_key';

// 2. Connect
const ws = new WebSocket(`wss://api.60db.ai/ws/tts?apiKey=${API_KEY}`);

// 3. Unique context ID
const contextId = 'my-session-' + Date.now();
const audioChunks = [];

// 4. Handle messages
ws.on('message', (data) => {
  const msg = JSON.parse(data);

  // Authenticated? Create context!
  if (msg.connection_established) {
    console.log('✅ Authenticated');
    ws.send(JSON.stringify({
      create_context: {
        context_id: contextId,
        voice_id: 'fbb75ed2-975a-40c7-9e06-38e30524a9a1',
        audio_config: { audio_encoding: 'LINEAR16', sample_rate_hertz: 16000 }
      }
    }));
  }

  // Context ready? Send text!
  if (msg.context_created) {
    console.log('✅ Context created');

    // Send your text
    ws.send(JSON.stringify({
      send_text: { context_id: contextId, text: 'Hello, world!' }
    }));

    // Flush to get audio
    ws.send(JSON.stringify({
      flush_context: { context_id: contextId }
    }));
  }

  // Got audio? Save it!
  if (msg.audio_chunk) {
    const audioData = Buffer.from(msg.audio_chunk.audioContent, 'base64');
    audioChunks.push(audioData);
    console.log('🔊 Audio chunk:', audioData.length, 'bytes');
  }

  // All audio received?
  if (msg.flush_completed) {
    console.log('✅ All audio received!');
    console.log('   Total:', audioChunks.length, 'chunks');

    // Close context
    ws.send(JSON.stringify({
      close_context: { context_id: contextId }
    }));
  }

  // Done! Save audio
  if (msg.context_closed) {
    console.log('✅ Complete!');

    // Save to file
    const completeAudio = Buffer.concat(audioChunks);
    fs.writeFileSync('output.pcm', completeAudio);
    console.log('💾 Saved: output.pcm');
    console.log('   Size:', completeAudio.length, 'bytes');

    ws.close();
  }
});
```

**That's it!** You'll see:

* ✅ Authenticated
* ✅ Context created
* 🔊 Audio chunk: 1024 bytes (multiple times)
* ✅ All audio received!
* ✅ Complete!
* 💾 Saved: output.pcm

***

## 📖 How It Works (5 Simple Steps)

1. **Connect** with your API key
2. **Create** a context with voice settings
3. **Send** your text message
4. **Flush** to trigger synthesis
5. **Close** when done (receive audio file)

***

## Endpoint

<ParamField name="url" type="string">
  `ws://api.60db.ai/ws/tts`
</ParamField>

## Authentication

Query parameter authentication:

<ParamField name="apiKey" type="string" required>
  Your API key for authentication
</ParamField>

Example:

```
ws://api.60db.ai/ws/tts?apiKey=sk_live_your_api_key
```

## Protocol Overview

```
Client                                  Server
  |                                       |
  |─── create_context ──────────────────▶ |
  |◀── context_created ─────────────────  |
  |                                       |
  |─── send_text ───────────────────────▶ |
  |─── flush_context ───────────────────▶ |
  |◀── audio_chunk #1 ──────────────────  |
  |◀── audio_chunk #N ──────────────────  |
  |◀── flush_completed ─────────────────  |
  |                                       |
  |─── close_context ───────────────────▶ |
  |◀── context_closed ──────────────────  |
```

## Connection Sequence

### 1. Connect

```javascript theme={null}
const ws = new WebSocket('ws://api.60db.ai/ws/tts?apiKey=sk_live_your_key');
```

### 2. Receive Authentication Message

<ResponseExample>
  ```json Response theme={null}
  {
    "connecting": true,
    "message": "Authenticating...",
    "timestamp": 1775465918269
  }
  ```
</ResponseExample>

### 3. Receive Connection Established

<ResponseExample>
  ```json Response theme={null}
  {
    "connection_established": {
      "service": "tts",
      "user_id": 43,
      "credit_balance": 9.97,
      "workspace": "default"
    }
  }
  ```
</ResponseExample>

**Fields:**

<ResponseField name="service" type="string">
  Service name: `"tts"`
</ResponseField>

<ResponseField name="user_id" type="integer">
  Your user ID
</ResponseField>

<ResponseField name="credit_balance" type="number">
  Available credits
</ResponseField>

<ResponseField name="workspace" type="string">
  Workspace name
</ResponseField>

## Client → Server Messages

### 1. create\_context

**Must be the first message.** Initializes the TTS session with voice and audio settings.

<RequestExample>
  ```json Request theme={null}
  {
    "create_context": {
      "context_id": "my-session-123",
      "voice_id": "7911a3e8",
      "audio_config": {
        "audio_encoding": "LINEAR16",
        "sample_rate_hertz": 16000
      },
      "speaking_rate": 1.0,
      "pitch": 0.0,
      "stability": 1.0,
      "boost_volume": false
    }
  }
  ```
</RequestExample>

**Parameters:**

<ParamField name="context_id" type="string">
  Unique session identifier. Default: auto-generated UUID
</ParamField>

<ParamField name="voice_id" type="string" required>
  Voice ID to use for synthesis
</ParamField>

<ParamField name="audio_config.audio_encoding" type="string" default="LINEAR16">
  Audio encoding. Options: `LINEAR16`, `PCM`, `MULAW`, `ULAW`, `OGG_OPUS`
</ParamField>

<ParamField name="audio_config.sample_rate_hertz" type="integer" default="16000">
  Sample rate in Hz. Options: `8000`, `16000`, `24000`, `48000`
</ParamField>

<ParamField name="speaking_rate" type="number" default="1.0">
  Speed multiplier (0.5 – 1.5). Values outside this range are clamped.
</ParamField>

<ParamField name="pitch" type="number" default="0.0">
  Pitch shift in semitones (-6.0 to 6.0). Values outside this range are clamped.
</ParamField>

<ParamField name="stability" type="number" default="1.0">
  Voice stability (0.6 = most expressive, 1.5 = most consistent). Controls generation temperature internally.
</ParamField>

<ParamField name="boost_volume" type="boolean" default="false">
  When `true`, normalizes output audio to -14 LUFS (broadcast loudness standard).
</ParamField>

**Supported encoding + sample rate combinations:**

| `audio_encoding` | Supported `sample_rate_hertz`               | Output format                              |
| ---------------- | ------------------------------------------- | ------------------------------------------ |
| `LINEAR16`       | `8000`, `16000` (default), `24000`, `48000` | Raw PCM, 16-bit signed little-endian, mono |
| `PCM`            | `8000`, `16000` (default), `24000`, `48000` | Same as LINEAR16                           |
| `MULAW`          | `8000`                                      | G.711 μ-law encoded, mono                  |
| `ULAW`           | `8000`                                      | Same as MULAW                              |
| `OGG_OPUS`       | `24000`                                     | Ogg Opus compressed audio                  |

> **Note:** `MULAW`/`ULAW` only works at `8000` Hz. `OGG_OPUS` only works at `24000` Hz.

**Limits:**

| Parameter                 | Min    | Max          | Default |
| ------------------------- | ------ | ------------ | ------- |
| `speaking_rate`           | 0.5    | 1.5          | 1.0     |
| `pitch`                   | -6.0   | 6.0          | 0.0     |
| `stability`               | 0.6    | 1.5          | 1.0     |
| `text` (per send\_text)   | 1 char | —            | —       |
| text buffer (accumulated) | —      | 50,000 chars | —       |

### 2. send\_text

Append text to the internal buffer. Text is accumulated until a `flush_context` or `close_context` is received.

<RequestExample>
  ```json Request theme={null}
  {
    "send_text": {
      "context_id": "my-session-123",
      "text": "Hello, how are you doing today?"
    }
  }
  ```
</RequestExample>

**Fields:**

<ParamField name="context_id" type="string">
  Session identifier
</ParamField>

<ParamField name="text" type="string" required>
  Text to append to buffer (max cumulative 50,000 characters)
</ParamField>

You can send multiple `send_text` messages to build up text incrementally (e.g., from an LLM token stream):

```json theme={null}
{"send_text": {"context_id": "ctx-1", "text": "Hello, "}}
{"send_text": {"context_id": "ctx-1", "text": "how are you "}}
{"send_text": {"context_id": "ctx-1", "text": "doing today?"}}
```

### 3. flush\_context

Triggers synthesis of all accumulated text. The server responds with `audio_chunk` messages followed by `flush_completed`.

<RequestExample>
  ```json Request theme={null}
  {
    "flush_context": {
      "context_id": "my-session-123"
    }
  }
  ```
</RequestExample>

### 4. close\_context

Flushes any remaining text, sends final audio, and closes the WebSocket connection.

<RequestExample>
  ```json Request theme={null}
  {
    "close_context": {
      "context_id": "my-session-123"
    }
  }
  ```
</RequestExample>

## Server → Client Messages

### context\_created

Confirms the session was initialized successfully.

<ResponseExample>
  ```json Response theme={null}
  {
    "context_created": {
      "context_id": "my-session-123"
    }
  }
  ```
</ResponseExample>

### audio\_chunk

Contains a chunk of synthesized audio. Multiple chunks are sent per flush.

<ResponseExample>
  ```json Response theme={null}
  {
    "audio_chunk": {
      "context_id": "my-session-123",
      "audioContent": "SGVsbG8gd29ybGQ..."
    }
  }
  ```
</ResponseExample>

**Fields:**

<ResponseField name="context_id" type="string">
  Session identifier
</ResponseField>

<ResponseField name="audioContent" type="string">
  Base64-encoded audio bytes
</ResponseField>

The audio encoding and chunk format depend on `audio_config`:

| Encoding           | Chunk format                    | Notes                                                                              |
| ------------------ | ------------------------------- | ---------------------------------------------------------------------------------- |
| `LINEAR16` / `PCM` | Raw PCM, 16-bit signed LE, mono | Chunks can be concatenated directly                                                |
| `MULAW` / `ULAW`   | G.711 μ-law, 8-bit, mono        | Chunks can be concatenated directly                                                |
| `OGG_OPUS`         | Independent Ogg Opus files      | Each chunk is a self-contained OGG file. **Chunks cannot be naively concatenated** |

### flush\_completed

Signals that all audio for the flushed text has been sent.

<ResponseExample>
  ```json Response theme={null}
  {
    "flush_completed": {
      "context_id": "my-session-123"
    }
  }
  ```
</ResponseExample>

### context\_closed

Confirms the session is closed. The WebSocket connection closes after this message.

<ResponseExample>
  ```json Response theme={null}
  {
    "context_closed": {
      "context_id": "my-session-123"
    }
  }
  ```
</ResponseExample>

### error

Sent if synthesis fails or a protocol violation occurs.

<ResponseExample>
  ```json Response theme={null}
  {
    "error": {
      "context_id": "my-session-123",
      "message": "voice_id required"
    }
  }
  ```
</ResponseExample>

**Common errors:**

| Message                                      | Cause                                      |
| -------------------------------------------- | ------------------------------------------ |
| `voice_id required`                          | `create_context` sent without `voice_id`   |
| `text_buffer exceeded 50000 character limit` | Too much text accumulated without flushing |
| `Unsupported audio_encoding: X`              | Invalid encoding value                     |
| `Unsupported sample_rate_hertz: X`           | Invalid sample rate                        |

## Complete Example

<Tabs>
  <TabItem value="javascript" label="JavaScript (Node.js)">
    ```javascript theme={null}
    const WebSocket = require('ws');

    const API_KEY = 'sk_live_your_key';
    const ws = new WebSocket(`ws://api.60db.ai/ws/tts?apiKey=${API_KEY}`);
    const contextId = 'test-' + Date.now();
    const audioChunks = [];

    ws.on('open', () => {
      console.log('✓ Connected');
    });

    ws.on('message', (data) => {
      const msg = JSON.parse(data);
      const msgType = Object.keys(msg)[0];
      console.log('←', msgType);

      if (msg.connection_established) {
        console.log('  Credits:', msg.connection_established.credit_balance);

        // Create context
        ws.send(JSON.stringify({
          create_context: {
            context_id: contextId,
            voice_id: 'fbb75ed2-975a-40c7-9e06-38e30524a9a1',
            audio_config: {
              audio_encoding: 'LINEAR16',
              sample_rate_hertz: 16000
            },
            speaking_rate: 1.0,
            pitch: 0.0,
            stability: 1.0
          }
        }));

      } else if (msg.context_created) {
        console.log('✓ Context created!');

        // Send text
        ws.send(JSON.stringify({
          send_text: {
            context_id: contextId,
            text: 'Hello, how are you doing today?'
          }
        }));

        // Flush
        ws.send(JSON.stringify({
          flush_context: { context_id: contextId }
        }));

      } else if (msg.audio_chunk) {
        const audioData = Buffer.from(msg.audio_chunk.audioContent, 'base64');
        audioChunks.push(audioData);
        console.log('  Received audio chunk:', audioData.length, 'bytes');

      } else if (msg.flush_completed) {
        console.log('✓ Flush completed!');
        console.log('  Total audio size:', audioChunks.reduce((sum, chunk) => sum + chunk.length, 0), 'bytes');

        // Close context
        ws.send(JSON.stringify({
          close_context: { context_id: contextId }
        }));

      } else if (msg.context_closed) {
        console.log('✓ Context closed');

        // Save audio
        const audio = Buffer.concat(audioChunks);
        require('fs').writeFileSync('output.pcm', audio);
        console.log('Saved output.pcm');

        ws.close();
      }

      if (msg.error) {
        console.error('TTS Error:', msg.error.message);
      }
    });

    ws.on('error', (error) => {
      console.error('Error:', error);
    });

    ws.on('close', () => {
      console.log('Connection closed');
    });
    ```
  </TabItem>

  <TabItem value="python" label="Python">
    ```python theme={null}
    import asyncio
    import json
    import base64
    import websockets

    async def tts_websocket():
        API_KEY = "sk_live_your_key"
        url = f"ws://api.60db.ai/ws/tts?apiKey={API_KEY}"
        context_id = f"session-{int(asyncio.get_event_loop().time())}"

        async with websockets.connect(url) as ws:
            # Wait for connection
            resp = json.loads(await ws.recv())
            if resp.get('connection_established'):
                print(f"✓ Connected (Credits: ${resp['connection_established']['credit_balance']})")

            # Create context
            await ws.send(json.dumps({
                "create_context": {
                    "context_id": context_id,
                    "voice_id": "fbb75ed2-975a-40c7-9e06-38e30524a9a1",
                    "audio_config": {
                        "audio_encoding": "LINEAR16",
                        "sample_rate_hertz": 16000
                    },
                    "speaking_rate": 1.0,
                    "pitch": 0.0,
                    "stability": 1.0
                }
            }))

            # Wait for context_created
            resp = json.loads(await ws.recv())
            assert "context_created" in resp
            print("✓ Context created!")

            # Send text + flush
            await ws.send(json.dumps({
                "send_text": {
                    "context_id": context_id,
                    "text": "Hello, how are you doing today?"
                }
            }))
            await ws.send(json.dumps({
                "flush_context": {"context_id": context_id}
            }))

            # Receive audio chunks until flush_completed
            audio_data = b""
            chunk_count = 0
            while True:
                msg = json.loads(await ws.recv())
                if "audio_chunk" in msg:
                    audio_data += base64.b64decode(msg["audio_chunk"]["audioContent"])
                    chunk_count += 1
                    print(f"  Received chunk #{chunk_count}")
                elif "flush_completed" in msg:
                    print("✓ Flush completed!")
                    break
                elif "error" in msg:
                    print(f"Error: {msg['error']['message']}")
                    break

            # Close context
            await ws.send(json.dumps({
                "close_context": {"context_id": context_id}
            }))
            resp = json.loads(await ws.recv())
            assert "context_closed" in resp

            # Save audio
            with open("output.pcm", "wb") as f:
                f.write(audio_data)
            print(f"✓ Saved {len(audio_data)} bytes to output.pcm")

    asyncio.run(tts_websocket())
    ```
  </TabItem>

  <TabItem value="browser" label="Browser">
    ```javascript theme={null}
    const ws = new WebSocket('ws://api.60db.ai/ws/tts?apiKey=sk_live_your_key');
    const contextId = 'test-' + Date.now();

    const audioChunks = [];
    let audioCtx;

    ws.onopen = () => {
      console.log('✓ Connected');
    };

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      const msgType = Object.keys(data)[0];
      console.log('←', msgType);

      if (data.connection_established) {
        console.log('  Credits:', data.connection_established.credit_balance);

        // Create context
        ws.send(JSON.stringify({
          create_context: {
            context_id: contextId,
            voice_id: 'fbb75ed2-975a-40c7-9e06-38e30524a9a1',
            audio_config: { audio_encoding: 'LINEAR16', sample_rate_hertz: 16000 },
            speaking_rate: 1.0,
            pitch: 0.0,
            stability: 1.0
          }
        }));

      } else if (data.context_created) {
        console.log('✓ Context created!');

        // Send text + flush
        ws.send(JSON.stringify({
          send_text: { context_id: contextId, text: 'Hello, how are you?' }
        }));
        ws.send(JSON.stringify({
          flush_context: { context_id: contextId }
        }));

      } else if (data.audio_chunk) {
        // Collect base64 audio
        const binary = atob(data.audio_chunk.audioContent);
        const bytes = new Uint8Array(binary.length);
        for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
        audioChunks.push(bytes);
        console.log('  Received audio chunk');

      } else if (data.flush_completed) {
        console.log('✓ Flush completed!');

        // Play collected audio
        playPCM16(audioChunks, 16000);

        // Close context
        ws.send(JSON.stringify({
          close_context: { context_id: contextId }
        }));
      }

      if (data.error) {
        console.error('TTS Error:', data.error.message);
      }

      if (data.context_closed) {
        console.log('✓ Context closed');
      }
    };

    function playPCM16(chunks, sampleRate) {
      const totalBytes = chunks.reduce((s, c) => s + c.length, 0);
      const merged = new Uint8Array(totalBytes);
      let offset = 0;
      for (const chunk of chunks) {
        merged.set(chunk, offset);
        offset += chunk.length;
      }

      const int16 = new Int16Array(merged.buffer);
      const float32 = new Float32Array(int16.length);
      for (let i = 0; i < int16.length; i++) float32[i] = int16[i] / 32768;

      audioCtx = audioCtx || new AudioContext({ sampleRate });
      const buf = audioCtx.createBuffer(1, float32.length, sampleRate);
      buf.getChannelData(0).set(float32);
      const src = audioCtx.createBufferSource();
      src.buffer = buf;
      src.connect(audioCtx.destination);
      src.start();
    }
    ```
  </TabItem>
</Tabs>

## Audio Configuration

| Encoding   | Sample Rates              | Description             |
| ---------- | ------------------------- | ----------------------- |
| `LINEAR16` | 8000, 16000, 24000, 48000 | PCM 16-bit signed       |
| `MULAW`    | 8000                      | G.711 μ-law (telephony) |
| `OGG_OPUS` | 24000                     | Compressed audio        |

For telephony integration (Twilio, etc.), use `MULAW` at `8000` Hz.

## Default Voice

The default voice ID is:

```
fbb75ed2-975a-40c7-9e06-38e30524a9a1
```

To get more voices, use the [Voices API](/api-reference/voices/get-voices).

## Context Management

### Reuse Context

Keep a context open for multiple syntheses:

```javascript theme={null}
// Create once
ws.send(JSON.stringify({
  create_context: { context_id, voice_id, audio_config }
}));

// Send multiple texts
ws.send(JSON.stringify({ send_text: { context_id, text: "Hello" } }));
ws.send(JSON.stringify({ flush_context: { context_id } }));

ws.send(JSON.stringify({ send_text: { context_id, text: "World" } }));
ws.send(JSON.stringify({ flush_context: { context_id } }));

// Close when done
ws.send(JSON.stringify({ close_context: { context_id } }));
```

### Multiple Contexts

You can create multiple contexts in one connection:

```javascript theme={null}
const context1 = 'ctx-1';
const context2 = 'ctx-2';

// Create both contexts
ws.send(JSON.stringify({
  create_context: { context_id: context1, voice_id: voice1, audio_config }
}));

ws.send(JSON.stringify({
  create_context: { context_id: context2, voice_id: voice2, audio_config }
}));
```

## Supported Languages

The TTS model supports synthesis in multiple Indic languages and English. The language is auto-detected from the input text.

| Language  | ID |
| --------- | -- |
| English   | en |
| Hindi     | hi |
| Bengali   | bn |
| Gujarati  | gu |
| Kannada   | kn |
| Malayalam | ml |
| Marathi   | mr |
| Punjabi   | pa |
| Tamil     | ta |
| Telugu    | te |

## Pricing

* **Rate**: \$0.00002 per character
* **Minimum**: \$0.01 per context
* **Billing**: Per character synthesized

## Related

* [STT WebSocket](/websocket-api/stt) - Speech-to-Text endpoint
* [WebSocket Quick Start](/websocket-api/quickstart) - Get started guide
* [WebSocket Playground](/websocket-playground) - Test in browser
* [Voices API](/api-reference/voices/get-voices) - Get available voices
