Аудио: распознавание и синтез речи

Speech to text (STT), text to speech (TTS), MP3 и PCM

Используйте API-ключ с областью gateway:write, разрешённой моделью и положительным балансом организации.

bash
export BASE_URL="https://api.kodikrouter.ru"
export KODIK_API_KEY="YOUR_API_KEY"
export STT_MODEL="openai/whisper-1"

Speech to text — распознавание речи

POST /v1/audio/transcriptions распознаёт аудио и возвращает JSON с text и usage. Принимается JSON с input_audio.data (base64) и input_audio.format либо multipart/form-data с file и model. Поддерживаются response_format: json и verbose_json; язык, температура и временные метки зависят от модели. Лимит всего тела запроса по умолчанию — 2 000 000 байт, включая base64 или multipart; большие записи разбивайте на части.

bash
curl --fail-with-body "$BASE_URL/v1/audio/transcriptions" \
  -H "Authorization: Bearer $KODIK_API_KEY" \
  -F "model=$STT_MODEL" \
  -F "file=@sample.wav"

Text to speech — синтез речи

POST /v1/audio/speech принимает model, input (до 4096 символов), voice, response_format (mp3 или pcm, по умолчанию mp3) и необязательный speed (0.25–4). Голос и формат должны поддерживаться моделью. По умолчанию ответ синхронный — аудиобайты, а не JSON. Шлюз буферизует до 32 МиБ перед выдачей и проверяет стоимость; потоковая выдача во время генерации и SSE пока не поддерживаются.

Для долгого синтеза используйте Prefer: respond-async: сервер вернёт JSON задания с HTTP 202. Пример ниже сохраняет прежний синхронный режим.

bash
curl --fail-with-body "$BASE_URL/v1/audio/speech" \
  -H "Authorization: Bearer $KODIK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"microsoft/mai-voice-2","input":"Hello world","voice":"en-US-Harper:MAI-Voice-2","response_format":"mp3"}' \
  -D speech.headers --output speech.mp3
Модели, стоимость и конфиденциальность
Используйте каталог /v1/catalog/models?api_surfaces=transcriptions или api_surfaces=speech. Для загрузки моделей выполните синхронизацию каталога в админке. Поддержаны подключения OpenRouter/KodikRouter; chat-модель с audio не обязательно поддерживает отдельный аудио-API. Стоимость берётся из usage.cost для транскрипции и данных генерации провайдера для речи, затем конвертируется в рубли с наценкой шлюза. В синхронном режиме метаданные стоимости ожидаются до 60 секунд; если они не получены, возвращается 502. В async-режиме звук сохраняется, а получение стоимости повторяется до завершения или истечения срока задания, без повторного синтеза. Исходное аудио отправляется провайдеру без удаления PII. При включённом маскировании обнаруженные PII в тексте синтеза или подсказке транскрипции отклоняются до отправки: восстановить плейсхолдеры внутри звука нельзя. Provider options в этом режиме недоступны.

В синхронном режиме передавайте Idempotency-Key: завершённый ответ хранится 24 часа, повтор с другим телом или одновременно выполняющийся запрос возвращает 409. Ошибка не сохраняется как успешный ответ: повтор после 502 может вызвать новую платную генерацию. Для аудио-чата по-прежнему используйте /v1/chat/completions.

Голоса для выбранной модели

GET
/v1/audio/voices?model={model}

Голоса из каталога; без model возвращаются доступные ключу speech-модели

bash
curl --fail-with-body -sS --get "$BASE_URL/v1/audio/voices" \
  -H "Authorization: Bearer $KODIK_API_KEY" \
  --data-urlencode "model=minimax/speech-2.8-hd"
json
{
  "data": [{
    "model": "minimax/speech-2.8-hd",
    "supported_voices": ["English_expressive_narrator"],
    "source": "provider_catalog"
  }]
}

Пример сокращён: фактический список зависит от синхронизированного каталога. Выберите ID из supported_voices и передайте его в voice. supported_voices: null и source: unavailable означают, что каталог не содержит списка, а не отсутствие поддержки голосов. API-ключу нужен scope gateway:read или gateway:write; применяется allowlist моделей. Также доступен /audio/voices. Пользовательские ID передаются провайдеру без изменения; доступ к приватным голосам ElevenLabs не гарантирован. Создание и клонирование голосов этим endpoint не поддерживаются.

Асинхронная генерация: 202 → polling → результат

Для долгих запросов добавьте Prefer: respond-async. Сервер сохранит задание и вернёт 202 Accepted с JSON, а не готовый результат. Клиент должен опрашивать статус и скачивать результат после завершения. Закрытие соединения не отменяет сохранённое задание.

Совместимость с синхронным API
Без заголовка сохраняется прежний синхронный ответ: JSON для изображений и аудиобайты для речи. Такой запрос всё ещё зависит от таймаутов сети и прокси. Async доступен только для POST /v1/images, /v1/images/generations и /v1/audio/speech, включая пути без /v1. Он не включает async для /v1/chat/completions или транскрипции и не является SSE-стримингом.

