Документация API
Yotta STT распознает казахскую, русскую и смешанную речь. Вы отправляете аудио и получаете текст с разметкой по фразам и словам. Базовый URL: https://api.yotta.kz. Все ответы в JSON, ошибки в виде {"detail": "текст"}. Полная схема методов: Swagger.
Быстрый старт
- Получите API-ключ у нас (ключи выдаем вручную). Ключ выглядит как
yk_live_…. - Загрузите файл одной командой. В ответ придет
idзадачи и ссылка на поток событий. - Получите результат: через поток событий (SSE), запросом
GETили webhook.
# 1. загрузить файл
curl -H "X-API-Key: $KEY" -F file=@call.wav https://api.yotta.kz/v1/transcriptions
# 2. следить за распознаванием в реальном времени
curl -N "https://api.yotta.kz/v1/transcriptions/$ID/events?token=$STREAM_TOKEN"
# 3. или забрать готовый результат
curl -H "X-API-Key: $KEY" https://api.yotta.kz/v1/transcriptions/$IDВремя обработки: около 0.3 с на 1 с аудио. Фраза 10 с распознается примерно за 5 с, запись 40 с за 13-15 с. Если очередь занята, дольше.
Авторизация
| Способ | Как передать | Для чего |
|---|---|---|
| API-ключ | X-API-Key: yk_live_… | Ваш сервер. Все методы. |
| Временный upload-токен | X-Upload-Token: ut.… | Браузер. Только загрузка файлов. |
| Токен потока | ?token=<stream_token> | Браузер. Только SSE одной задачи или пачки. |
Временный upload-токен
Выпускается вашим сервером по API-ключу. Токен многоразовый в пределах срока жизни, файлы списываются с аккаунта ключа. ttl_s: от 60 до 3600 секунд. max_duration_s: максимальная длительность одного файла (для более длинных вернется 413).
curl -X POST https://api.yotta.kz/v1/upload-tokens \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"ttl_s": 600, "max_duration_s": 300}'
# 201 {"upload_token": "ut.acc_….1794….300.ab12…", "expires_at": 1794509204, "max_duration_s": 300}Загрузка
Один файл
POST /v1/transcriptions, multipart/form-data. Заголовок X-API-Key или X-Upload-Token.
| Поле | Описание |
|---|---|
file | Аудиофайл, обязательно |
merge | true | false, по умолчанию true. Слияние двух моделей через LLM: точнее, но чуть дольше. |
language | auto | kk | ru. Пока не используется, язык определяется автоматически. |
metadata | JSON-строка, вернется в результате без изменений (например, id кассы или звонка) |
webhook_url | Куда прислать результат после завершения (см. Webhook) |
curl -H "X-API-Key: $KEY" \
-F file=@call.wav \
-F merge=true \
-F 'metadata={"store":"almaty-12"}' \
-F webhook_url=https://example.kz/yotta/webhook \
https://api.yotta.kz/v1/transcriptionsОтвет 202:
{
"id": "tr_1a1d…",
"status": "queued",
"filename": "call.wav",
"duration_s": 10.8,
"billed_s": 11,
"stream_url": "https://api.yotta.kz/v1/transcriptions/tr_1a1d…/events",
"stream_token": "1794509204.39dc0e57…"
}Прогресс загрузки большого файла в браузере доступен через XMLHttpRequest.upload.onprogress (у fetch его нет).
Пачка файлов
POST /v1/batches, поле files повторяется. Остальные поля такие же. Файлы, которые не удалось принять, отмечаются в ответе как rejected, остальные обрабатываются.
curl -H "X-API-Key: $KEY" \
-F files=@a.wav -F files=@b.mp3 -F files=@c.ogg \
https://api.yotta.kz/v1/batches
# 202 {"batch_id": "bt_…",
# "items": [ {…как для одного файла…}, {"filename": "x.txt", "status": "rejected", "error": "…"} ],
# "stream_url": "…/v1/batches/bt_…/events", "stream_token": "…"}Форматы и лимиты
- Форматы: wav, mp3, ogg, opus, m4a, flac, webm, aac, mp4.
- Размер файла: до 200 МБ.
- Длительность: ограничивается upload-токеном (
max_duration_s). - Если у аккаунта слишком много задач в очереди, вернется
429: повторите запрос позже.
Получение результата
SSE: события в реальном времени
Откройте stream_url с ?token=<stream_token> (или с заголовком X-API-Key на сервере). Токен потока живет 1 час.
| Событие | data | Что делать |
|---|---|---|
status | {id, status:"queued", position} | Задача в очереди |
status | {id, status:"processing", attempt} | Распознавание началось |
status | {id, status:"queued", retry:true, reason} | Повтор после сбоя, фразы придут заново |
progress | {id, stage:"asr", percent} | Прогресс |
segment | {id, segment:{…}} | Upsert фразы по segment.id, сортировка по start |
done | полный результат | Показать результат, закрыть поток |
error | {id, status:"failed", code, message} | Показать ошибку, закрыть поток |
- Каждая фраза приходит минимум дважды: черновик (
merged: false) и итог (merged: true). После повтора задачи еще раз. Заменяйте фразу поsegment.id, не добавляйте новую. - Порядок фраз не гарантирован, сортируйте по
start. - При обрыве
EventSourceпереподключается сам, сервер дошлет пропущенное поLast-Event-ID. При новом подключении придет вся история задачи. - После
doneилиerrorсервер закрывает поток. ЗакрывайтеEventSourceв этих обработчиках, иначе браузер попробует переподключиться. - Раз в 15 с приходит комментарий
: pingдля поддержания соединения.
const segments = new Map();
const es = new EventSource(`${task.stream_url}?token=${task.stream_token}`);
es.addEventListener("status", (e) => {
const d = JSON.parse(e.data); // queued(position) | processing | queued + retry
});
es.addEventListener("progress", (e) => {
const { percent } = JSON.parse(e.data);
});
es.addEventListener("segment", (e) => {
const { segment } = JSON.parse(e.data);
segments.set(segment.id, segment); // upsert
render([...segments.values()].sort((a, b) => a.start - b.start));
});
es.addEventListener("done", (e) => {
const result = JSON.parse(e.data);
es.close();
});
es.addEventListener("error", (e) => {
if (e.data) { // ошибка задачи, а не обрыв сети
const err = JSON.parse(e.data);
es.close();
}
});Поток пачки (/v1/batches/{batch_id}/events) присылает события всех задач пачки, поле id в событии это id задачи. Поток закрывается, когда завершены все задачи.
GET: опрос статуса
| Метод | Путь | Ответ |
|---|---|---|
| GET | /v1/transcriptions/{id} | статус или полный результат; для queued есть position |
| GET | /v1/transcriptions?status=&limit= | список задач аккаунта (без фраз) |
| GET | /v1/transcriptions/{id}/events | SSE |
| GET | /v1/transcriptions/{id}/export?format= | файл txt | srt | vtt | json |
| DELETE | /v1/transcriptions/{id} | 204, задача и результат удалены |
| GET | /v1/batches/{batch_id} | {counts: {done: 3, …}, items: […]} |
| GET | /v1/balance | {balance_s, balance_h} |
curl -H "X-API-Key: $KEY" https://api.yotta.kz/v1/transcriptions/$IDWebhook
Если при загрузке указан webhook_url, после завершения задачи мы отправим туда POST. При неудаче до 5 попыток с паузами 2-32 с. Отвечайте 2xx как можно быстрее.
POST <webhook_url>
X-Yotta-Timestamp: 1794509204
X-Yotta-Signature: sha256=<HMAC-SHA256(webhook_secret, timestamp + "." + body)>
Content-Type: application/json
{"event": "transcription.done" | "transcription.failed", "data": { …результат как в GET… }}webhook_secret выдается при создании аккаунта. Проверяйте подпись по сырому телу запроса и отклоняйте запросы со старым X-Yotta-Timestamp (например, старше 5 минут).
import crypto from "node:crypto";
// Next.js route handler: app/api/yotta/webhook/route.ts
export async function POST(req: Request) {
const raw = await req.text();
const ts = req.headers.get("x-yotta-timestamp") ?? "";
const sig = req.headers.get("x-yotta-signature") ?? "";
const expected = "sha256=" + crypto
.createHmac("sha256", process.env.YOTTA_WEBHOOK_SECRET!)
.update(`${ts}.${raw}`)
.digest("hex");
const ok = sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
if (!ok || !fresh) return new Response("bad signature", { status: 401 });
const { event, data } = JSON.parse(raw);
// event: "transcription.done" | "transcription.failed"
return new Response("ok");
}Формат результата
{
"id": "tr_1a1d0e9d5ee6fe6fbd2",
"status": "done",
"batch_id": null,
"filename": "t3_001.wav",
"created_at": "2026-11-12T17:59:08.498143+00:00",
"finished_at": "2026-11-12T17:59:58.101+00:00",
"duration_s": 39.99,
"billed_s": 40,
"params": {"language": "auto", "diarization": false, "merge": true},
"metadata": {"store": "almaty-12"},
"text": "нормально, еле-еле мне нужны которые …",
"speech_s": 31.85,
"languages": ["kk", "ru"],
"speakers": [],
"segments": [
{
"id": 1, "start": 1.91, "end": 3.27,
"text": "мне нужны которые",
"language": "ru", "language_confidence": 1.0,
"confidence": 0.812,
"speaker": null, "speaker_confidence": null,
"merged": true,
"words": [
{"w": "мне", "start": 1.99, "end": 2.11, "conf": 0.888},
{"w": "которые", "start": 2.63, "end": 2.95, "conf": 0.7, "edited": true}
]
}
],
"model": {"asr": "gigaam-ml+k5", "merge": "…", "worker_version": "0.1.0"},
"timings": {"vad_s": 0.3, "asr_s": 11.6, "total_s": 12.8, "rtf": 0.32}
}| Поле | Значение |
|---|---|
status | queued → processing → done / failed |
text | Весь текст одной строкой |
duration_s / speech_s | Длина файла / сколько в нем речи, секунды |
billed_s | Сколько секунд кредитов списано |
languages | Языки в записи: kk, ru |
segments[] | Фразы между паузами. id: номер фразы, start/end: секунды от начала файла |
segment.language | kk / ru / mixed |
segment.merged | false: черновик, true: итог после слияния |
words[].conf | Уверенность слова, от 0 до 1. Ниже 0.5 стоит проверить, 0.5-0.8 скорее верно |
words[].edited | Слово исправлено ИИ (ни одна модель не дала его в таком виде) |
speakers, speaker* | Разделение говорящих: пока пусто и null |
metadata | То, что вы передали при загрузке |
error | Для failed: {code, message} |
Экспорт
GET /v1/transcriptions/{id}/export?format=txt|srt|vtt|json возвращает готовый файл. Для незавершенной задачи вернется 409.
curl -H "X-API-Key: $KEY" -o call.srt "https://api.yotta.kz/v1/transcriptions/$ID/export?format=srt"Формат SRT: номера с 1, время HH:MM:SS,mmm, между блоками пустая строка.
1
00:00:01,910 --> 00:00:03,270
мне нужны которыеVTT: то же с заголовком WEBVTT, точкой вместо запятой в миллисекундах и без номеров.
Ошибки
Тело ошибки: {"detail": "текст на русском"}. Текст можно показывать пользователю.
| Код | Когда |
|---|---|
| 401 | Нет или неверен ключ / токен, токен просрочен |
| 402 | Не хватает кредитов |
| 404 | Задача не найдена |
| 409 | Экспорт еще не готовой задачи |
| 413 | Файл больше 200 МБ или длиннее лимита |
| 415 | Формат не поддерживается |
| 422 | Не аудио или неверные параметры |
| 429 | Слишком много задач в очереди у аккаунта, повторите позже |
Кредиты
- 1 кредит = 1 секунда присланного аудио (округляется вверх, см.
billed_s). - Кредиты блокируются при загрузке и списываются после успешного распознавания.
- Если задача завершилась ошибкой, кредиты возвращаются.
- Баланс:
GET /v1/balance→{balance_s, balance_h}. Пополнение по счету, напишите нам.
curl -H "X-API-Key: $KEY" https://api.yotta.kz/v1/balance