> For clean Markdown of any page, append `.md` to the page URL.
> For a complete documentation index, see https://docs.sarvam.ai/llms.txt.
> For full documentation content in one file, see https://docs.sarvam.ai/llms-full.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.sarvam.ai/_mcp/server.

# API reference — Text-to-Speech

> The InvokeEndpoint request and response contract for a self-hosted Bulbul v3 endpoint on SageMaker: parameters, the three inference modes (real-time, server-side streaming, bidirectional), voices, codecs, and errors.

The contract for invoking a self-hosted **Bulbul v3** endpoint. Send **`model: bulbul:v3`** in every request — it is the only accepted value (`bulbul:v3-beta` and v2 are rejected). Request bodies are JSON; responses are base64 audio (real-time) or a raw audio byte stream (streaming).

## Real-time — `InvokeEndpoint`

| Field            | Value                                      |
| ---------------- | ------------------------------------------ |
| **Operation**    | `sagemaker-runtime:InvokeEndpoint`         |
| **Content-Type** | `application/json`                         |
| **Accept**       | `application/json`                         |
| **Max text**     | 2500 characters (use streaming for longer) |

### Request fields

| Field                  | Type   | Required | Description                                                                                 |
| ---------------------- | ------ | :------: | ------------------------------------------------------------------------------------------- |
| `text`                 | string |    yes   | Text to synthesize (≤ 2500 chars real-time).                                                |
| `model`                | string |    yes   | `bulbul:v3` — the only accepted value.                                                      |
| `speaker`              | string |    no    | Voice; default `shubh`. 38 voices (aditya, ritu, priya, neha, amit, kavya, …).              |
| `language_code`        | string |    no    | e.g. `en-IN` (default).                                                                     |
| `output_audio_codec`   | string |    no    | `wav` (default), `mp3`, `linear16`, `mulaw`, `alaw`, `opus`, `aac`, `flac`.                 |
| `speech_sample_rate`   | int    |    no    | `8000`, `16000`, `22050`, `24000` (native 24 kHz; higher is rejected `400`, not upsampled). |
| `pace`                 | float  |    no    | `0.5`–`2.0` (default `1.0`).                                                                |
| `output_audio_bitrate` | string |    no    | For lossy codecs, e.g. `128k`.                                                              |
| `enable_preprocessing` | bool   |    no    | Text normalization.                                                                         |

The 38 voices are: aditya, ritu, priya, neha, rahul, pooja, rohan, simran, kavya, amit, dev, ishita, shreya, ratan, varun, manan, sumit, roopa, kabir, aayan, shubh, ashutosh, advait, anand, tanya, tarun, sunny, mani, gokul, vijay, shruti, suhani, mohit, kavitha, rehan, soham, rupali, niharika.

### Response

```json
{ "request_id": "20260730_…", "audios": ["<base64 audio>"] }
```

`audios` always contains exactly one element (the list is a historical artifact).

## Server-side streaming (SSE) — `InvokeEndpointWithResponseStream`

Call the streaming operation **and set `"stream": true` in the request body**. The flag lives in the JSON payload, not in an API parameter — SageMaker relays the body to the container verbatim, so calling the streaming API *without* the flag returns a single buffered chunk. The response is a sequence of `PayloadPart` events carrying raw encoded audio; concatenate them. **Time-to-first-byte is \~0.5–0.6 s regardless of text length.** Max text 3500 characters.

```python
resp = runtime.invoke_endpoint_with_response_stream(
    EndpointName=endpoint,
    ContentType="application/json",
    Body=json.dumps({**payload, "output_audio_codec": "mp3", "stream": True}),
)
for event in resp["Body"]:
    part = event.get("PayloadPart")
    if part:
        audio_out.write(part["Bytes"])
```

## Bidirectional — `InvokeEndpointWithBidirectionalStream`

A full-duplex session over SigV4 **HTTP/2, port 8443** (client `aws-sdk-sagemaker-runtime-http2`). Put `model` in the URL-encoded `ModelQueryString`. Frames are sent with `data_type="UTF8"`:

* **Send:** `{"type":"config","data":{…speaker, language_code, output_audio_codec, speech_sample_rate, pace…}}`, then per turn `{"type":"text","data":{"text":"…"}}` … `{"type":"flush"}`; `{"type":"ping"}` keep-alive.
* **Receive:** `{"type":"audio","data":{"audio":"<base64>"}}`, `{"type":"event","data":{"event_type":"final"}}`, and on failure `{"type":"error","data":{"message":"…","code":<http status>}}`.

**Codec caveat.** For **lossy** codecs (mp3 / aac / opus) the final partial frame plus the container trailer are flushed **at connection close, not at the turn's `final` event** (\~72 ms mp3 / \~128 ms aac / \~200 ms opus). **WAV and LINEAR16 are byte-exact at every `final`.** `flac` is accepted on real-time (`200`) but rejected (`400`) on the streaming paths.

## Errors

See [Error handling on SageMaker](/api/self-hosted/sagemaker/errors) for the 424 collapse and the shared envelope. Bad input → **`400 invalid_request_error`** (wrong `model`, empty or over-length `text`, unsupported `speech_sample_rate`, `pace` out of range, malformed JSON) with a descriptive Pydantic message:

```json
{
  "error": {
    "message": "Validation Error(s):\n- model: Input should be 'bulbul:v3'",
    "code": "invalid_request_error",
    "request_id": "20260729_…"
  }
}
```

Overload → **`503` / `code: "service_overloaded"`** with body `retry_after` (shed immediately, never queued):

```json
{
  "error": {
    "message": "server at capacity, retry later",
    "code": "service_overloaded",
    "request_id": "20260729_…",
    "retry_after": 5
  }
}
```

#### [Deploy a Bulbul v3 endpoint](/api/self-hosted/sagemaker/deploy-bulbul)

Step-by-step real-time, SSE, and bidirectional deployment with boto3.