# Clarion: Документация API

API Clarion переводит узбекскую речь в текст с пунктуацией, таймкодами слов и звуковыми событиями: файлы через REST, живой звук через WebSocket. Он совместим с OpenAI, поэтому в OpenAI SDK меняется только `base_url`.

Базовый URL: `https://api.uzasr.uz/v1`

## Аутентификация

Ключ передаётся в заголовке каждого запроса.

Ключ вида `uzasr_live_…` показывается один раз. Храните его на сервере, не в браузере и не в приложении.

```http
Authorization: Bearer uzasr_live_…
```

## Быстрый старт

Распознавание файла одним запросом.

```python
import os

from openai import OpenAI

client = OpenAI(
    base_url="https://api.uzasr.uz/v1",
    api_key=os.environ["UZASR_API_KEY"],
)

result = client.audio.transcriptions.create(
    model="uzasr-large",
    file=open("call.mp3", "rb"),
    response_format="verbose_json",
    timestamp_granularities=["word"],
)
print(result.text)
```

```javascript
import fs from "node:fs"
import OpenAI from "openai"

const client = new OpenAI({
  baseURL: "https://api.uzasr.uz/v1",
  apiKey: process.env.UZASR_API_KEY,
})

const result = await client.audio.transcriptions.create({
  model: "uzasr-large",
  file: fs.createReadStream("call.mp3"),
  response_format: "verbose_json",
  timestamp_granularities: ["word"],
})
console.log(result.text)
```

## Синхронное распознавание

Файлы до 10 минут. Ответ приходит в том же запросе.

`POST /v1/audio/transcriptions`

| Параметр | Тип | Описание |
| --- | --- | --- |
| `file` | file | Аудио или видео: wav, mp3, m4a, ogg, flac, mp4, webm. |
| `model` | string | `uzasr-large` или `uzasr-base`, либо точная версия. |
| `response_format` | string | `json`, `text` или `verbose_json`. По умолчанию `json`. |
| `timestamp_granularities[]` | string | `word`: таймкоды слов, только с `verbose_json`. |
| `language` | string | Необязательный. Поддерживается только `uz`. |

## Формат ответа

`verbose_json` с таймкодами слов и звуковыми событиями.

| Параметр | Тип | Описание |
| --- | --- | --- |
| `text` | string | Текст с пунктуацией, латиница. |
| `words[]` | object | `word`, `start`, `end`: слово и его границы в секундах. |
| `segments[]` | object | Фрагменты записи с границами и текстом. |
| `events[]` | object | `label`, `start`, `end`, `score`. Метки: `laughter`, `applause`, `music`. Неизвестные метки игнорируйте. |

```json
{
  "task": "transcribe",
  "language": "uz",
  "duration": 28.0,
  "text": "Oyoqdagi yechish bilan shunaqa ovoz chiqaradiki, …",
  "segments": [
    { "id": 0, "start": 0.0, "end": 9.8, "text": "Oyoqdagi yechish bilan …" }
  ],
  "words": [
    { "word": "Oyoqdagi", "start": 0.12, "end": 0.64 }
  ],
  "events": [
    { "label": "laughter", "start": 2.28, "end": 2.52, "score": 0.557 },
    { "label": "applause", "start": 19.28, "end": 21.04, "score": 0.659 }
  ]
}
```

Заголовок `X-Uzasr-Processing-Seconds` содержит чистое время обработки на GPU, без очереди и запуска.

## Асинхронные задачи

Файлы до 3 часов. Создайте задачу и опрашивайте статус.

`POST /v1/jobs` · `GET /v1/jobs/{id}`

Повтор с тем же `Idempotency-Key` вернёт исходную задачу. Статусы: `queued`, `succeeded`, `failed`.

```python
import os
import time

import requests

API = "https://api.uzasr.uz/v1"
headers = {"Authorization": f"Bearer {os.environ['UZASR_API_KEY']}"}

job = requests.post(
    f"{API}/jobs",
    headers={**headers, "Idempotency-Key": "call-2026-10-21-0042"},
    files={"file": open("meeting.mp3", "rb")},
    data={"model": "uzasr-large"},
).json()

while job["status"] == "queued":
    time.sleep(5)
    job = requests.get(f"{API}/jobs/{job['id']}", headers=headers).json()

print(job["result"]["text"])
```

