
## Расшифровать аудио

`POST /v1/audio/transcriptions`

Преобразует аудиофайл в текст через Whisper Large v3 Turbo — без подключения BYOK. Расшифровка учитывается в AI-квоте портала по длительности аудио: в рамках квоты тарифа деньги с баланса не списываются, сверх квоты расход списывается с денежного баланса портала по базовой цене модели. Принимает файл через `multipart/form-data`. Формат запроса и ответа совместим с `POST /v1/audio/transcriptions` из OpenAI API.

> **Ответ приходит в сыром OpenAI-формате.**
>
> Обёртки `{success, data}`, которая используется в остальных эндпоинтах Вайбкод — `/v1/deals`, `/v1/tasks` и других, — здесь нет.
>
> Так сделано для совместимости с OpenAI SDK. Если у вас единый клиент с проверкой `if (!response.success)`, добавьте для AI Router исключение.

## Поля запроса (form-data)

| Поле | Тип | Обяз. | По умолч. | Описание |
|------|-----|:-----:|-----------|----------|
| `file` | file | да | — | Аудиофайл. Часть multipart обязана называться именно `file`. Отсутствующий параметр `filename` и пустое значение `filename=""` принимаются и заменяются именем `audio` без расширения — одинаково для `Content-Type: audio/mpeg` и `Content-Type: application/octet-stream`. Расширение в имени НЕ проверяется: контейнер определяет распознавание по содержимому, поэтому имя без расширения и редкие форматы диктофонов принимаются наравне с `mp3`, `mpeg`, `mpga`, `mp4`, `m4a`, `wav`, `ogg`, `oga`, `flac`, `webm`, `opus`, `aac`, `amr`, `3gp`, `3gpp`, `wma` — эти перечислены потому, что для них мы отдаём распознаванию подсказку о формате, когда её нет в самом запросе. Файл, который распознавание прочитать не смогло, возвращается как `ai_provider_rejected`. Максимальный размер — 25 МБ |
| `model` | string | нет | `deepdml/faster-whisper-large-v3-turbo-ct2` | ID Whisper-модели. Префикс `bitrix/` опционален и автоматически удаляется |
| `language` | string | нет | автоопределение | Код языка по `ISO 639` (2-3 буквы): `ru`, `en`, `de`, `fr`, `zh` и т. п. Указание языка ускоряет распознавание |
| `prompt` | string | нет | — | Контекстная подсказка: тема разговора, стиль, правильное написание терминов. До 2000 символов, модель учитывает последние ~224 токена |
| `hotwords` | string | нет | — | Спец-слова и термины через запятую — повышают точность распознавания редких названий и брендов. До 500 символов |
| `temperature` | number | нет | `0` | Температура декодера от `0` до `1`. `0` — детерминированный результат, выше — больше вариативности. Значения вне диапазона отклоняются |
| `vad_filter` | boolean | нет | — | `true` включает VAD-фильтр: модель вырезает тишину перед распознаванием — меньше галлюцинаций на записях с паузами |
| `timestamp_granularities[]` | string | нет | `segment` | Детализация таймстампов: `word` или `segment` (поле можно повторять). Только с `response_format=verbose_json`. С `word` каждый сегмент дополняется массивом `words` с таймингом и вероятностью каждого слова |
| `response_format` | string | нет | `json` | Формат результата: `json`, `text`, `srt`, `vtt`, `verbose_json` |

## Примеры

### curl — личный ключ

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/audio/transcriptions \
  -H "X-Api-Key: YOUR_API_KEY" \
  -F "file=@call-recording.mp3" \
  -F "language=ru" \
  -F "response_format=json"
```

### curl — OAuth-приложение

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/audio/transcriptions \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -F "file=@call-recording.mp3" \
  -F "language=ru" \
  -F "response_format=json"
```

### JavaScript — личный ключ

