Text-to-Speech — Bulbul
Live in-chat TTS → sarvam-mcp (sarvam_tools_tts_*). This skill = SDK code.
[!IMPORTANT]
Auth: api-subscription-key header — NOT Authorization: Bearer. Base URL: https://api.sarvam.ai (NOT /v1 — that prefix is only for the OpenAI-compatible chat endpoint)
SDK floor for the code below: Python sarvamai>=0.1.29, JS sarvamai@>=1.1.8. On older versions the TTS param is target_language_code — see Gotchas.
Model
bulbul:v3 — 11 languages, 37 voices (23 male, 14 female; default: shubh), REST/HTTP stream/WebSocket.
Quick Start (Python)
from sarvamai import SarvamAI
from sarvamai.play import save
client = SarvamAI()
response = client.text_to_speech.convert(
text="नमस्ते, आप कैसे हैं?",
language_code="hi-IN",
model="bulbul:v3",
speaker="shubh"
)
save(response, "output.wav")
# HTTP Stream (lower latency, binary audio)
chunks = []
for chunk in client.text_to_speech.convert_stream(
text="Hello from Sarvam AI",
language_code="en-IN",
speaker="shubh",
model="bulbul:v3"
):
chunks.append(chunk)
audio = b"".join(chunks)
Quick Start (JavaScript/TypeScript)
import { SarvamAIClient } from "sarvamai";
import { writeFile } from "fs/promises";
const client = new SarvamAIClient({ apiSubscriptionKey: "YOUR_SARVAM_API_KEY" });
// REST
const response = await client.textToSpeech.convert({
text: "नमस्ते, आप कैसे हैं?",
language_code: "hi-IN",
model: "bulbul:v3",
speaker: "shubh"
});
// HTTP Stream (lower latency, returns BinaryResponse)
const streamResponse = await client.textToSpeech.convertStream({
text: "Hello from Sarvam AI",
language_code: "en-IN",
speaker: "shubh",
model: "bulbul:v3"
});
const bytes = await streamResponse.bytes();
await writeFile("output.wav", bytes);
WebSocket Streaming
import asyncio
from sarvamai import AsyncSarvamAI
async def tts_stream():
client = AsyncSarvamAI()
async with client.text_to_speech_streaming.connect(model="bulbul:v3") as ws:
await ws.configure(target_language_code="hi-IN", speaker="shubh")
await ws.convert("Your text here")
await ws.flush()
async for message in ws:
pass # base64 audio chunks
asyncio.run(tts_stream())
Character Limits
| Method |
Max Text |
REST (convert) |
2,500 chars |
HTTP Stream (convert_stream) |
3,500 chars |
| WebSocket |
2,500 chars/msg (keep <500 for lowest latency; send many messages per connection) |
Gotchas
| Gotcha |
Detail |
language_code is version-gated |
Python >=0.1.29 and JS >=1.1.8 (both 2026-08-03) take language_code; earlier versions take target_language_code. Hard rename — no alias, no deprecation shim, and the JSON body key changed too, so the wrong name raises TypeError before any request goes out. Check with pip show sarvamai / npm ls sarvamai if you hit that. |
| WebSocket was not renamed |
Python ws.configure() still takes target_language_code (mapped to language_code on the wire); JS configureConnection() takes language_code. REST and WebSocket disagree inside the Python SDK. |
| JS method name |
client.textToSpeech.convert({...}) and .convertStream({...}) — camelCase. Stream returns BinaryResponse with .stream(), .bytes(), .blob(). |
pitch/loudness rejected |
SDK accepts these but API returns 400 for v3. Only pace (0.5–2.0) works. |
| v2 voices incompatible |
anushka, abhilash, arya, etc. don't work with v3. Use shubh (default). |
| Sample rate >24kHz |
32kHz, 44.1kHz, 48kHz only via REST, not streaming. |
| REST response |
Base64-encoded audio in response.audios[0]. Use sarvamai.play.save() or base64.b64decode(). |
| No SSML |
SSML markup is NOT supported. Use pace for speed control and the pronunciation dictionary for word-level fixes. |
| Use native script |
Romanized Indic input ("Aapka order confirm ho gaya hai") degrades quality. Write Indic words in native script. |
| Pronunciation dictionary |
dict_id param teaches custom word pronunciations (bulbul:v3 only; 10 dicts/user, 100 words/dict). Create via Python client.pronunciation_dictionary.create(file=f). JS SDK upload is broken (missing multipart Content-Type) — use raw fetch + FormData with an explicit Blob type. |
Full Docs
Fetch voice catalog, streaming protocol, pronunciation dictionary CRUD, and codec options from:
1---2name: text-to-speech3description: Write correct Sarvam Bulbul TTS code — REST, HTTP stream, WebSocket, pronunciation dictionaries, and v3 parameter traps (pitch/loudness, speaker compatibility). Use this skill when generating speech in an app with Python or JS/TS. For live TTS in chat via MCP, use sarvam-mcp instead.4license: Apache-2.05---67# Text-to-Speech — Bulbul89> Live in-chat TTS → [sarvam-mcp](../sarvam-mcp) (`sarvam_tools_tts_*`). This skill = **SDK code**.1011> [!IMPORTANT]12> Auth: `api-subscription-key` header — NOT `Authorization: Bearer`. Base URL: `https://api.sarvam.ai` (NOT `/v1` — that prefix is only for the OpenAI-compatible chat endpoint)13> SDK floor for the code below: Python `sarvamai>=0.1.29`, JS `sarvamai@>=1.1.8`. On older versions the TTS param is `target_language_code` — see Gotchas.1415## Model1617`bulbul:v3` — 11 languages, 37 voices (23 male, 14 female; default: `shubh`), REST/HTTP stream/WebSocket.1819## Quick Start (Python)2021```python22from sarvamai import SarvamAI23from sarvamai.play import save2425client = SarvamAI()2627response = client.text_to_speech.convert(28 text="नमस्ते, आप कैसे हैं?",29 language_code="hi-IN",30 model="bulbul:v3",31 speaker="shubh"32)33save(response, "output.wav")3435# HTTP Stream (lower latency, binary audio)36chunks = []37for chunk in client.text_to_speech.convert_stream(38 text="Hello from Sarvam AI",39 language_code="en-IN",40 speaker="shubh",41 model="bulbul:v3"42):43 chunks.append(chunk)44audio = b"".join(chunks)45```4647## Quick Start (JavaScript/TypeScript)4849```typescript50import { SarvamAIClient } from "sarvamai";51import { writeFile } from "fs/promises";5253const client = new SarvamAIClient({ apiSubscriptionKey: "YOUR_SARVAM_API_KEY" });5455// REST56const response = await client.textToSpeech.convert({57 text: "नमस्ते, आप कैसे हैं?",58 language_code: "hi-IN",59 model: "bulbul:v3",60 speaker: "shubh"61});6263// HTTP Stream (lower latency, returns BinaryResponse)64const streamResponse = await client.textToSpeech.convertStream({65 text: "Hello from Sarvam AI",66 language_code: "en-IN",67 speaker: "shubh",68 model: "bulbul:v3"69});70const bytes = await streamResponse.bytes();71await writeFile("output.wav", bytes);72```7374## WebSocket Streaming7576```python77import asyncio78from sarvamai import AsyncSarvamAI7980async def tts_stream():81 client = AsyncSarvamAI()82 async with client.text_to_speech_streaming.connect(model="bulbul:v3") as ws:83 await ws.configure(target_language_code="hi-IN", speaker="shubh")84 await ws.convert("Your text here")85 await ws.flush()86 async for message in ws:87 pass # base64 audio chunks8889asyncio.run(tts_stream())90```9192## Character Limits9394| Method | Max Text |95|--------|----------|96| **REST** (`convert`) | 2,500 chars |97| **HTTP Stream** (`convert_stream`) | 3,500 chars |98| **WebSocket** | 2,500 chars/msg (keep <500 for lowest latency; send many messages per connection) |99100## Gotchas101102| Gotcha | Detail |103|--------|--------|104| **`language_code` is version-gated** | Python `>=0.1.29` and JS `>=1.1.8` (both 2026-08-03) take `language_code`; earlier versions take `target_language_code`. Hard rename — no alias, no deprecation shim, and the JSON body key changed too, so the wrong name raises `TypeError` before any request goes out. Check with `pip show sarvamai` / `npm ls sarvamai` if you hit that. |105| **WebSocket was not renamed** | Python `ws.configure()` still takes `target_language_code` (mapped to `language_code` on the wire); JS `configureConnection()` takes `language_code`. REST and WebSocket disagree inside the Python SDK. |106| **JS method name** | `client.textToSpeech.convert({...})` and `.convertStream({...})` — camelCase. Stream returns `BinaryResponse` with `.stream()`, `.bytes()`, `.blob()`. |107| **`pitch`/`loudness` rejected** | SDK accepts these but API returns 400 for v3. Only `pace` (0.5–2.0) works. |108| **v2 voices incompatible** | `anushka`, `abhilash`, `arya`, etc. don't work with v3. Use `shubh` (default). |109| **Sample rate >24kHz** | 32kHz, 44.1kHz, 48kHz only via REST, not streaming. |110| **REST response** | Base64-encoded audio in `response.audios[0]`. Use `sarvamai.play.save()` or `base64.b64decode()`. |111| **No SSML** | SSML markup is NOT supported. Use `pace` for speed control and the pronunciation dictionary for word-level fixes. |112| **Use native script** | Romanized Indic input ("Aapka order confirm ho gaya hai") degrades quality. Write Indic words in native script. |113| **Pronunciation dictionary** | `dict_id` param teaches custom word pronunciations (bulbul:v3 only; 10 dicts/user, 100 words/dict). Create via Python `client.pronunciation_dictionary.create(file=f)`. JS SDK upload is broken (missing multipart `Content-Type`) — use raw `fetch` + `FormData` with an explicit `Blob` type. |114115## Full Docs116117Fetch voice catalog, streaming protocol, pronunciation dictionary CRUD, and codec options from:118119- **https://docs.sarvam.ai/llms.txt** — comprehensive docs index120- [TTS Overview](https://docs.sarvam.ai/api/api-guides-tutorials/text-to-speech/overview)121- [Voice Catalog](https://docs.sarvam.ai/api/api-guides-tutorials/text-to-speech/how-to/change-the-speaker-voice)122- [HTTP Stream](https://docs.sarvam.ai/api/api-guides-tutorials/text-to-speech/streaming-api/http-stream)123- [Pronunciation Dictionary](https://docs.sarvam.ai/api/api-guides-tutorials/text-to-speech/pronunciation-dictionary)124- [Rate Limits](https://docs.sarvam.ai/api/ratelimits)