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}")
Metadata
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 | Provenance | Actions |
|---|---|---|---|---|
| convozen-0.4.3.tar.gz | 20.3 kB | Without provenance | Details for convozen-0.4.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| convozen-0.4.3-py3-none-any.whl | Python 3 | none | any | Details for convozen-0.4.3-py3-none-any.whl |
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
|
Security for convozen 0.4.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.