OvozAI
EnglishРусскийO'zbekchaЎзбекчаالعربيةGet an API key

OvozAI API

Speech to text and text to speech for your apps · https://api.ovozai.uz/v1

Quick startStart with an AI assistantAuthenticationPricingTranscribe a recordingJobs: long recordings and YouTube linksTimestamps and subtitlesWebhooksText to speechBalanceErrorsLimitsYour data

Quick start

The OvozAI API turns speech into text and text into speech. It's built for Uzbek (Latin or Cyrillic) and works just as well with Russian, English and the other languages people speak in one recording: each part is written in the language it was spoken.

  1. Sign in at app.ovozai.uz and open API to make a key.
  2. Send a recording:
curl https://api.ovozai.uz/v1/audio/transcriptions \
  -H "Authorization: Bearer $OVOZAI_API_KEY" \
  -F file=@voice.ogg

The answer is the text:

{ "id": "6f1c…", "text": "Assalomu alaykum, …", "duration": 12.4, "credits": 12 }

The transcription endpoint works like OpenAI's, so the OpenAI libraries work with OvozAI by changing the base URL:

from openai import OpenAI

client = OpenAI(base_url="https://api.ovozai.uz/v1", api_key="ovz_…")
with open("lecture.mp3", "rb") as audio:
    result = client.audio.transcriptions.create(model="ovozai-transcribe", file=audio)
print(result.text)
import OpenAI from "openai";
import fs from "node:fs";

const client = new OpenAI({ baseURL: "https://api.ovozai.uz/v1", apiKey: process.env.OVOZAI_API_KEY });
const result = await client.audio.transcriptions.create({
  model: "ovozai-transcribe",
  file: fs.createReadStream("voice.ogg"),
});
console.log(result.text);

Start with an AI assistant

Using ChatGPT, Claude, Cursor or another coding assistant? Copy this prompt into it, add what you're building, and it has everything it needs to write the integration.

I want to use the OvozAI API in my project. OvozAI does speech to text and text to speech, built for Uzbek (Latin or Cyrillic) and also good with Russian, English and recordings that mix languages. Help me integrate it: first ask me what I'm building and in which language/framework, unless I've said so below, then write working code.

What I'm building: <describe it here, e.g. "a Telegram bot in Python that transcribes voice messages">

## The API
- Base URL: https://api.ovozai.uz/v1
- Auth: header `Authorization: Bearer ovz_…` (my key; keep it on the server, read it from an environment variable such as OVOZAI_API_KEY, never ship it to a browser or app).
- Docs: https://api.ovozai.uz/docs

### Transcribe a file and wait for the text
POST /audio/transcriptions, multipart/form-data. OpenAI-compatible: the official OpenAI SDKs work with base_url set to https://api.ovozai.uz/v1 (model: "ovozai-transcribe").
- file (required): audio or video, up to 500 MB (ogg, mp3, m4a, wav, mp4, webm…)
- language (optional): "uz-Cyrl" writes Uzbek in Cyrillic; otherwise Latin. Other languages are written as spoken.
- response_format (optional): json (default) → {"id","text","duration","credits"}; text → plain text; verbose_json, srt or vtt → with timestamps (segments; add timestamp_granularities[]=word for word timings).
Response headers: OvozAI-Transcription-Id, OvozAI-Credits. A minute of audio takes a few seconds; an hour a few minutes.

### Jobs: long recordings and YouTube links
POST /transcriptions answers at once (202) with a job.
- A file as multipart (fields: file, language, timestamps=true|false, webhook_url), or JSON {"url": "https://www.youtube.com/watch?v=…", "language": …, "webhook_url": …} for a public YouTube video up to 3 hours (no timestamps for links).
- Job: {"id","status": "queued"|"processing"|"completed"|"failed","source","title","duration","credits","text","segments","words","error": {"code","message"}|null}
- GET /transcriptions/{id} (?format=text|srt|vtt for the result as a file), GET /transcriptions (latest, ?limit=), DELETE /transcriptions/{id}. Poll every few seconds, or use the webhook. Results are kept 7 days.
- Webhook: POST to webhook_url with {"id","type": "transcription.completed"|"transcription.failed","created_at","data": <job>}. Verify the OvozAI-Signature header "t=<unix time>,v1=<hex>": v1 = HMAC-SHA256 of "<t>.<raw body>" with my webhook secret (from the API page); reject if t is older than 5 minutes. Answer 2xx within 10 seconds; failed deliveries are retried.

### Text to speech
POST /audio/speech, JSON {"input": "<20–20000 characters>", "voice": "female"|"male"|<a voice name>, "response_format": "mp3"|"opus"|"wav"}. Answers with the audio bytes (OpenAI-compatible, model "ovozai-speech"). About a minute for 5,000 characters. Headers: OvozAI-Audio-Seconds, OvozAI-Credits.
- Voices (case doesn't matter): Zephyr (female, bright), Leda (female, youthful), Achernar (female, soft), Callirrhoe (female, easygoing), Charon (male, informative), Achird (male, friendly), Algieba (male, smooth), Iapetus (male, clear). "female" is Zephyr (the default), "male" is Charon.
- Expressions: sounds go inside input in angle brackets, in English whatever the text's language, where they should be heard: <laugh>, <chuckle>, <sigh>, <gasp>, <breath>, <cough>, <cry>, <short pause>, <long pause>; <whispers> whispers to the end of the sentence. They're not read aloud. A word in CAPITALS is stressed.

### Balance
GET /balance → {"credits","available_credits","unlimited","top_up_url"}.

### Prices (credits, rounded down)
- Transcription: 1 credit per second of audio (files and YouTube links)
- With timestamps: 2 credits per second
- Text to speech: 400 credits per 1,000 characters
- No speech, failures and jobs cancelled before they start cost nothing.

### Errors and limits
Errors: HTTP status + {"error": {"code","message"}}. 400 invalid_request/file_missing/unreadable_file/unsupported_url/timestamps_unavailable, 401 unauthorized, 402 insufficient_balance (message says how many credits are needed), 404 not_found, 409 not_ready/conflict, 413 file_too_large, 429 rate_limited/too_many_jobs, 502 transcription_failed/timestamps_failed/speech_failed (retry), 503 server_busy/links_unavailable (retry later).
Limits: 120 requests a minute per key; 2 transcriptions at a time per account (up to 20 waiting); 2 speech requests at a time.

## How to write it
- Use the OpenAI SDK where it fits (transcription, speech); plain HTTP for jobs, YouTube links and balance.
- Prefer jobs with a webhook (or polling) for recordings longer than a few minutes; the synchronous endpoint is fine for voice messages.
- Handle 402 by telling the user to top up, 429 by backing off, 502/503 by retrying with backoff.
- Keep the key out of client-side code and out of the repository.

Authentication

Every request carries one of your keys in the Authorization header:

Authorization: Bearer ovz_…

A key is shown once, when it's made. Keep it on your server: anyone with it can spend your credits. You can have several (one per app, say) and revoke any of them on the API page. The base URL is https://api.ovozai.uz/v1.

Pricing

The API is paid in credits, a balance of its own, apart from your time in the OvozAI bot and website. A credit is a second of transcription:

Credits
Transcription (a file or a YouTube link)1 credit a second of audio
Transcription with timestamps (verbose_json, SRT, VTT)2 credits a second of audio
Text to speech400 credits per 1,000 characters of text

Credits are rounded down, never up: a 10.6-second recording costs 10 credits (21 with timestamps), 143 characters of speech 57. Recordings with no speech, requests that fail and jobs cancelled before they start cost nothing. Your first key comes with 1,800 credits to try it.

CreditsTranscriptionUzbek cardsInternational cards
36,00010 h90,000 UZS$8.99
180,00050 h390,000 UZS$38.99
720,000200 h1,400,000 UZS$138

Launch offer: your first credit pack comes with 2× the credits if you buy it by 9 November (Tashkent time).

Buy credits on the website; they don't expire. The API page shows what you spent, day by day, and a log of every charge and failed request. Need more volume? Write to info@ovozai.uz.

Transcribe a recording

POST /audio/transcriptions takes an audio or video file (up to 500 MB; anything ffmpeg reads: ogg, mp3, m4a, wav, mp4, webm…) as multipart/form-data and answers once it's transcribed. A minute of audio takes a few seconds; an hour, a few minutes. For long recordings you may prefer a job.

Field
filerequiredThe recording.
modeloptionalAny value; OvozAI picks the model. OpenAI's libraries require it.
languageoptionaluz-Cyrl writes Uzbek in Cyrillic; otherwise it's Latin. Other languages are written as spoken whatever this says.
response_formatoptionaljson (default), text, or with timestamps (2 credits a second): verbose_json, srt, vtt.
timestamp_granularities[]optionalword adds word timings to verbose_json.

The response carries the headers OvozAI-Transcription-Id and OvozAI-Credits (what it cost). A recording with no speech comes back with empty text and costs nothing.

If the connection drops before the transcription starts, it's cancelled and costs nothing. Once it has started, it finishes and can be fetched by its id for 7 days.

Jobs: long recordings and YouTube links

POST /transcriptions answers at once (202) with a job; you then fetch it, or get a webhook when it's done. Send either a file, as multipart/form-data:

curl https://api.ovozai.uz/v1/transcriptions \
  -H "Authorization: Bearer $OVOZAI_API_KEY" \
  -F file=@lecture.mp4 \
  -F timestamps=true \
  -F webhook_url=https://example.com/ovozai

or a YouTube video link, as JSON (public or unlisted videos up to 3 hours; nothing is downloaded on your side):

curl https://api.ovozai.uz/v1/transcriptions \
  -H "Authorization: Bearer $OVOZAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.youtube.com/watch?v=…"}'
