Skip to content

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

  1. Create audio with POST /api/v1/tts/post/ and a transcript.
  2. If you send webhook_notification_url, the request is async (202 Accepted) and returns a queued task_id.
  3. Check results with GET /api/v1/tts/status/{id}/ or history.
  4. For realtime, use wss://api.voicelab.uz/api/v1/tts/realtime or gRPC api.voicelab.uz:443.
  5. Reuse one WebSocket session for multiple text turns — reconnecting per turn is not required.
  6. 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)

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) — Gulnoza only: Neutral, Cheerful, Happy, Sad.
  • speed (float) — 0.52.0, default 1.0.
  • voice_id (integer) — when set, do not send mood.
  • webhook_notification_url (string, optional) — makes the request async.

Limits:

  • transcript: up to 1000 characters with an API key (500 for 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.wav

Responses

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_path returned.
  • 202 — Request queued (async).
  • 400 — Invalid transcript / 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_idhappy, cheerful, neutral, sad.
  • language (uz / en / ru) — default uz.
  • text (required) — text to speak.
  • speed (float, default 1.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 — default uz.
  • speed — default 1.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.