Theme
TTS API
VoiceLab Text-to-Speech endpoints: /api/v1/tts/* and streaming services.
Base URL: https://api.voicelab.uz
Server integrations must send X-Api-Key on every REST request.
Overview
- Create audio with
POST /api/v1/tts/post/and atranscript. - If you send
webhook_notification_url, the request is async (202 Accepted) and returns a queuedtask_id. - Check results with
GET /api/v1/tts/status/{id}/or history. - For realtime, use
wss://api.voicelab.uz/api/v1/tts/realtimeor gRPCapi.voicelab.uz:443. - Reuse one WebSocket session for multiple
textturns — reconnecting per turn is not required. - Billing balance is checked when the stream starts.
API key
Headers
X-Api-Key: <api_key>- Streaming auth:
- WebSocket:
?token=<api_key> - gRPC:
x-api-key: <api_key>(metadata)
- WebSocket:
Create audio
Request fields
transcript(required,string) — text to synthesize.language(string) —uz(default),en,ru.model(string) — built-in model currently:Gulnoza.mood(string) —Gulnozaonly:Neutral,Cheerful,Happy,Sad.speed(float) —0.5–2.0, default1.0.voice_id(integer) — when set, do not sendmood.webhook_notification_url(string, optional) — makes the request async.
Limits:
transcript: up to1000characters with an API key (500for public requests).
Do not send model, mood, or speed for en / ru.
Examples
bash
curl --request POST \
--url https://api.voicelab.uz/api/v1/tts/post/ \
--header 'X-Api-Key: your_api_key' \
--header 'Accept-Language: en' \
--form 'transcript=Assalomu alaykum, this is a VoiceLab TTS test.' \
--form 'language=uz' \
--form 'model=Gulnoza' \
--form 'mood=Neutral' \
--form 'speed=1.0'bash
curl --request POST \
--url https://api.voicelab.uz/api/v1/tts/post/ \
--header 'X-Api-Key: your_api_key' \
--header 'Accept-Language: en' \
--form 'transcript=Async webhook test transcript.' \
--form 'language=uz' \
--form 'webhook_notification_url=https://example.com/webhooks/tts'bash
export AISHA_API_KEY=your_api_key
npx aisha-ai tts "Salom dunyo" --model Gulnoza --out salom.wavResponses
Sync success (201):
json
{
"audio_path": "/media/tts_audios/request-id.wav"
}Async queue (202):
json
{
"id": 184,
"task_id": "7d5f8779-9cb0-4230-9318-2f8c3c3f0e31",
"status": "PENDING"
}Status codes
201— Audio ready;audio_pathreturned.202— Request queued (async).400— Invalidtranscript/language/model/speed.401— Custom voice requires auth.402— Insufficient balance.503— TTS service temporarily unavailable.
Check status
Request
GET /api/v1/tts/status/{id}/
bash
curl --request GET \
--url https://api.voicelab.uz/api/v1/tts/status/184/ \
--header 'X-Api-Key: your_api_key'Response
json
{
"id": 184,
"status": "SUCCESS",
"task_id": "7d5f8779-9cb0-4230-9318-2f8c3c3f0e31",
"audio_path": "/media/tts_audios/request-id.wav",
"characters": 42
}Status codes
200— Status returned.403— No access to another user's record.404— TTS record not found.
History list
bash
curl --request GET \
--url 'https://api.voicelab.uz/api/v1/tts/get/?page=1&limit=10' \
--header 'X-Api-Key: your_api_key'json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 184,
"transcript": "Assalomu alaykum, this is a VoiceLab TTS test.",
"audio_url": "/media/tts_audios/request-id.wav",
"model": "Gulnoza",
"mood": "Neutral",
"created_at": "2026-05-04T10:15:30Z"
}
]
}Status codes
200— History returned.403— Missing or invalid API key.
Realtime WebSocket TTS
Endpoint: wss://api.voicelab.uz/api/v1/tts/realtime?token=<api_key>
Request fields
request_id(required) — unique id per turn.speaker_id—happy,cheerful,neutral,sad.language(uz/en/ru) — defaultuz.text(required) — text to speak.speed(float, default1.0) — speech rate.
Example
js
const token = 'your_api_key'
const ws = new WebSocket('wss://api.voicelab.uz/api/v1/tts/realtime?token=' + encodeURIComponent(token))
ws.onmessage = event => {
if (typeof event.data === 'string') {
const message = JSON.parse(event.data)
if (message.type === 'audio') {
console.log(message.request_id, message.sample_rate, message.duration_sec)
}
if (message.type === 'error') console.error(message.code, message.message)
return
}
// Binary frame: complete 16 kHz mono WAV bytes.
playWav(event.data)
}
ws.onopen = () => ws.send(JSON.stringify({
request_id: 'turn-1',
speaker_id: 'happy',
language: 'uz',
text: 'Assalomu alaykum, this is a VoiceLab TTS test.'
}))
// Reuse this connection for subsequent turns; close once the session ends.
function finishSession() {
ws.send(JSON.stringify({ type: 'end' }))
}Responses
json
{
"type": "session_started",
"session_id": "7ab6d67a-9a29-4ad9-90b7-d2f5b2fc08fb",
"billing": "characters"
}json
{
"type": "audio",
"request_id": "turn-1",
"speaker_id": "happy",
"sample_rate": 16000,
"duration_sec": 1.84,
"characters": 42
}json
{
"type": "error",
"code": "insufficient_balance",
"message": "TTS character balance limit reached"
}Close codes
1000— Session closed normally.1008— API key or balance rejected.
Persistent gRPC stream
Endpoint: api.voicelab.uz:443
Request fields
text(required) — text to speak.speaker_id— built-in speaker.language— defaultuz.speed— default1.0.end(bool) — end-of-session flag.
Example
python
import grpc
import tts_pb2
import tts_pb2_grpc
channel = grpc.secure_channel('api.voicelab.uz:443', grpc.ssl_channel_credentials())
client = tts_pb2_grpc.RealtimeTTSStub(channel)
def requests():
yield tts_pb2.SynthesizeRequest(
request_id='turn-1', speaker_id='happy', language='uz',
text='Assalomu alaykum, this is a VoiceLab TTS test.'
)
yield tts_pb2.SynthesizeRequest(
request_id='turn-2', speaker_id='neutral', language='uz',
text='The next turn continues on the same channel.'
)
yield tts_pb2.SynthesizeRequest(end=True)
for response in client.Synthesize(requests(), metadata=(('x-api-key', 'your_api_key'),)):
if response.error_code:
raise RuntimeError(response.error_message)
save_wav(response.audio_wav)
channel.close()Response shape
json
{
"request_id": "turn-1",
"speaker_id": "happy",
"sample_rate": 16000,
"duration_sec": 1.84,
"characters": 42,
"audio_wav": "bytes"
}Close / error codes
1000— Stream closed normally.1008— API key or balance issue.1011— Internal stream error.