```javascript
const formData = new FormData()
formData.append('file', audioFile)  // объект File или Blob
formData.append('language', 'ru')
formData.append('response_format', 'json')

const res = await fetch('https://vibecode.bitrix24.tech/v1/audio/transcriptions', {
  method: 'POST',
  headers: { 'X-Api-Key': 'YOUR_API_KEY' },
  body: formData,
})

const result = await res.json()
console.log('Распознанный текст:', result.text)
```

### JavaScript — OAuth-приложение

```javascript
const formData = new FormData()
formData.append('file', audioFile)
formData.append('language', 'ru')

const res = await fetch('https://vibecode.bitrix24.tech/v1/audio/transcriptions', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
  },
  body: formData,
})

const result = await res.json()
console.log('Текст:', result.text)
```

## Поля ответа

Структура ответа зависит от `response_format`. Формат `json` (по умолчанию) — самый компактный, `verbose_json` — с таймингами.

### `response_format: json`

| Поле | Тип | Описание |
|------|-----|----------|
| `text` | string | Распознанный текст целиком |

### `response_format: text`

Ответ — обычная строка с распознанным текстом, без `JSON`-обёртки.

### `response_format: verbose_json`

| Поле | Тип | Описание |
|------|-----|----------|
| `task` | string | Тип задачи: `transcribe` |
| `language` | string | Код определённого или указанного языка |
| `duration` | number | Длительность аудио в секундах |
| `text` | string | Распознанный текст целиком |
| `usage` | object \| null | Метаданные использования модели. `null`, если модель их не возвращает |
| `words` | array | Пословные тайминги на уровне всего ответа. Пустой массив, если пословные тайминги не запрошены |
| `segments` | array | Сегменты с таймингами и метаданными распознавания |
| `segments[].id` | number | Порядковый номер сегмента |
| `segments[].seek` | number | Внутренний сдвиг окна распознавания Whisper |
| `segments[].start` | number | Начало сегмента в секундах |
| `segments[].end` | number | Конец сегмента в секундах |
| `segments[].text` | string | Текст сегмента |
| `segments[].tokens` | array | Токены модели для текста сегмента (целые числа) |
| `segments[].temperature` | number | Температура декодирования, применённая моделью |
| `segments[].avg_logprob` | number | Средняя логарифмическая вероятность токенов сегмента — метрика уверенности |
| `segments[].compression_ratio` | number | Коэффициент сжатия текста сегмента |
| `segments[].no_speech_prob` | number | Вероятность того, что в сегменте нет речи |
| `segments[].words` | array | Пословные тайминги внутри сегмента. Пустой массив, если не запрошены |
| `segments[].emotion` | string \| null | Определённая эмоция сегмента. `null`, если не определена |

### `response_format: srt` / `vtt`

Ответ — субтитры в формате `SubRip Text` или `WebVTT`.

## Пример ответа

`response_format: json`:

```json
{
  "text": "Здравствуйте, ООО Вектор. Хотим CRM на 50 пользователей, бюджет до 500 тысяч в месяц."
}
```

`response_format: verbose_json`:

```json
{
  "task": "transcribe",
  "language": "ru",
  "duration": 8.42,
  "text": "Здравствуйте, ООО Вектор. Хотим CRM на 50 пользователей, бюджет до 500 тысяч в месяц.",
  "usage": null,
  "words": [],
  "segments": [
    {
      "id": 0,
      "seek": 0,
      "start": 0.0,
      "end": 3.2,
      "text": "Здравствуйте, ООО Вектор.",
      "tokens": [50365, 2425, 11, 341, 307],
      "temperature": 0.0,
      "avg_logprob": -0.38,
      "compression_ratio": 1.12,
      "no_speech_prob": 0.02,
      "words": [],
      "emotion": null
    },
    {
      "id": 1,
      "seek": 320,
      "start": 3.2,
      "end": 8.42,
      "text": "Хотим CRM на 50 пользователей, бюджет до 500 тысяч в месяц.",
      "tokens": [50414, 1003, 13767, 295, 1500],
      "temperature": 0.0,
      "avg_logprob": -0.41,
      "compression_ratio": 1.20,
      "no_speech_prob": 0.01,
      "words": [],
      "emotion": null
    }
  ]
}
```

