Natural voices,
one simple key.
Turn text into natural speech from your apps, bots and AI agents. Send text, get back a studio-quality MP3 with matching subtitles. One request, any programming language.
Let your AI assistant make the voices
Copy the full API guide (with your key) and paste it into any AI chat. It will understand the API and can write the code or call it for you. Agents can also read it directly from the links below.
Quick start
You can make your first request in under a minute.
Buy tokens ($5 = 2M) or get one from your admin. It starts with sk_.
Browse the Voice Library or call GET /api/v1/voices.
POST to /api/v1/tts and save the MP3 you get back.
curl -X POST {{BASE}}/api/v1/tts \
-H "Authorization: Bearer {{KEY}}" \
-H "Content-Type: application/json" \
-d '{"text": "Hello class, welcome to the lesson.", "voice": "{{V:default}}"}' \
--output speech.mp3
import requests
res = requests.post(
"{{BASE}}/api/v1/tts",
headers={"Authorization": "Bearer {{KEY}}"},
json={"text": "Hello class, welcome to today's lesson.", "voice": "{{V:default}}"},
)
res.raise_for_status()
open("speech.mp3", "wb").write(res.content)
print("Saved", res.headers["X-Audio-Duration"], "seconds")
const res = await fetch("{{BASE}}/api/v1/tts", {
method: "POST",
headers: { "Authorization": "Bearer {{KEY}}", "Content-Type": "application/json" },
body: JSON.stringify({ text: "Hello class, welcome to today's lesson.", voice: "{{V:default}}" }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const audio = new Audio(URL.createObjectURL(await res.blob()));
audio.play();
Authentication
Send your key in the Authorization header as a bearer token. The X-API-Key header works too.
Authorization: Bearer {{KEY}}Text to speech
Converts text to speech. Long text is split and voiced in parallel automatically, so even a full chapter comes back quickly.
| Field | Description |
|---|---|
textrequiredstring | The words to speak. Supports [pause 2s] tags. |
voicestring | A voice id such as {{V:urdu_female}}. Defaults to {{V:default}}. |
speednumber · 0.5–2.0 | Playback speed multiplier. 1.0 is normal. You can send rate as a percent (-50 to 100) instead. |
pitchnumber · -50–50 | Pitch shift in Hz. Negative is deeper. |
volumenumber · -100–100 | Volume change in percent. |
emotionstring | "auto" gives every sentence its own feeling from its meaning, or set one emotion such as "happy". See Emotions & styles. |
stylestring | Speaking style, e.g. "storytelling", "news", "cinematic". |
intensitynumber · 0–2 | How strong the emotion is. 1 is normal. |
format"mp3" | "json" | mp3 (default) returns the audio bytes directly. json returns links to the MP3 and SRT files plus timed subtitle cues. |
MP3 response headers
X-Audio-Duration | Length of the audio in seconds. |
X-Characters | Tokens used by this request (1 token = 1 character). |
X-Tokens-Remaining | Tokens left on your key, or unlimited. |
X-Audio-Id | Use it to fetch the subtitles: GET /files/<id>.srt. |
JSON response
{
"id": "q1Xb3mXr0P2kZ8aB",
"audio_url": "{{BASE}}/files/q1Xb3mXr0P2kZ8aB.mp3",
"srt_url": "{{BASE}}/files/q1Xb3mXr0P2kZ8aB.srt",
"duration": 6.74,
"characters": 68,
"voice": "{{V:male}}",
"cues": [[0.05, 1.21, "Hello students."], [2.93, 5.32, "This is a test."]],
"quota": { "tokens": 1999932, "unlimited": false, "used_today": 68 }
}Files made with your key are kept in your library for 7 days: list them again with GET /api/v1/library (same Authorization header) and download any audio_url. Delete one with POST /api/v1/library/ID/delete.
Emotions & styles
Add emotion and/or style to any request (text, dialogue or SRT). With "emotion": "auto" each sentence is read with the feeling that fits its words and punctuation, and the mood fades naturally between sentences instead of staying flat.
emotion | auto, neutral, happy, sad, angry, excited, fearful, suspenseful, curious, surprised, shocked, confident, empathetic, sarcastic, romantic, hopeful, frustrated, nervous, calm, soothing, mysterious, inspirational |
style | natural, conversational, cinematic, documentary, storytelling, audiobook, podcast, news, motivational, comedic, whispering, shouting, asmr |
intensity | 0 to 2, default 1. |
dramatic_pauses | Default true: longer pauses at ... and dashes. |
pitch_variation | Default true: small pitch movement between sentences, so long reads don't sound robotic. |
Tags inside the text
Put a tag before a sentence to control it. It lasts until the next tag or the end of the paragraph.
- Emotion:
[happy],[sad],[angry],[mysterious]… (any emotion above),[neutral]to reset. - Delivery:
[whisper],[shout]. - Sounds:
[laugh],[chuckle],[sigh],[gasp],[breath],[cry]. These are short spoken cues ("Haha!", "Hmm…", "Oh!") or pauses, not recorded laughter or crying. - Emphasis: wrap a word in
**double stars**to stress it. - Pause:
[pause 2s]or[pause 500ms].
{
"text": "It was a quiet night... Suddenly, the door creaked open! [gasp] Who is there? [whisper] Don't make a sound.",
"voice": "{{V:male}}",
"style": "storytelling",
"emotion": "auto",
"intensity": 1.2
}
import requests
res = requests.post("{{BASE}}/api/v1/tts",
headers={"Authorization": "Bearer {{KEY}}"},
json={"text": "I can't believe we **finally** did it! [laugh] Thank you, all of you.",
"voice": "{{V:default}}", "style": "podcast", "emotion": "auto"})
open("story.mp3", "wb").write(res.content)
The full list is also available as JSON at GET /api/v1/expressions.
Voices
Lists every available voice. The list is kept up to date automatically, so new voices appear on their own. No key needed.
| Query | Description |
|---|---|
lang | Language code, e.g. ur, en, ar. |
locale | Locale prefix, e.g. en-GB. |
gender | Female or Male. |
multilingual | true for voices that can speak many languages. |
q | Free-text search over name, language and country. |
curl "{{BASE}}/api/v1/voices?lang=ur"
{
"count": 4,
"voices": [
{ "id": "{{V:urdu_female}}", "name": "{{N:urdu_female}}", "locale": "ur-PK", "lang": "ur",
"language": "Urdu", "region": "Pakistan", "gender": "Female",
"multilingual": false, "personalities": ["Friendly", "Positive"] }
]
}
Hear any voice instantly with GET /api/v1/voices/<id>/preview. It returns a short MP3 sample.
Dialogue
Create a conversation with a different voice for every line. Great for language-learning role plays, podcasts and stories.
linesrequiredarray | A list of {"voice": "...", "text": "..."} objects, in order. |
gapnumber | Seconds of silence between lines (default 0.35). |
speed, pitch, volume, format | Same as text to speech. |
import requests
lines = [
{"voice": "{{V:urdu_male}}", "text": "السلام علیکم! آج ہم کیا پڑھیں گے؟"},
{"voice": "{{V:urdu_female}}", "text": "آج ہم سائنس کا سبق پڑھیں گے۔"},
]
res = requests.post("{{BASE}}/api/v1/dialogue",
headers={"Authorization": "Bearer {{KEY}}"},
json={"lines": lines, "gap": 0.4})
open("dialogue.mp3", "wb").write(res.content)
curl -X POST {{BASE}}/api/v1/dialogue \
-H "Authorization: Bearer {{KEY}}" -H "Content-Type: application/json" \
-d '{"lines": [
{"voice": "{{V:male}}", "text": "Ready for the quiz?"},
{"voice": "{{V:default}}", "text": "Absolutely. Ask me anything!"}
]}' --output dialogue.mp3
SRT dubbing
Send a subtitle file and get back one audio track where every line starts exactly at its timestamp. Perfect for dubbing videos into another language.
srtrequiredstring | The full contents of an .srt file. |
voice | The voice to use for every line. |
auto_fitboolean | Default true. Speeds up any line that would run into the next one. |
speed, pitch, volume, format | Same as text to speech. |
import requests
srt = open("video.srt", encoding="utf-8").read()
res = requests.post("{{BASE}}/api/v1/srt",
headers={"Authorization": "Bearer {{KEY}}"},
json={"srt": srt, "voice": "{{V:hindi_female}}", "auto_fit": True})
open("dub.mp3", "wb").write(res.content)OpenAI compatible
Already using OpenAI's text-to-speech? Point the official SDK at this server and it just works. Use any voice id from this site, or the OpenAI names alloy, echo, fable, onyx, nova, shimmer, ash, coral, sage.
from openai import OpenAI
client = OpenAI(api_key="{{KEY}}", base_url="{{BASE}}/v1")
with client.audio.speech.with_streaming_response.create(
model="tts-1",
voice="{{V:urdu_female}}", # or "nova", "alloy", ...
input="یہ آواز OpenAI SDK سے بنائی گئی ہے۔",
) as response:
response.stream_to_file("speech.mp3")
import OpenAI from "openai";
import fs from "node:fs";
const client = new OpenAI({ apiKey: "{{KEY}}", baseURL: "{{BASE}}/v1" });
const mp3 = await client.audio.speech.create({ model: "tts-1", voice: "nova", input: "Hello from Node!" });
fs.writeFileSync("speech.mp3", Buffer.from(await mp3.arrayBuffer()));
Tokens & balance
1 token = 1 character of text you send. Pause tags, extra spaces, markdown symbols and emoji are not counted. $5 buys 2,000,000 tokens, which never expire. Buy or top up here.
{
"kind": "key",
"name": "Ayesha Khan",
"quota": {
"tokens": 1996880, "unlimited": false, "used_today": 3120,
"max_request_chars": 100000, "expires_at": null
}
}Every request needs your API key. Your balance drops only when audio is generated successfully.
Pauses & tips
- Pauses: add
[pause](1 second),[pause 2s]or[pause 500ms]anywhere in your text. - Paragraphs: sentence breaks and new lines already add natural pauses.
- Most natural voices have ids ending in
-multi(for example{{V:default}},{{V:male}}). They can also read many languages in one script. - Urdu & Hindi: use
{{V:urdu_female}},{{V:urdu_male}},{{V:hindi_female}}or{{V:hindi_male}}for the most natural accent. - AI-written scripts: markdown (
#,**, bullet points) and emoji are cleaned up automatically, and headings get a natural pause. - Big jobs: send up to your per-request limit in one go. The server splits it and voices the pieces in parallel.
Errors
Errors come back as JSON with a stable code you can check in your program:
{ "error": { "code": "insufficient_tokens", "message": "Not enough tokens: this needs 5,200 but the key has 1,040 left." } }| Status | Code & meaning |
|---|---|
400 | empty_text, invalid_voice, invalid_param, invalid_srt, invalid_json: check your request body. |
401 | invalid_key or key_required: the key is missing or wrong. |
402 | insufficient_tokens: top up your key on the pricing page. |
403 | key_disabled or key_expired: contact support. |
413 | text_too_long: split the text into smaller requests. |
429 | quota_exceeded: the key's daily limit is used up. Try again tomorrow or ask the admin. |
502 | tts_failed: a temporary hiccup. Retry after a few seconds. |
Playground
Try the API right here. This sends a real request with the key you enter.