ConvoZen Voice SDK
Text-to-Speech (TTS) and Speech-to-Text (STT) for Python.
One API key for both TTS and STT.
Install
pip install convozen
Requires Python 3.9+
TTS — Convert Text to Speech
Copy and run:
import convozen
client = convozen.Client(api_key="your-api-key")
audio = client.tts.synthesize("नमस्ते… मुझे आपके सर्विस के बारे में थोड़ी जानकारी चाहिए।")
# language omitted → server auto-detects Devanagari as Hindi
with open("output.wav", "wb") as f:
f.write(audio)
With options:
import convozen
client = convozen.Client(api_key="your-api-key")
audio = client.tts.synthesize(
text="Welcome to Playground.",
language="en", # optional — omit for native-script auto-detect
voice="roohi", # default: "roohi"
model="ragini-v1", # default: "ragini-v1"
speed=1.2, # default: 1.0
enable_transliteration=False, # default: False — opt-in Roman→native
is_roman_input=False, # default: False — Latin as Indic romanization
)
with open("output.wav", "wb") as f:
f.write(audio)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
text |
str |
required | Text to convert to speech |
language |
str | None |
None |
Language code. Optional — omit for native Indic script auto-detect on the server |
voice |
str |
"roohi" |
Voice ID for synthesis (see available voices below) |
model |
str |
"ragini-v1" |
TTS model: "ragini-v1" (Indian languages), "ragini-v2" (Ragini 2.0, higher fidelity), "ragini-lite" / "ragini_lite" (lightweight Hindi), or "rawi-v1" (Arabic) |
speed |
float |
1.0 |
Speech speed. 0.5 = half speed, 2.0 = double speed. Not supported on ragini-v2 or ragini_lite — accepted but silently ignored. |
sample_rate |
int |
model-dependent | Output sample rate in Hz. Defaults to 48000 for ragini-v2, 22050 for ragini_lite, 24000 for other models |
stream |
bool |
False |
If True, returns audio chunks as they are generated |
format |
str |
"wav" |
Audio output format. Currently only wav is supported |
enable_transliteration |
bool |
False |
Opt-in Roman→native transliteration for romanized Indic (adds latency) |
is_roman_input |
bool |
False |
Treat Latin text as Indic romanization; pass a language hint when transliteration is off |
Language auto-detect & romanized Indic
# Native script — no language needed
audio = client.tts.synthesize("नमस्ते आप कैसे हैं")
# Romanized Hindi — high quality (transliterate first)
audio = client.tts.synthesize(
"namaste kaise ho",
language="hi",
enable_transliteration=True,
)
# Romanized Hindi — faster path (keep Latin, hint language for phonemize)
audio = client.tts.synthesize(
"namaste kaise ho",
language="hi",
is_roman_input=True,
)
Streaming
import convozen
client = convozen.Client(api_key="your-api-key")
for chunk in client.tts.synthesize("Long text here...", language="en", stream=True):
audio_player.write(chunk)
Available Voices
These are the ragini-v1 voices. ragini-v2 has different voice sets — see
Ragini 2.0 → Voices; ragini_lite has a single voice, and rawi-v1
has its own. Passing a voice to a model that does not have it is not an error:
the name is forwarded as-is and you get an arbitrary voice back with a 200.
| Voice | ID | Default |
|---|---|---|
| Roohi | roohi |
Yes |
| Amaya | amaya |
|
| Kiyansh | kiyansh |
|
| Neeraj | neeraj |
|
| Manya | manya |
|
| Nidhi | nidhi |
|
| Ira | ira |
|
| Trisha | trisha |
|
| Charvi | charvi |
audio = client.tts.synthesize("Hello", language="en", voice="roohi")
Choosing a TTS model
| Model | Languages | Rate | Speed control | Use it for |
|---|---|---|---|---|
ragini-v1 (default) |
en, hi, kn, ta, te | 24 kHz | Yes | The general default |
ragini-v2 |
en, hi, kn, ta, te | 48 kHz | Not yet | Highest fidelity |
ragini_lite |
hi, en | 22.05 kHz | No | Smallest and fastest |
rawi-v1 |
Arabic + 6 dialects | 24 kHz | Yes | Arabic |
ragini-v1 and ragini-v2 cover the same languages, so switching is a one-word
change — but the voices differ, so change voice alongside model.
Ragini 2.0
ragini-v2 is the second-generation Ragini backend (VoxCPM2), serving the same
languages as ragini-v1 at 48 kHz instead of 24 kHz.
audio = client.tts.synthesize(
"नमस्ते, आप कैसे हैं?",
language="hi",
model="ragini-v2",
voice="sherin",
) # note: `speed` is not supported on ragini-v2 yet — see below
"ragini-v2", "ragini_v2", "ragini-2" and "ragini-2.0" are all accepted —
the SDK normalizes them to the same model.
Supported parameters
| Parameter | Value for ragini-v2 |
Notes |
|---|---|---|
text |
any non-empty string | Required |
language |
"en", "hi", "kn", "ta", "te" |
Anything else is rejected — see below |
voice |
see the voice table below | Voice sets differ per language group |
sample_rate |
int, defaults to 48000 |
The pods always return 48 kHz; see below |
stream |
True / False |
Chunked streaming supported |
format |
"wav" |
Only wav is supported |
Not supported / no effect
| Parameter | Behavior on ragini-v2 |
|---|---|
speed |
Not supported yet. The request is accepted and the audio comes back at normal pace — you get no error, so do not rely on it. Use ragini-v1 if you need speed control today. |
sample_rate |
Output is always 48 kHz. Passing another value does not resample; resample client-side if you need a different rate. |
Voices
Ragini 2.0 runs two checkpoints with different voice sets. A voice from the
wrong group does not error — the pod passes an unknown name through as a raw
speaker tag, so a typo returns an arbitrary voice rather than a 400.
| Languages | Voices | Default |
|---|---|---|
en, hi |
shrajay, siddharth, parmi, sherin, bhavya, shreya |
shrajay |
kn, ta, te |
amaya, parmi, roohi, bhavya/bhagya, shrajay, siddharth |
amaya |
Unsupported languages are rejected
Unlike ragini-v1, a language with no Ragini 2.0 deployment returns a 400
rather than falling back to another checkpoint — sending Marathi to the en/hi
model would otherwise produce plausible-sounding but wrong audio.
client.tts.synthesize("...", language="mr", model="ragini-v2")
# APIError: 400 - ragini-v2 is not available for language 'mr'.
# Supported languages: ['en', 'hi', 'kn', 'ta', 'te']
If the Ragini 2.0 pods are unreachable, the request fails with a 502. It never
silently falls back to ragini-v1.
Ragini Lite
ragini_lite is the lightweight Ragini backend — a Piper/VITS Hindi voice served
at 22.05 kHz. Use it when you want a smaller, faster Hindi voice.
audio = client.tts.synthesize(
"नमस्ते, आप कैसे हैं?",
language="hi",
model="ragini-lite", # "ragini-lite" and "ragini_lite" both work
)
Both "ragini-lite" (hyphen, matching ragini-v1) and "ragini_lite" (underscore)
are accepted — the SDK normalizes them to the same model.
Supported parameters
| Parameter | Value for ragini_lite |
Notes |
|---|---|---|
text |
any non-empty string | Required |
model |
"ragini-lite" or "ragini_lite" |
Both forms accepted |
language |
"hi", "en" |
Hindi voice |
sample_rate |
int, defaults to 22050 |
When omitted, the SDK sends 22050 (vs 24000 for other models) |
stream |
True / False |
Chunked streaming supported |
format |
"wav" |
Only wav is supported |
Not supported / no effect
| Parameter | Behavior on ragini_lite |
|---|---|
voice |
Single voice only. Defaults to roohi; any other voice ID falls back to roohi. |
speed |
Not applied. Accepted for API compatibility but has no effect on the output. |
Rawi — Arabic TTS
Use model="rawi-v1" for Arabic dialect synthesis:
import convozen
client = convozen.Client(api_key="your-api-key")
audio = client.tts.synthesize(
text="Welcome to Playground.",
language="jo", # Jordanian dialect
voice="hadi",
model="rawi-v1",
speed=1.2,
)
with open("output.wav", "wb") as f:
f.write(audio)
Rawi languages: en, ar, ae, eg, jo, ms, hz, nj
Rawi voices: ahmad, hadi, karim, mohammad, rafoush, ghaida, hana, heba, layan, salma
STT — Convert Speech to Text
Copy and run:
import convozen
client = convozen.Client(api_key="your-api-key")
result = client.stt.transcribe("recording.wav")
print(result.text) # "hello how can I help you"
print(result.score) # -2.45 (confidence — closer to 0 is better)
By default this transcribes mono, single-speaker audio and returns an STTResponse.
Alif — Arabic STT
Use model="alif-v1" for Arabic speech recognition:
import convozen
client = convozen.Client(api_key="your-api-key")
result = client.stt.transcribe(
"arabic_recording.wav",
model="alif-v1",
)
print(result.text) # Arabic transcript
print(result.score) # confidence — closer to 0 is better
Notes for alif-v1:
- Optimized for Arabic audio (all dialects supported by the model)
- Plain transcription only — use
akshara-profor diarization, stereo, or Indian-language STT lang_tagsare not required (the model does not use language-penalty hints)
Word-level timestamps
Timestamps are opt-in — pass word_time_stamps=True:
result = client.stt.transcribe("recording.wav", word_time_stamps=True)
for w in result.word_timestamps:
print(f"{w.word} {w.start_s:.2f}s – {w.end_s:.2f}s")
Works on both the transcript and diarization paths (per-turn timestamps on DiarizeTurn.word_timestamps).
Speaker Diarization (mono)
For a mono recording with multiple speakers, set diarize=True to get a per-speaker breakdown (DiarizeResponse):
result = client.stt.transcribe("call.wav", diarize=True)
for turn in result.turns:
print(f"[{turn.speaker_id}] {turn.start_sec:.1f}s – {turn.end_sec:.1f}s")
print(f" {turn.transcript}")
# [s0] 0.0s – 3.2s
# Hello, this is support. How can I help you?
# [s1] 4.0s – 7.8s
# Hi, I'd like to reschedule my appointment.
If you know how many speakers to expect, pass n_speakers (an integer 2–20) as a hint. Leave it as None (default) to auto-detect:
result = client.stt.transcribe("interview.wav", diarize=True, n_speakers=2)
Response fields (DiarizeResponse):
| Field | Type | Description |
|---|---|---|
turns |
list[DiarizeTurn] |
Speaker turns in order |
num_speakers |
int |
Number of distinct speakers detected |
total_duration_sec |
float |
Total audio duration in seconds |
Each DiarizeTurn has speaker_id, start_sec, end_sec, transcript, and word_timestamps (populated when word_time_stamps=True).
Stereo audio
If each speaker is on a separate channel (e.g. a 2-channel call recording), set audio_channels="stereo". Each channel is treated as one speaker, and you get a DiarizeResponse:
result = client.stt.transcribe("stereo_call.wav", audio_channels="stereo")
For stereo, keep
diarize=False(the channels already are the speakers) and leaven_speakers=None. The declaredaudio_channelsmust match the file — a mono file declared"stereo"(or vice-versa) is rejected with a400.
Denoising
For noisy multi-speaker audio, enable denoise=True. It applies on the diarization path (diarize=True or audio_channels="stereo"):
result = client.stt.transcribe("noisy_call.wav", diarize=True, denoise=True)
Specify Languages
If you know which languages are spoken in the audio, pass them in lang_tags. This improves accuracy — especially for multilingual audio like Hindi + English call recordings:
result = client.stt.transcribe(
"recording.wav",
lang_tags=["hi", "en"],
)
print(result.text) # "हां मुझे appointment reschedule करना है"
Keyword Boosting
Have domain-specific words the model keeps getting wrong? Pass them in keywords and the model will bias towards recognizing them:
# Without keyword boosting: "can you tell me about conversion"
# With keyword boosting: "can you tell me about convozen"
result = client.stt.transcribe(
"recording.wav",
keywords=["convozen", "akshara"],
)
Choose a Model
Use model to select which ASR model processes the audio:
result = client.stt.transcribe("recording.wav", model="akshara-pro") # default
result = client.stt.transcribe("recording.wav", model="akshara-base")
result = client.stt.transcribe("arabic.wav", model="alif-v1") # Arabic STT
| Model | Best for |
|---|---|
akshara-pro |
Maximum accuracy for Indian languages (default) |
akshara-base |
Lighter / faster Indian-language STT |
alif-v1 |
Arabic speech-to-text |
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
audio |
str | os.PathLike |
required | Path to the audio file |
model |
str |
"akshara-pro" |
ASR model: "akshara-pro", "akshara-base", or "alif-v1" (Arabic) |
audio_channels |
str |
"mono" |
"mono" or "stereo". Must match the actual file |
diarize |
bool |
False |
True to return per-speaker turns (mono only) |
n_speakers |
int |
None |
Expected speaker count (2–20) for mono diarization. None → auto-detect |
denoise |
bool |
False |
Apply denoising on the diarization path — helps on noisy audio |
lang_tags |
list[str] |
None |
Languages spoken in the audio — improves accuracy when known |
word_time_stamps |
bool |
False |
True to return per-word timestamps |
blank_penalty |
float |
None |
Penalizes silence/blank tokens; increase to reduce empty gaps. None → derived from lang_tags |
keywords |
list[str] |
None |
Domain words the model should bias towards recognizing |
When to use what
audio_channels |
diarize |
n_speakers |
Result |
|---|---|---|---|
"mono" |
False |
None |
Plain transcript → STTResponse |
"mono" |
True |
None |
Auto speaker count → DiarizeResponse |
"mono" |
True |
2–4 |
Hinted speaker count → DiarizeResponse |
"stereo" |
False |
None |
Channel-split diarization → DiarizeResponse |
Anything outside these rows raises a ValueError (e.g. n_speakers without diarize, n_speakers outside 2–20, diarize=True with "stereo").
Supported Languages
| Code | Language | TTS | STT |
|---|---|---|---|
en |
English | Yes (ragini-v1, ragini-v2) |
Yes |
hi |
Hindi | Yes (ragini-v1, ragini-v2, ragini_lite) |
Yes |
ta |
Tamil | Yes (ragini-v1, ragini-v2) |
Yes |
te |
Telugu | Yes (ragini-v1, ragini-v2) |
Yes |
kn |
Kannada | Yes (ragini-v1, ragini-v2) |
Yes |
ar |
Arabic | Yes (rawi-v1) |
Yes (alif-v1) |
ae |
Emirati Arabic | Yes (rawi-v1) |
Yes (alif-v1) |
eg |
Egyptian Arabic | Yes (rawi-v1) |
Yes (alif-v1) |
jo |
Jordanian Arabic | Yes (rawi-v1) |
Yes (alif-v1) |
ms |
Modern Standard Arabic | Yes (rawi-v1) |
Yes (alif-v1) |
hz |
Hijazi Arabic | Yes (rawi-v1) |
Yes (alif-v1) |
nj |
Najdi Arabic | Yes (rawi-v1) |
Yes (alif-v1) |
mr |
Marathi | — | Yes |
bn |
Bengali | — | Yes |
gu |
Gujarati | — | Yes |
ml |
Malayalam | — | Yes |
Check Credits
import convozen
client = convozen.Client(api_key="your-api-key")
info = client.account.info()
print(info.global_balance) # credits remaining (shared across TTS and STT)
print(info.plan) # e.g. "pro"
print(info.allowed_services) # e.g. ["tts", "stt"]
print(info.tts_lifetime_used) # credits ever spent on TTS
print(info.stt_lifetime_used) # credits ever spent on STT
print(info.rate_limit.limit) # requests allowed per window
print(info.rate_limit.remaining) # requests left in the current window
print(info.rate_limit.reset_in_seconds) # seconds until the window resets
print(info.rate_limit.window_seconds) # length of the window
AccountInfo
| Field | Type | Description |
|---|---|---|
account_id |
str |
Account identifier |
org_id |
str |
Organization identifier |
plan |
str |
Plan tier (e.g. "pro", "free") |
global_balance |
int |
Credits remaining, shared across TTS and STT |
allowed_services |
List[str] |
Services enabled for the key, e.g. ["tts", "stt"] |
tts_lifetime_used |
int |
Credits ever spent on TTS |
stt_lifetime_used |
int |
Credits ever spent on STT |
rate_limit |
RateLimitInfo |
limit, remaining, reset_in_seconds, window_seconds |
Changed: credits are a single
global_balanceshared by TTS and STT. The oldinfo.credits.tts.balance/info.credits.stt.balanceaccessors (and theCredits/CreditBalancetypes) have been removed — they described a schema the server no longer returns and silently reported0.
Error Handling
import convozen
from convozen import AuthenticationError, RateLimitError, APIError
client = convozen.Client(api_key="your-api-key")
try:
audio = client.tts.synthesize("Hello")
except AuthenticationError:
print("Invalid API key")
except RateLimitError:
print("Too many requests")
except APIError as e:
print(f"Server error: {e}")
Standalone Clients
from convozen import TTS, STT
tts = TTS(api_key="your-api-key")
audio = tts.synthesize("Hello", language="hi")
stt = STT(api_key="your-api-key")
# Flat transcript
result = stt.transcribe("recording.wav")
print(result.text)
# Per-speaker diarization (mono, multiple speakers)
result = stt.transcribe("call.wav", diarize=True)
for turn in result.turns:
print(f"[{turn.speaker_id}] {turn.start_sec:.2f}s – {turn.end_sec:.2f}s : {turn.transcript}")
Release files for convozen 0.4.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| convozen-0.4.3.tar.gz | 20.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| convozen-0.4.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 45.3 kB
Release files / convozen-0.4.3.tar.gz
| Download URL | convozen-0.4.3.tar.gz |
|---|---|
| Size | 20.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
504208cfbe1ca660ae80c80e9919e8183e030641cfd483315917ad67f6cfd9b7
|
|
BLAKE2b-256 checksum How to use checksums |
5729fb6ac1c7d2cb9220f6af9c3b9ed5d83e83c0166398348593d415b3bcc751
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / convozen-0.4.3-py3-none-any.whl
| Download URL | convozen-0.4.3-py3-none-any.whl |
|---|---|
| Size | 25.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4625d245619272601602be680ac79c45ce081e1451d13ef2fcad4d6a87e1f93c
|
|
BLAKE2b-256 checksum How to use checksums |
2f4f1b210a18fa580f13b6aa91d816a97c5ef8dd0ff417641f391082997ad5f5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|