Text to speech API
Hindi that sounds like a person said it.
One POST sends text and a voice name, and audio bytes come straight back — no job id, no polling. bulbul:v3 covers 11 Indian language codes with 37 voices, and eight more models sit behind the same endpoint when you need another accent or another language.
- Endpoint
- POST /v1/audio/speech
- Auth
- Bearer cm_ key
- bulbul:v3 voices
- 37
- Per 10,000 characters
- $0.3125
Build a request that the model will accept
Every model takes a different set of controls. Pick one below and the form offers only the fields the docs say it accepts, then writes the curl for you — so the command you paste into a terminal is valid before you run it.
curl -X POST https://api.callmissed.com/v1/audio/speech \
-H "Authorization: Bearer cm_your_key" \
-H "Content-Type: application/json" \
-d '{
"model": "bulbul:v3",
"input": "Namaste, kaise hain aap?",
"voice": "shubh",
"language": "hi-IN",
"response_format": "mp3"
}' \
--output speech.mp3The response is the file
A successful call returns raw audio in the format you asked for, so you write the body to disk or pipe it to a player. There is no JSON envelope to unwrap and nothing to poll. These samples use plain HTTP; the OpenAI speech shape means client.audio.speech.create() works too once base_url points at https://api.callmissed.com/v1.
// Node 18+ — no SDK, just fetch
import { writeFile } from "node:fs/promises";
const res = await fetch("https://api.callmissed.com/v1/audio/speech", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.CALLMISSED_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "bulbul:v3",
voice: "shubh",
language: "hi-IN",
input: "Namaste, kaise hain aap?",
response_format: "mp3",
}),
});
if (!res.ok) throw new Error("TTS failed: " + res.status);
await writeFile("speech.mp3", Buffer.from(await res.arrayBuffer()));# Python 3.9+ — plain HTTP, no SDK
import os, requests
r = requests.post(
"https://api.callmissed.com/v1/audio/speech",
headers={"Authorization": f"Bearer {os.environ['CALLMISSED_API_KEY']}"},
json={
"model": "bulbul:v3",
"voice": "shubh",
"language": "hi-IN",
"input": "Namaste, kaise hain aap?",
"response_format": "mp3",
},
timeout=60,
)
r.raise_for_status()
open("speech.mp3", "wb").write(r.content)Streaming, where it exists
Set stream to true on deepgram-aura-2 or deepgram-aura-1 and audio arrives as a chunked HTTP response. Streaming carries raw encodings only — linear16, mulaw or alaw. Ask for mp3, opus, aac or flac with stream true and you get the full audio in one buffered response instead. Other models ignore the field.
curl -N -X POST https://api.callmissed.com/v1/audio/speech \
-H "Authorization: Bearer cm_your_key" \
-H "Content-Type: application/json" \
-d '{"model": "deepgram-aura-2", "voice": "thalia", "input": "Streaming hello.", "response_format": "linear16", "stream": true}' \
--output speech.raw- response_format
- mp3opusaacflacwavpcm
- speech_sample_rate
- 800016000220502400048000
- 8000 Hz comes out ready for a phone line, so telephony audio needs no resampling on your side.
Every voice name the reference publishes
Every bulbul:v3 voice speaks every one of its 11 language codes, so the voice name and the language field are independent choices. A voice name the model does not recognise falls back to its default rather than failing the request — pick a model below and click a name to copy it.
37 voices. Each speaks all 11 language codes — voice and language are independent fields.
73 voices across English, Hindi and other Indian languages.
The 16 featured handles. The live public library behind them is bigger and paginated — query it rather than hardcoding names.
Six voices; the docs name no default.
90 voices across seven languages — the reference names only the default.
12 legacy English voices — the reference names only the default.
40 English voices — the reference names only the default.
10 Spanish voices — none are named in the reference.
The reference names no voices for melotts.
The reference publishes no gender or per-language labels for the bulbul and gnani rosters, so none are claimed here.
bulbul:v3 language codes
- hi-IN
- bn-IN
- ta-IN
- te-IN
- mr-IN
- gu-IN
- kn-IN
- ml-IN
- pa-IN
- od-IN
- en-IN
The sonic-3.6 library is bigger than any list
sonic-3.6 has 16 featured handles and a public library behind them, and it takes base codes like en and hi rather than hi-IN. Because the library is paginated and changes, ask the API for it instead of hardcoding names. riya, arushi, siya, parvati and kabir are native Hindi speakers — pair them with "language": "hi".
- q
- Free-text search over the voice library.
- language
- Filter to one base language code.
- gender
- masculine, feminine or gender_neutral.
- limit
- Page size, 1 to 100.
- starting_after
- Cursor for the next page.
# Search the sonic-3.6 voice library
curl "https://api.callmissed.com/v1/audio/voices?model=sonic-3.6&q=hindi&limit=50" \
-H "Authorization: Bearer cm_your_key"If the upstream library is briefly unreachable the first page still answers 200 with the featured handles and an extra "degraded": true field, so a voice picker keeps rendering and every voice it returns stays usable. Voice reference
Four ways to change how it lands
Text written for the eye reads badly out loud. These are the fields that fix it, and the models each one applies to — sending a control to a model that does not take it is the most common reason synthetic speech sounds wrong.
humanize
Every model. Default true.
Shapes your text for natural speech before synthesis: markdown and emoji are stripped, URLs and emails are spoken as words, long digit runs are grouped into readable chunks. Set it to false to synthesize your text byte for byte.
https://callmissed.com → "callmissed dot com"
temperature
bulbul:v3 only. 0.01 to 2.0, default 0.9.
The expressiveness dial. The default sits warmer than the model's own flat setting, so read-aloud copy already has some colour in it. Lower it for numbers and confirmations, raise it for conversational lines.
"temperature": 0.4 → flatter, steadier delivery
instructions
gpt-4o-mini-tts only. Up to 2,000 characters.
A plain-English brief for the delivery rather than a numeric dial. It describes how the line should land — pace, warmth, attitude — alongside the text you want spoken.
"instructions": "Calm, slow, reassuring."
Pause cues
deepgram-aura-2, deepgram-aura-1, aura-2-en, aura-2-es.
These four read punctuation you write into the text: an ellipsis gives a longer natural pause, a comma or period a short one, and um or uh render as natural hesitation. bulbul:v3 and gnani-timbre-v2.0 do not support pause markup or SSML — they would speak the dots aloud.
One moment... let me check that.
Nine models, one endpoint, one model field
Switching model is a one-word change. Prices are read from the same table billing uses, so this cannot drift. 4 of the 9 TTS models are on the free tier.
| Model id | Voices | Languages | speed | Expressiveness | Streams | Per 10K chars |
|---|---|---|---|---|---|---|
| bulbul:v3Free tier | 37 | 11 Indian languages | 0.5–2 | temperature 0.01–2.0 | No | $0.3125 |
| sonic-3.6 | 16 featured + public library | 44, incl. Hindi and Hinglish | 0.6–1.5 | — | No | $0.5208 |
| gnani-timbre-v2.0 | 73 | English, Hindi, other Indian | 0.85–1.15 | — | No | $0.2813 |
| gpt-4o-mini-tts | 6 | — | 0.25–4 | instructions, up to 2000 chars | No | $0.2083 |
| deepgram-aura-2 | 90 | en, es, de, fr, nl, it, ja | 0.7–1.5 | pause cues in the text | Yes | $0.3125 |
| deepgram-aura-1 | 12 | English | 0.7–1.5 | pause cues in the text | Yes | $0.1563 |
| aura-2-enFree tier | 40 | English | — | pause cues in the text | No | $0.4167 |
| aura-2-esFree tier | 10 | Spanish | — | pause cues in the text | No | $0.4167 |
| melottsFree tier | — | — | — | — | No | $0.05208 |
Browse the full model catalogue · See plans and credits · Go the other way with speech to text
Text to speech API questions
Which Indian languages does the text to speech API support?
bulbul:v3 speaks 11 Indian language codes: hi-IN, bn-IN, ta-IN, te-IN, mr-IN, gu-IN, kn-IN, ml-IN, pa-IN, od-IN and en-IN, with 37 voices. sonic-3.6 covers 44 languages including Hindi and Hinglish, and gnani-timbre-v2.0 has 73 voices across English, Hindi and other Indian languages.
How much does text to speech cost?
bulbul:v3 is $0.3125 per 10,000 characters. 4 of the 9 TTS models are on the free tier. Streaming is billed the same as non-streaming, per character. The model matrix on this page lists every model's price from the same table billing uses.
What audio formats can I get back?
Set response_format to mp3, opus, aac, flac, wav or pcm. speech_sample_rate accepts 8000, 16000, 22050, 24000 or 48000 Hz, so 8 kHz telephony audio needs no resampling on your side.
Can I stream audio as it is generated?
On deepgram-aura-2 and deepgram-aura-1, set stream to true and audio arrives as a chunked HTTP response. Streaming supports raw encodings only: linear16 (or pcm/wav), mulaw or alaw. If you ask for mp3, opus, aac or flac with stream true, the full audio is returned in one buffered response instead. Other models ignore stream.
How do I make the voice sound less robotic?
In order of impact: write text that reads like speech and leave humanize on (the default), which strips markdown and emoji and speaks URLs and long digit runs naturally. Then use the model's expressiveness control: temperature on bulbul:v3, instructions on gpt-4o-mini-tts. On the Aura models, write pauses into the text with an ellipsis. bulbul:v3 and gnani-timbre-v2.0 do not support pause markup or SSML.
Does the OpenAI SDK work?
Yes. The endpoint follows the OpenAI speech shape, so client.audio.speech.create() works once base_url is https://api.callmissed.com/v1 and the key is a cm_ API key. Plain HTTP works too, as the examples on this page show.




