ovozAI
Get an API key

ovozAI API

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

Quick startAuthenticationPricingTranscribe 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, "billed_seconds": 13 }

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);

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 balance. 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 uses your account's balance, the same one as the ovozAI bot and website:

  • Transcription takes voice-to-text time, by the length of the recording: first the free 1 h every month, then purchased time. Timestamps take twice the length.
  • Speech takes text-to-voice time, by the length of the audio made: 5 min free, then purchased time.
  • Recordings with no speech, and requests that fail, cost nothing.
Voice-to-text timeUzbek cardsInternational cards
1 h10,000 UZS$1.49
3 h27,000 UZS$2.99
7 h60,000 UZS$6.49
Text-to-voice timeUzbek cardsInternational cards
10 min10,000 UZS$1.49
1 h54,000 UZS$5.99
2 h102,000 UZS$10.49

Buy time on the website. Purchased time doesn't expire. 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×): verbose_json, srt, vtt.
timestamp_granularities[]optionalword adds word timings to verbose_json.

The response carries the headers OvozAI-Transcription-Id and OvozAI-Billed-Seconds. 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 or urlrequiredThe recording, or a YouTube link.
languageoptionalAs above: uz-Cyrl for Cyrillic Uzbek.
timestampsoptionaltrue to time the text (files only, 2×).
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
  "billed_seconds": 3669,         // 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 twice the recording's length from your balance, 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) or male.
response_formatoptionalmp3 (default), opus (Ogg Opus, as Telegram voice messages) or wav.
modeloptionalAny value; ignored.

Long texts take a while: about a minute for 5,000 characters. The headers OvozAI-Audio-Seconds and OvozAI-Billed-Seconds say how long the audio is and what it took.

Balance

GET /balance:

{
  "object": "balance",
  "unlimited": false,
  "transcription": {
    "free_seconds": 2400,                       // free time left this month
    "free_resets_at": "2026-11-01T00:00:00.000Z",
    "purchased_seconds": 25200,
    "available_seconds": 27000                  // less what waiting jobs will take
  },
  "speech": { "free_seconds": 300, "purchased_seconds": 0 },
  "top_up_url": "https://app.ovozai.uz/?page=buy"
}

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 time left for this; the message says how much is needed.
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}. 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