Theme
STT API
VoiceLab Speech-to-Text endpoints: /api/v1/stt/*, /api/v2/stt/*, WebSocket, and gRPC.
Base URL: https://api.voicelab.uz
Auth: X-Api-Key on all REST requests.
Overview
- Short audio:
POST /api/v1/stt/post/(sync). - Long audio:
POST /api/v2/stt/post/(async, returnstask_id). - Realtime:
wss://api.voicelab.uz/api/v1/stt/realtime. - Keep a realtime connection open and stream sequential chunks for longer sessions.
- gRPC streaming host:
api.voicelab.uz:443(separate RPC perTranscribecall).
API key
- Header:
X-Api-Key: <api_key> - WebSocket: query
token - Realtime
formatquery param:webm(default) orpcm
Short audio transcription
Request fields
audio(required, file) — input audio.language—uz(default),en,ru.has_diarization(boolean string) — label speakers.has_offset(boolean string) — return segment offsets.is_summary(boolean string) — generate a summary.title(string) — optional label.
Audio formats: mp3, wav, ogg, m4a. Diarization requires audio ≥ 15 s.
Examples
bash
curl --request POST \
--url https://api.voicelab.uz/api/v1/stt/post/ \
--header 'X-Api-Key: your_api_key' \
--header 'Accept-Language: en' \
--form 'audio=@/path/to/voice-note.mp3' \
--form 'language=uz' \
--form 'has_diarization=false'bash
export AISHA_API_KEY=your_api_key
npx aisha-ai stt ./audio.wavResponse
json
{
"id": 531,
"gender": "unknown",
"title": null,
"created_at": "2026-05-04T10:12:43.212Z",
"duration": 18.7,
"transcript": "Assalomu alaykum, this is a short audio transcription."
}Status codes
200— Result returned.400— Missing audio or bad format.402— Insufficient balance.403— Duration limit or access issue.503— STT service temporarily unavailable.
v1 history list
bash
curl --request GET \
--url 'https://api.voicelab.uz/api/v1/stt/get/?page=1&limit=10' \
--header 'X-Api-Key: your_api_key'GET /api/v1/stt/audios/ returns the same response shape.
json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 531,
"title": null,
"gender": "unknown",
"status": "SUCCESS",
"language": "uz",
"created_at": "2026-05-04T10:12:43.212Z",
"duration": 18.7,
"transcript": "Assalomu alaykum, this is a short audio transcription.",
"summary": "",
"diarization": [],
"audio_url": "/media/audio/f35f6c3a.wav",
"speakers": []
}
]
}Status codes
200— History returned.403— Missing or invalid API key.
Long audio async transcription
Request fields
audio(required, file) — audio file.language—uz(default),en,ru.has_diarization— speaker labeling.has_offset— segment offsets.is_summary— summary.is_meeting— meeting profiling.title— optional label.
Max file size: 500MB.
Example
bash
curl --request POST \
--url https://api.voicelab.uz/api/v2/stt/post/ \
--header 'X-Api-Key: your_api_key' \
--form 'audio=@/path/to/meeting-record.mp3' \
--form 'language=uz' \
--form 'has_diarization=true' \
--form 'is_summary=true'Response
json
{
"id": 901,
"has_diarization": true,
"is_meeting": false,
"task_id": "66e92db4-95cf-4bb9-acbc-49462039d19f",
"status": "PENDING",
"title": "sales-call-13-april",
"audio_url": "/media/audio/273bc5f8-1f91-4573-b3df-3c38c44294d0.mp3"
}Status codes
200— Task created.400— Missing audio or file too large.401— Missing or invalid API key.403— Balance or access error.500— Internal error.
Realtime WebSocket transcription
Endpoint: wss://api.voicelab.uz/api/v1/stt/realtime?format=webm&token=<api_key>
Request fields
token(required).format—webmorpcm.- Stream audio bytes/chunks continuously.
- End the session once with
{"event":"end"}.
Examples
js
const token = 'your_api_key'
const ws = new WebSocket(
`wss://api.voicelab.uz/api/v1/stt/realtime?format=webm&token=${encodeURIComponent(token)}`
)
ws.onmessage = event => {
const message = JSON.parse(event.data)
if (message.type === 'transcription') {
console.log(message.text, message.partial, message.consumed_audio_seconds)
}
}
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
const recorder = new MediaRecorder(stream, { mimeType: 'audio/webm;codecs=opus' })
recorder.ondataavailable = async event => {
if (event.data.size > 0 && ws.readyState === WebSocket.OPEN) {
ws.send(await event.data.arrayBuffer())
}
}python
import asyncio
import websockets
TOKEN = "your_api_key"
URL = f"wss://api.voicelab.uz/api/v1/stt/realtime?format=pcm&token={TOKEN}"
async def stream_pcm():
async with websockets.connect(URL, max_size=None) as ws:
async def reader():
async for message in ws:
print(message)
reader_task = asyncio.create_task(reader())
with open("audio.s16le", "rb") as audio:
while chunk := audio.read(3200):
await ws.send(chunk)
await asyncio.sleep(0.1)
await ws.send('{"event":"end"}')
await reader_task
asyncio.run(stream_pcm())Responses
json
{
"type": "session_started",
"session_id": "7ab6d67a-9a29-4ad9-90b7-d2f5b2fc08fb",
"user_id": "42",
"allowed_audio_seconds": 1280,
"format": "webm"
}json
{
"type": "transcription",
"session_id": "7ab6d67a-9a29-4ad9-90b7-d2f5b2fc08fb",
"text": "Assalomu alaykum, please check my order status.",
"partial": false,
"segment_event": "end",
"consumed_audio_seconds": 4.32
}json
{
"type": "error",
"code": "insufficient_balance",
"message": "Balance limit reached",
"session_id": "7ab6d67a-9a29-4ad9-90b7-d2f5b2fc08fb"
}Close codes
1000— Stream closed normally.1008— Token, balance, or format error.1011— Server failed to process the stream.
gRPC streaming transcription
Endpoint: api.voicelab.uz:443
Request fields
audio_chunk(bytes, required).language(uz/ru/en) — first chunk only.first(bool) — first-chunk marker.
Example
python
import grpc
import stt_pb2
import stt_pb2_grpc
channel = grpc.secure_channel("api.voicelab.uz:443", grpc.ssl_channel_credentials())
client = stt_pb2_grpc.RealtimeSTTStub(channel)
def chunks(path):
with open(path, "rb") as audio:
first = True
while data := audio.read(32000):
yield stt_pb2.TranscribeChunk(
audio_chunk=data,
language="uz" if first else "",
first=first,
)
first = False
def transcribe(path):
response = client.Transcribe(chunks(path))
print(response.text)
for segment in response.segments:
print(segment.start, segment.end, segment.text)
transcribe("audio.wav")
transcribe("another.wav")Response
json
{
"text": "Assalomu alaykum, please check my order status.",
"language": "uz",
"duration": 4.32,
"audio_bytes": 138240,
"segments": [
{
"start": 0.0,
"end": 4.32,
"text": "Assalomu alaykum, please check my order status."
}
],
"timings": {
"transcribe_sec": 0.74,
"total_sec": 0.82
}
}Close / error codes
1000— Stream closed normally.1008— Token, balance, or format error.1011— Server failed to process the stream.
v2 history and detail
v2 history
bash
curl --request GET \
--url 'https://api.voicelab.uz/api/v2/stt/get/?page=1&limit=10' \
--header 'X-Api-Key: your_api_key'json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 901,
"title": "sales-call-13-april",
"status": "PENDING",
"created_at": "2026-05-04T10:39:58.501Z",
"duration": null,
"audio_url": "/media/audio/273bc5f8-1f91-4573-b3df-3c38c44294d0.mp3"
}
]
}bash
curl --request GET \
--url https://api.voicelab.uz/api/v2/stt/get/901/ \
--header 'X-Api-Key: your_api_key'json
{
"id": 901,
"title": "sales-call-13-april",
"status": "SUCCESS",
"created_at": "2026-05-04T10:39:58.501Z",
"duration": 612.4,
"transcript": "Long meeting transcript text...",
"summary": "Short summary...",
"diarization": [],
"audio_url": "/media/audio/273bc5f8-1f91-4573-b3df-3c38c44294d0.mp3"
}Status codes
200— History or detail returned.401— Missing or invalid API key.404— Transcript not found.
Task status polling
bash
curl --request GET \
--url 'https://api.voicelab.uz/task-status/task-123/?instance_id=944' \
--header 'Authorization: Bearer <user_jwt>'json
{
"task_id": "task-123",
"status": "PENDING",
"transcript_status": "PENDING",
"message": "Task is still processing"
}json
{
"task_id": "task-123",
"status": "SUCCESS",
"transcript_status": "SUCCESS",
"result": {
"transcript": "Hello world"
}
}Prefer v2 detail with API keys
/task-status/ expects a user JWT. API-key integrations should poll GET /api/v2/stt/get/{id}/ instead.
Status codes
200— Task state returned.400— Missinginstance_id.403— Access denied.500— Task failed or unknown state.