> 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. | > **Note** > > 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": [""] } ``` `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":""}}`, `{"type":"event","data":{"event_type":"final"}}`, and on failure `{"type":"error","data":{"message":"…","code":}}`. > **Warning** > > **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. > 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.