Пример использует Bash, curl и jq. Выберите доступную вашему ключу модель; для речи также проверьте голос через /v1/audio/voices. Сохраните Idempotency-Key и тело запроса до отправки. Для нового намеренного задания задайте новый ключ; для повтора того же запроса оставьте прежний.

bash
export BASE_URL="https://api.kodikrouter.ru"
export IDEMPOTENCY_KEY="speech-request-001"
# KODIK_API_KEY должен быть задан в окружении.
(
  set -e
  HTTP_CODE=$(curl --fail-with-body -sS --max-time 30 \
    "$BASE_URL/v1/audio/speech" \
    -H "Authorization: Bearer $KODIK_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Prefer: respond-async" \
    -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
    --data '{"model":"minimax/speech-2.8-hd","input":"Hello world","voice":"English_expressive_narrator","response_format":"mp3"}' \
    --output job.json --write-out '%{http_code}')
  test "$HTTP_CODE" = "202" || { cat job.json; exit 1; }
  jq -er '.id' job.json > job-id.txt
  cat job.json
)

Ответ содержит id, status, polling_url, expires_at и request_id; content_url появляется при completed. Заголовки ответа: Location, Retry-After, Preference-Applied: respond-async и X-KodikRouter-Request-Id.

GET
/v1/media/jobs/{id}

Статус задания; используйте тот же API-ключ, что и при отправке

GET
/v1/media/jobs/{id}/content

Готовый результат: JSON изображения или аудиобайты речи

Выполните следующий блок после получения job-id.txt. При временном сбое повторяйте этот блок с сохранённым ID, а не создавайте новое задание. При 429 соблюдайте Retry-After.

bash
(
  set -e
  JOB_ID=$(cat job-id.txt)
  for attempt in $(seq 1 180); do
    curl --fail-with-body -sS --max-time 30 \
      "$BASE_URL/v1/media/jobs/$JOB_ID" \
      -H "Authorization: Bearer $KODIK_API_KEY" --output status.json
    STATUS=$(jq -er '.status' status.json)
    echo "$STATUS"
    case "$STATUS" in
      completed)
        curl --fail-with-body -sS --max-time 120 \
          "$BASE_URL/v1/media/jobs/$JOB_ID/content" \
          -H "Authorization: Bearer $KODIK_API_KEY" \
          --output speech.mp3
        file speech.mp3
        exit 0 ;;
      failed|needs_reconciliation|expired)
        cat status.json
        exit 1 ;;
      queued|running|billing_pending|billing) sleep 10 ;;
      *) cat status.json; exit 1 ;;
    esac
  done
  echo "Ожидание завершено. Сохраните job-id.txt и продолжите polling позже."
  exit 1
)

queued — в очереди; running — генерация; billing_pending / billing — ожидание или обработка стоимости; completed — результат доступен. В billing_pending звук уже может быть сохранён: сервер повторяет получение стоимости, не синтез. failed означает отказ, а needs_reconciliation — неизвестный исход платного запроса: обратитесь в поддержку с ID задания и request_id, не запускайте генерацию автоматически повторно.

Повторы и срок хранения
Сохраняйте Prefer: respond-async, тот же API-ключ, Idempotency-Key и тело при каждом повторе, в том числе если ответ 202 потерян. Повтор возвращает прежнее задание; другое тело с тем же ключом даёт 409. Не переключайтесь на sync для повтора. Результаты хранятся по умолчанию 24 часа после отправки — ориентируйтесь на expires_at и скачивайте их вовремя. После истечения срока возвращается 410; до готовности /content возвращает 409.

Дневной или месячный лимит расходов блокирует новые генерации (402), но не polling и скачивание уже оплаченного результата. Ключ должен оставаться активным и неистёкшим; scope gateway:write, доступ к модели и rate limit продолжают действовать. Другой ключ не получает доступ к заданию. Повторные GET не запускают генерацию и не списывают стоимость задания снова. Async не отменяет сетевые таймауты скачивания больших файлов.

Транскрипция через JSON

json
{
  "model": "openai/whisper-1",
  "input_audio": {"data": "BASE64_AUDIO", "format": "mp3"},
  "response_format": "json"
}

В поле data передайте base64-содержимое записи. Пример структуры успешного ответа; значения зависят от записи и модели:

json
{"text":"Hello world.","usage":{"cost":0.001,"input_tokens":10,"output_tokens":3}}

Проверка результата и ошибки

Для синхронного speech или скачивания завершённого async-задания ожидайте HTTP 200, Content-Type: audio/mpeg для MP3 или audio/pcm для PCM и X-Generation-Id. При async-отправке ожидайте 202 с JSON задания, не сохраняйте его как MP3. MP3 можно открыть аудиоплеером; PCM — необработанные аудиоданные.

При ошибке возвращается JSON, а не аудиофайл. speech_generation означает ошибку получения звука; speech_billing — ошибку получения стоимости. Сохраните X-KodikRouter-Request-Id и X-Generation-Id для диагностики.

bash
cat speech.headers
file speech.mp3