Field
file / urlrequiredThe recording, or a YouTube link.
languageoptionalAs above: uz-Cyrl for Cyrillic Uzbek.
timestampsoptionaltrue to time the text (files only, 2 credits a second).
webhook_urloptionalA public https:// URL to call when it's done or failed.

A job:

{
  "id": "6f1c…",
  "object": "transcription",
  "status": "completed",          // queued, processing, completed or failed
  "source": "file",               // or "youtube"
  "title": "lecture.mp4",
  "url": null,                    // the YouTube link, for links
  "language": "uz",
  "timestamps": true,
  "created_at": "2026-10-31T10:00:00.000Z",
  "completed_at": "2026-10-31T10:02:41.000Z",
  "duration": 1834.2,             // seconds of recording
  "credits": 3668,                // what it cost, once completed or failed
  "text": "…",
  "segments": [{ "id": 0, "start": 0.42, "end": 3.1, "text": "…" }],
  "words": [{ "word": "Assalomu", "start": 0.42, "end": 0.93 }],
  "error": null                   // { "code", "message" } when failed
}
Request
GET /transcriptions/{id}The job. ?format=text, srt or vtt gives the result as a file instead.
GET /transcriptionsYour latest jobs, newest first, without their text (?limit= up to 100).
DELETE /transcriptions/{id}Deletes a job and its result. A waiting job is cancelled and costs nothing; a running one can be deleted once it's done.

Results are deleted 7 days after they're done.

Timestamps and subtitles

With timestamps, OvozAI places every word of the transcript on the recording: you get segments (subtitle-sized lines, at most 42 characters by 2 lines) and words, or the same as SRT or WebVTT. They take 2 credits a second of the recording, and need the recording itself, so they aren't available for YouTube links. If timing fails, the whole request fails and costs nothing.

curl https://api.ovozai.uz/v1/audio/transcriptions \
  -H "Authorization: Bearer $OVOZAI_API_KEY" \
  -F file=@video.mp4 -F response_format=srt > video.srt

Webhooks

A job with a webhook_url gets a POST when it's completed or failed:

{
  "id": "evt_…",
  "type": "transcription.completed",   // or "transcription.failed"
  "created_at": "2026-10-31T10:02:41.000Z",
  "data": { …the job, with its text… }
}

Answer with any 2xx within 10 seconds. Otherwise it's sent again after 10 s, 1 min, 10 min, 1 h (5 tries in all); you can always fetch the job yourself.

Each one is signed with your webhook secret (on the API page) in the OvozAI-Signature header: t=<unix time>,v1=<signature>, where the signature is the hex HMAC-SHA256 of <t>.<raw body>. Check it, and that t is recent, before trusting the body:

import crypto from "node:crypto";

function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((part) => part.split("=")));
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  return fresh && crypto.timingSafeEqual(Buffer.from(v1, "hex"), Buffer.from(expected, "hex"));
}
import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(part.split("=", 1) for part in header.split(","))
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
    fresh = abs(time.time() - int(parts["t"])) < 300
    return fresh and hmac.compare_digest(parts["v1"], expected)

Text to speech

POST /audio/speech reads text aloud in a natural voice (Uzbek, Russian, English and more) and answers with the audio. It works like OpenAI's speech endpoint.

curl https://api.ovozai.uz/v1/audio/speech \
  -H "Authorization: Bearer $OVOZAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input": "Assalomu alaykum! Bugun havo juda yaxshi.", "voice": "female"}' \
  --output speech.mp3
Field
inputrequiredThe text, 20–20,000 characters.
voiceoptionalfemale (default, Zephyr), male (Charon), or any of these by name: Zephyr, Leda, Achernar, Callirrhoe, Charon, Achird, Algieba, Iapetus. Hear them on the app's Text to speech page.
response_formatoptionalmp3 (default), opus (Ogg Opus, as Telegram voice messages) or wav.
modeloptionalAny value; ignored.

Put sounds inside input, in angle brackets, right where they should be heard: <laugh>, <chuckle>, <sigh>, <gasp>, <breath>, <cough>, <cry>, <short pause>, <long pause>. Write them in English, whatever the text's language; they aren't read aloud. <whispers> whispers to the end of the sentence, and a word in CAPITALS is stressed.

Salom! <laugh> Bugun havo juda yaxshi. <short pause> Keling, sayr qilamiz.

It costs 400 credits per 1,000 characters of input, rounded down. Long texts take a while: about a minute for 5,000 characters. The headers OvozAI-Audio-Seconds and OvozAI-Credits say how long the audio is and what it cost.

Balance

GET /balance:

{
  "object": "balance",
  "unlimited": false,
  "credits": 34200,
  "available_credits": 30600,     // less what waiting and running work holds
  "top_up_url": "https://app.ovozai.uz/buy/api"
}

A request is turned away with 402 when available_credits doesn't cover it: a file or video's length is known before it starts, so it's checked up front.

Errors

Errors come with an HTTP status and a body like {"error": {"code": "insufficient_balance", "message": "…"}}.

StatusCodeMeaning
400invalid_requestA field is missing or wrong; the message says which.
400file_missingNo file in the file field (or no url).
400unreadable_fileThe file isn't audio or video ffmpeg can read.
400unsupported_urlOnly YouTube video links are taken.
400video_unavailable, live_video, too_longThe video is private or deleted, live, or too long.
400timestamps_unavailableTimestamps were asked for a YouTube link, or a format that needs them from a job made without.
400input_too_short, input_too_longThe text for speech is too short or too long.
401unauthorizedThe key is missing, wrong or revoked.
402insufficient_balanceNot enough credits for this; the message says how many it needs.
404not_foundNo such job (or endpoint).
409not_ready, conflictThe job isn't done yet, or is running.
413file_too_largeThe file is over 500 MB.
429rate_limited, too_many_jobsToo many requests, or too many jobs waiting.
502transcription_failed, timestamps_failed, speech_failedIt didn't work this time. Nothing was charged; try again.
503links_unavailable, server_busyTry again in a little while.

Limits

  • Files up to 500 MB; YouTube videos up to 3 hours.
  • 2 transcriptions at a time per account; more wait their turn, up to 20 waiting or running.
  • 2 speech requests at a time per account.
  • 120 requests a minute per key.

Your data

Recordings sent through the API are deleted as soon as they're transcribed, and are never kept or used to improve our models. Results are kept for 7 days so you can fetch them, then deleted; delete them sooner with DELETE /transcriptions/{id}. Transcripts of YouTube links are the exception: we keep a copy, with the link, until you delete the transcription. Text sent for speech isn't kept, nor the audio made from it. If you send other people's recordings, you need their permission, and should tell your users that OvozAI processes them. See the privacy policy and terms.

© 2026 OvozAI · info@ovozai.uz · Privacy · Terms