## Пример ответа при ошибке

`400 no_file` — поле `file` не передано:

```json
{
  "error": {
    "message": "Audio file is required. Send as multipart/form-data with field \"file\".",
    "type": "invalid_request_error",
    "code": "no_file"
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|----------|
| 400 | `no_file` | Поле `file` не передано в `multipart/form-data`, либо файловая часть названа иначе (например `audio`). Размер такой части на ответ не влияет: имя читается из заголовка части, до содержимого |
| 400 | `empty_file` | Поле `file` передано, но файл пустой (0 байт). Частая причина: `curl -F "file=path"` без префикса `@` — curl отправляет строку пути вместо содержимого файла |
| 400 | `invalid_prompt` | Поле `prompt` длиннее 2000 символов |
| 400 | `invalid_hotwords` | Поле `hotwords` длиннее 500 символов |
| 400 | `invalid_temperature` | Поле `temperature` не число или вне диапазона 0..1 |
| 400 | `invalid_vad_filter` | Поле `vad_filter` не `true` и не `false` |
| 400 | `invalid_timestamp_granularities` | Значение не `word`/`segment`, либо формат ответа не `verbose_json` |
| 400 | `invalid_language` | Код языка не соответствует формату `ISO 639` (2-3 буквы) |
| 400 | `ai_provider_rejected` | Сервис распознавания отклонил само содержимое запроса (его ответ — 400 или 422). Поле `providerStatusCode` несёт исходный статус. Повторять запрос без изменений бессмысленно |
| 402 | `ai_credentials_not_configured` | На платформе не настроен Bitrix-провайдер |
| 402 | `insufficient_balance` | PREPAY-счёт ушёл за овердрафт — проверяется ДО вызова распознавания (только PLATFORM/PORTAL-ключи, BYOK бесплатен и не проверяется). Полностью замороженный счёт отклоняется раньше кодом `ACCOUNT_FROZEN` |
| 402 | `ai_quota_exhausted` | Месячная AI-квота портала исчерпана — см. «Известные особенности» ниже |
| 402 | `company_budget_exhausted` | Исчерпан месячный бюджет расхода компании, который задаёт администратор портала. Поле `scope` называет пробитый бюджет: `USER` — личный бюджет вызывающего, `PORTAL` — бюджет всего портала. Поле `canRequest` говорит, можно ли запросить увеличение: у личного бюджета `true`, у портального `false` — его поднимает только администратор. Отказ приходит только на вызовах, которые списывают средства с баланса портала. Расшифровка внутри тарифной квоты, ключом подписки Cowork/Code и на собственном ключе продолжает работать |
| 403 | `scope_missing` | API-ключу не хватает скоупа `vibe:ai` |
| 413 | `request_too_large` | Превышен один из лимитов multipart: файл больше 25 МБ, либо в запросе больше 16 текстовых полей или 24 частей, либо одно поле больше 64 КБ |
| 502 | `ai_provider_unavailable` | Сервис распознавания недоступен либо вернул ошибку авторизации или внутреннюю ошибку. Отказ по содержимому запроса приходит отдельным кодом `ai_provider_rejected` |
| 503 | `ai_provider_timeout` | Whisper не ответил в течение 15 минут — файл слишком длинный или сервис перегружен. Для записей длиннее ~30 минут разбивайте на части |
| 401 | `MISSING_API_KEY` | Не передан заголовок `X-Api-Key` |
| 429 | `ai_provider_cooldown` | Кластер распознавания временно недоступен, и платформа держит паузу, чтобы повторы его не добивали. Запрос не выполнялся, списания нет — повторите его через число секунд из `Retry-After`. Заголовков `X-RateLimit-Scope` и `X-AI-Admission` у этого ответа нет |
| 429 | `ai_congested` | Пул AI-кластера перегружен. Запрос не выполнялся, списания нет, повторите его по заголовку `Retry-After`. Ответ несёт заголовок `X-AI-Admission: shed`, а не `X-RateLimit-Scope` |
| 429 | `ai_pacing_limited` | Превышено суточное или недельное окно равномерного расходования квоты. Это не исчерпание квоты — повторите запрос по заголовку `Retry-After`. Подробнее — [«Равномерное расходование»](/docs/ai/consumption/quota#равномерное-расходование-pacing) |

Полный список общих ошибок API — [Ошибки](/docs/errors).

## Известные особенности

**Подсказки повышают точность на редких терминах.** Поле `prompt` задаёт контекст — тему разговора, стиль, правильное написание терминов. Поле `hotwords` перечисляет через запятую спец-слова, на которые декодер получает повышенный приоритет. Разница на фразе «обсуждаем интеграцию NeuralDeep с Kimi и подход RAG»:

| Запрос | Результат |
|---|---|
| Без подсказок | «…интеграцию нейролдипскими и подход рак для поиска…» |
| С `hotwords` | «…интеграцию NeuralDeep с Kimi и подход RAG для поиска…» |


**В рамках квоты — без списаний, сверх квоты — по базовой цене модели.** Whisper работает на инфраструктуре Битрикс24. Расшифровка тарифицируется по длительности аудио и учитывается в месячной AI-квоте портала. Пока расход укладывается в квоту тарифа, деньги с баланса портала не списываются. Сверх квоты расход списывается с денежного баланса портала по базовой цене модели из каталога. На порталах с включённым контролем квоты запрос может вернуть `402 ai_quota_exhausted`, когда месячный лимит исчерпан. Поле `reason` различает три случая: `breaker` — сработал часовой предохранитель расходов сверх квоты, `wallet_empty` — квота исчерпана и на балансе портала нет средств, `wallet_off` — расход сверх квоты для портала недоступен. Поле `resetAt` — момент, когда запросы снова начнут проходить, для `wallet_off` оно может отсутствовать. В ветке `wallet_empty` ответ может дополнительно нести строку `hint` с подсказкой и ссылку `topupUrl` на пополнение баланса — оба поля появляются, когда на платформе включены принудительный контроль квоты и подсказка о пополнении, поэтому читайте их как необязательные. Поле `hint` в этом ответе — строка.

**Ключ подписки Cowork/Code считается отдельно.** Вызов ключом со скоупом `vibe:cowork` в AI-квоту аккаунта не попадает. Если администратор платформы назначил модели расшифровки цену за минуту аудио, такой вызов расходует квоту подписки: при исчерпании окна ручка отвечает `402` с кодом `cowork_quota_exhausted`, заголовком `Retry-After` и полями `window` (`5h`, `week` или `month`), `resetAt` и `nextTier`. Пока цена не назначена, расшифровка таким ключом не тарифицируется. Форматы `text`, `srt` и `vtt` длительность не возвращают, поэтому тарифицируются по цене за вызов.

**Лимит размера — 25 МБ.** Файл сверх лимита отклоняется целиком — `413 request_too_large`, списания нет. Для длинных записей разбивайте файл на части до 25 МБ и склеивайте результаты на стороне клиента. Один час `mp3` 128 кбит/с примерно 60 МБ — придётся резать.

**Тайм-аут запроса — 15 минут.** Расшифровка длится примерно 5-15 % от длины аудио. Файл на 5 минут (5-7 МБ `mp3`) расшифровывается за 15-45 секунд. При превышении тайм-аута возвращается `503 ai_provider_timeout`.

## Смотрите также

- [Распознавание речи](/docs/ai/audio)
- [Создать чат-комплишен](/docs/ai/chat/completions)
- [AI Router](/docs/ai)