```javascript
import { openAsBlob } from "node:fs"

const API = "https://api.uzasr.uz/v1"
const headers = { Authorization: `Bearer ${process.env.UZASR_API_KEY}` }

const form = new FormData()
form.append("file", await openAsBlob("meeting.mp3"), "meeting.mp3")
form.append("model", "uzasr-large")

let job = await fetch(`${API}/jobs`, {
  method: "POST",
  headers: { ...headers, "Idempotency-Key": "call-2026-10-21-0042" },
  body: form,
}).then((res) => res.json())

while (job.status === "queued") {
  await new Promise((resolve) => setTimeout(resolve, 5000))
  const poll = await fetch(`${API}/jobs/${job.id}`, { headers })
  job = await poll.json()
}

console.log(job.result.text)
```

## Онлайн-режим

WebSocket: PCM-поток на вход, события текста на выход.

`WS /v1/realtime?model=uzasr-large`

| Событие | Направление | Описание |
| --- | --- | --- |
| `ready` | ← сервер | Сессия открыта, указана точная версия модели. |
| `partial` | ← сервер | Текущая фраза. Поле `stable` не изменится, остальное может уточниться. |
| `final` | ← сервер | Фраза зафиксирована после паузы 0,8 с, с пунктуацией. |
| `event` | ← сервер | Звуковое событие, через 1–1,5 с после его окончания. |
| `done` | ← сервер | Сессия закрыта, `audio_seconds` для учёта. |
| `binary` | → клиент | PCM s16le, 16 кГц, моно. Рекомендуемый размер кадра 100 мс. |
| `stop` | → клиент | `{"type":"stop"}`: зафиксировать последнюю фразу и закрыть сессию. |

```text
wss://api.uzasr.uz/v1/realtime?model=uzasr-large

← { "type": "ready" }
→ <binary PCM s16le 16 kHz, 100 ms>
← { "type": "partial", "utterance": 0, "start": 0.0,
    "text": "Assalomu alaykum qanday", "stable": "Assalomu alaykum" }
← { "type": "final", "utterance": 0, "start": 0.0, "end": 2.4,
    "text": "Assalomu alaykum, qanday yordam bera olaman?" }
→ { "type": "stop" }
← { "type": "done", "audio_seconds": 2.9 }
```

Браузер не передаёт заголовки в WebSocket: подключайтесь со своего сервера и проксируйте поток.

## Модели

Псевдоним всегда указывает на актуальную версию.

`GET /v1/models`

| Модель | Параметры | WER | Когда выбирать |
| --- | --- | --- | --- |
| `uzasr-large` | 600M | 8,1% | По умолчанию, максимальная точность |
| `uzasr-base` | 220M | 9,4% | Большие объёмы, ниже стоимость |

- [Модель 600M на Hugging Face](https://huggingface.co/rustam1221/UzASR-CTC-600M)
- [Модель 220M на Hugging Face](https://huggingface.co/rustam1221/UzASR-CTC-220M)

## Ошибки

Единый формат, совместимый с OpenAI.

| HTTP | code | Что произошло |
| --- | --- | --- |
| 400 | `unsupported_parameter` | Неизвестный параметр формы. |
| 400 | `unsupported_language` | Параметр `language` отличается от `uz`. |
| 400 | `audio_unreadable` | Не удалось декодировать аудио. |
| 400 | `audio_too_long` | Запись длиннее лимита эндпоинта. Используйте `/v1/jobs`. |
| 401 | `invalid_api_key` | Ключ не передан, неверен или отозван. |
| 404 | `model_not_found` | Такой модели нет. Список: `GET /v1/models`. |
| 413 | `file_too_large` | Файл больше лимита. |
| 429 | `concurrency_limit_exceeded` | Превышено число параллельных запросов. Смотрите `Retry-After`. |
| 503 | `model_unavailable` | Модель запускается или на обслуживании. Повторите через минуту. |
| 504 | `timeout` | Распознавание не уложилось в срок. Для длинных файлов используйте `/v1/jobs`. |

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "unsupported_language",
    "message": "Only 'uz' is supported"
  }
}
```

## Лимиты

Значения по умолчанию. Для корпоративных клиентов настраиваются.

| Ограничение | Значение |
| --- | --- |
| Синхронный запрос | до 10 минут аудио |
| Асинхронная задача | до 3 часов аудио |
| Параллельные запросы на ключ | 8 |
| Онлайн-сессия | до 5 минут |
| Хранение результата задачи | 7 дней |
