POST /v1/audio/speech
Озвучка текста (TTS) — текст на входе, готовый аудиофайл на выходе. OpenAI-совместимый формат, работает официальный openai SDK.
Превращает текст в речь: озвучка статей и рассылок, голосовые ответы бота, аудиоверсии инструкций, реплики персонажей. OpenAI-совместимый формат — без переписывания работают openai Python/TypeScript SDK и любые клиенты под OpenAI Audio API: достаточно поменять base_url.
Ответ — сами байты аудио, а не JSON: сохраняйте их в файл или отдавайте плееру. Стрима по флагу нет — один POST возвращает один готовый ответ целиком (его можно читать потоково, но это всё тот же единственный ответ). Модели озвучки — в каталоге (фильтр «Озвучка») или запросом GET /v1/models?output_modalities=speech: MAI Voice, Aura-2, MiniMax Speech, Kokoro, Orpheus, Zonos, Voxtral TTS и другие.
Эндпоинт
https://api.hubris.pw/v1/audio/speechЗаголовки:
| Header | Значение |
|---|---|
Authorization | Bearer sk-gw-... (обязательно) |
Content-Type | application/json |
Тело запроса
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
model | string | — | ID модели озвучки из каталога. |
input | string | — | Текст для озвучки, до 50 000 символов. Тарифицируется по количеству символов. |
voice | string | — | Голос модели. Обязателен, у каждой модели свой набор — см. ниже. |
response_format | string | зависит от модели | mp3 или pcm (16-bit LE). Если поле не передать, формат выбирает модель — как правило, это pcm; для файла на диске указывайте mp3 явно. |
Дополнительные поля, которые понимает конкретная модель (например, скорость речи или инструкции по стилю), передаются ей как есть — схема их не отбрасывает. Тело запроса ограничено 1 МБ: это текст, а не медиа, лимита хватает с большим запасом.
Голоса
voice обязателен, и наборы голосов у моделей не пересекаются: где-то их два, где-то девяносто. Список голосов конкретной модели — в её карточке в каталоге. Часть провайдеров список не публикует — тогда берите значение из документации самой модели.
Если голос не из списка, который знает каталог, запрос отклоняется до обращения к модели — с кодом 400, code: "voice_not_supported", и в тексте ошибки перечислены доступные значения:
{
"error": {
"message": "Голос «нет-такого» недоступен у модели microsoft/mai-voice-2-flash. Доступные: en-US-Harper:MAI-Voice-2, …",
"type": "invalid_request_error",
"code": "voice_not_supported"
}
}Если голосов у модели много, в сообщении перечислены первые восемь и число оставшихся. За отклонённый таким образом запрос ничего не списывается.
Минимальный пример
Модель и голос всегда идут парой: сменили модель — берите голос из её карточки в каталоге.
curl -s https://api.hubris.pw/v1/audio/speech \-H "Authorization: Bearer sk-gw-..." \-H "Content-Type: application/json" \-d '{ "model": "microsoft/mai-voice-2-flash", "voice": "en-US-Harper:MAI-Voice-2", "input": "Привет! Это проверка озвучки.", "response_format": "mp3"}' \--output speech.mp3from openai import OpenAIclient = OpenAI( base_url="https://api.hubris.pw/v1", api_key="sk-gw-...",)# Модель и голос — из карточки модели в каталоге /modelsMODEL = "microsoft/mai-voice-2-flash"VOICE = "en-US-Harper:MAI-Voice-2"with client.audio.speech.with_streaming_response.create( model=MODEL, voice=VOICE, input="Привет! Это проверка озвучки.", response_format="mp3",) as response: response.stream_to_file("speech.mp3")import fs from "node:fs";import OpenAI from "openai";const client = new OpenAI({baseURL: "https://api.hubris.pw/v1",apiKey: "sk-gw-...",});// Модель и голос — из карточки модели в каталоге /modelsconst MODEL = "microsoft/mai-voice-2-flash";const VOICE = "en-US-Harper:MAI-Voice-2";const speech = await client.audio.speech.create({model: MODEL,voice: VOICE,input: "Привет! Это проверка озвучки.",response_format: "mp3",});fs.writeFileSync("speech.mp3", Buffer.from(await speech.arrayBuffer()));Python-вариант с with_streaming_response читает тот же единственный ответ по мере поступления байтов — это удобно для длинного текста, но никакого пофразового стрима, как в чате, здесь нет.
Ответ
200 — тело целиком состоит из байтов аудио. JSON в успешном ответе не приходит, поэтому и поля usage в нём нет: расход по запросу смотрите в журнале.
| Заголовок | Значение |
|---|---|
Content-Type | audio/mpeg при response_format: "mp3", audio/pcm при pcm. |
Content-Length | Размер аудио в байтах. |
Cache-Control | no-store — ответ не кэшируется. |
pcm — это сырые сэмплы без заголовка WAV: их либо скармливают плееру/микшеру напрямую, либо оборачивают в контейнер. Если нужен файл, который просто откроется двойным кликом, берите mp3.
Ошибки, наоборот, всегда приходят JSON-ом в обычном формате — по Content-Type ответа легко отличить одно от другого.
HTTP-коды
| Код | code | Когда |
|---|---|---|
200 | — | Успех, в теле — аудио. |
400 | voice_not_supported | Голос не входит в набор этой модели (в тексте ошибки — доступные). |
400 | invalid_request | Тело не соответствует схеме: нет model, input или voice, неизвестный response_format, невалидный JSON. |
401 | — | Ключ отсутствует / отозван / невалиден. |
402 | insufficient_balance | Недостаточно средств на балансе. |
404 | model_not_found | Модели с таким model не существует или это не модель озвучки. |
413 | request_too_large | Тело запроса больше 1 МБ. |
429 | — | Превышен дневной лимит на ключе. |
502 | pricing_unavailable | У модели нет ставки в каталоге — считать стоимость нечем, запрос не выполняется. |
502 | no_audio_returned | Модель ответила успехом, но пустым телом. Списания нет, запрос стоит повторить. |
502 / 503 / 504 | — | Транзиентные сбои провайдера, курса ЦБ или таймаут озвучки. |
Формат тела ошибки и стратегия retry — Ошибки.
Биллинг
Озвучка тарифицируется за символы входного текста — не за токены и не за длительность получившегося аудио. Стоимость поэтому известна заранее: сколько символов отправили, столько и оплатили, независимо от того, насколько неторопливо модель их проговорила. Ставка каждой модели видна в её карточке в каталоге.
Списание — по факту выполненного запроса, в рублях; сумма попадает в журнал запросов вместе с моделью и временем. За отклонённые до обращения к модели запросы (voice_not_supported, model_not_found, невалидное тело) и за пустой ответ модели не списывается ничего.
Что дальше
- POST /v1/audio/transcriptions — обратная задача: запись в текст.
- Аудио в чате — модель рассуждает о записи или отвечает голосом внутри диалога.
- Модели — как выбрать модель и прочитать её карточку.
- Ошибки — что делать на 429/502.
- Цены — как считается стоимость запроса.
Обновлено: