Перейти к содержимому
tonvio

API озвучки текста

API озвучки текста: весь контракт — до регистрации

У tonvio есть публичный HTTP API для синтеза речи и клонирования голоса. Эта страница — полный справочник: ручки, поля запроса, лимиты, коды ошибок и машинная спецификация OpenAPI 3.1. Ничего не спрятано за логином, поэтому инженер может оценить интеграцию раньше, чем кто-нибудь заведёт аккаунт.

API работает на том же движке, что и кабинет: те же голоса и возможности генерации, та же очередь, тот же готовый MP3.

Базовый адрес

Все пути ниже — относительно этого адреса. API говорит только на JSON; исключение одно — multipart-ручка для загрузки образца голоса.

https://api.tonvio.net/api/v1

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

Передайте ключ как Bearer-токен в заголовке Authorization или в заголовке X-API-Key — это равнозначно. Ключ — серверный секрет: кто им владеет, тот тратит ваши кредиты, поэтому во фронтенд его класть нельзя. Ключи создаются в кабинете и показываются целиком ровно один раз.

Authorization: Bearer tvk_your_key_here
# or
X-API-Key: tvk_your_key_here

Как устроена интеграция

API асинхронный по замыслу. Синтез длинного текста занимает минуты, а HTTP-запрос, висящий минутами, умирает где-то посередине — в прокси, балансировщике или CDN. Поэтому сервер принимает задачу, отвечает сразу, а клиент опрашивает.

  1. 01

    Создайте задачу

    POST /speech с текстом и id голоса. Ответ — 202 с id задачи и статусом queued. Кредиты в этот момент резервируются, а не списываются.

  2. 02

    Опрашивайте статус

    GET /speech/{id} каждые 2–3 секунды. progress доходит до 100 только вместе со статусом completed — пока задача идёт, потолок 99, поэтому «100%» никогда не значит «почти готово».

  3. 03

    Заберите аудио

    При статусе completed в audio_url лежит ссылка на MP3. Ссылка короткоживущая (около шести часов) — запросите задачу ещё раз и получите свежую.

  4. 04

    Обработайте терминальные исходы

    failed и canceled — тоже конец. При отказе резерв возвращается, а error_code говорит, есть ли смысл повторять. Повтор — это ВСЕГДА новая задача и новое списание.

# 1. Create a job
curl -X POST https://api.tonvio.net/api/v1/speech \
  -H "Authorization: Bearer tvk_your_key_here" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: replace-with-a-unique-request-id" \
  -d '{ "text": "Hello from tonvio!", "voice_id": "el_XXXX", "language": "en" }'
# → 202 { "id": "JOB_ID", "status": "queued", "characters": 18, "queue_position": 1 }

# 2. Poll until completed (every 2-3 seconds)
curl https://api.tonvio.net/api/v1/speech/JOB_ID \
  -H "Authorization: Bearer tvk_your_key_here"
# → { "status": "completed", "progress": 100, "audio_url": "https://cdn.tonvio.net/..." }

# 3. Or read what this key may actually spend and run into
curl https://api.tonvio.net/api/v1/account \
  -H "X-API-Key: tvk_your_key_here"

Ручки

Четырнадцать ручек и один конверт. Любой запрос требует ключа, а у каждого ключа есть набор прав из последней колонки.

МетодПутьЧто делаетПраво
POST/speechСоздать задачу синтеза (асинхронно).speech
GET/speech/{id}Статус задачи и, когда готово, ссылка на аудио.speech:read
GET/speechЗадачи этого ключа, свежие первыми, курсорная пагинация.speech:read
POST/speech/{id}/cancelОстановить задачу; за отрендеренное списывается, остаток возвращается.speech
DELETE/speech/{id}Удалить задачу и её аудио; незавершённую сперва останавливает.speech
GET/voicesКаталог голосов, которыми этот ключ может озвучивать.voices
GET/voices/{id}Один голос по id.voices
GET/voices/{id}/warmГотов ли голос к синтезу.voices
POST/voices/{id}/warmПрогреть голос до первой платной задачи.voices
GET/accountБаланс, действующие лимиты и расход этого ключа.account
POST/voice-clonesЗаказать клон голоса (асинхронно).clone
GET/voice-clones/{id}Состояние заявки и, когда готово, её voice_id.clone
GET/voice-clonesЗаявки этого ключа, курсорная пагинация.clone
DELETE/voice-clones/{id}Снять заявку или удалить клон и освободить слот у провайдера.clone

Права ключа

У каждого ключа свой набор прав. Заводской набор — speech, voices, account: он открывает всё, что API умел и раньше. Право clone выдаётся отдельно. Обращение к ручке без права — 403 insufficient_scope, а в поле required перечислены права, любого из которых хватило бы. Ключу можно назначить и срок: после него любой запрос получает 401 api_key_expired. Свои права и срок ключ читает в GET /account.

ПравоЧто открывает
speechСоздавать и отменять задачи. Покрывает и speech:read.
speech:readТолько читать состояние задач — право для наблюдателя, которому нельзя тратить деньги.
voicesЧитать каталог голосов.
accountЧитать баланс, лимиты и расход.
cloneЗаказывать и удалять клоны голоса. В заводской набор НЕ входит.

POST /speech — тело запроса

Идентификатор голоса нужен один: либо каталожный voice_id, либо идентификатор того же голоса во внешнем каталоге, на котором построен первый провайдер.

ПолеСмысл
textобязательноТекст для озвучки. Тариф ограничивает символы на запрос — действующая цифра в GET /account (limits.max_chars_per_request).
voice_idодно из двухКаталожный id голоса из GET /voices или voice_id готового приватного клона.
elevenlabs_voice_idодно из двухИдентификатор голоса во внешнем каталоге, на котором построен первый провайдер. Провайдеры, не работающие с этим каталогом, его отклоняют.
voice_engineнеобязательноДвижок синтеза. У каталожного голоса берётся его собственный дефолт; у приватного клона дефолта нет — передайте явно. Провайдеры без понятия «движок» поле игнорируют.
modelнеобязательноПсевдоним voice_engine. Если присланы оба, они обязаны совпадать.
languageнеобязательноЯзык синтеза, BCP 47 (ru, en, pt-BR). Там, где голос — это стек клонов по языкам, молчание не означает «пусть решит движок»: это молчаливый выбор. Языки, которыми голос действительно владеет, — в его поле languages.
settingsнеобязательноТонкая настройка: stability (0–1), settings_preset, delay_between_chunks (0–60 с). Незнакомый ключ отвергается, а не игнорируется молча.
idempotency_keyнеобязательноТо же, что заголовок Idempotency-Key. Если присланы оба, они обязаны совпадать.

Что возвращается

202 с id задачи, её статусом, зарезервированными символами и позицией в очереди. Если в тексте была разметка, которую целевой провайдер не понимает, в ответе появится warnings: каждый заменённый или снятый тег со счётчиком. За снятый тег деньги не берутся — это единственное место, где сервер меняет ваш текст, и он всегда об этом говорит.

Кредиты, частота и объёмные квоты

API работает на купленных символьных кредитах: 1 символ = 1 кредит. Подписки и бонусы за него не платят, а читающие запросы баланс не тратят вовсе. Сколько реально доступно — в GET /account (credits.api_spendable_chars); при нуле /speech отвечает 402 insufficient_balance.

  • Частота: по умолчанию 300 запросов в минуту на ключ. Ключу можно назначить свою цифру, а действующая приезжает в заголовках ответа RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset и RateLimit-Policy. Ключ копит запас: короткий всплеск проходит, ровный перебор — 429 rate_limited.
  • Параллельность: сколько задач идёт одновременно, решают тариф и лимиты ключа. По умолчанию это ТЕ ЖЕ слоты, что у кабинета, — они общие. Слотов не осталось — 429 too_many_jobs, а поле scope говорит, чей потолок сработал: account (общий с кабинетом) или key (собственный потолок ключа).
  • Объём: ключу можно назначить суточный и месячный кап символов. Оба считаются в UTC — суточный обнуляется в 00:00 UTC, месячный первого числа. Превышение — 429 quota_exceeded с полями period, used, limit и resets_at; остаток лежит в usage.key.
  • Все действующие цифры GET /account отдаёт вместе с limit_sources — картой, где сказано, откуда каждая взялась: key (назначена на ключе), plan (унаследована от тарифа владельца) или default (служебное значение сервиса, которого нет ни там, ни там).

Идемпотентность

Передайте idempotency_key в теле или заголовок Idempotency-Key — и повтор станет безопасным: тот же ключ с теми же параметрами вернёт исходную задачу, а не создаст (и не оплатит) вторую. Тот же ключ с другими параметрами — 409 idempotency_mismatch; ключ, чей результат уже удалён, — 409 idempotency_key_consumed. Без ключа каждый вызов создаёт новую задачу. У заказа клона заголовок обязателен, и причина грубая: повтор заказа стоит слота у провайдера, а ретраем слот не возвращается.

Клонирование голоса через API

Клон заказывается асинхронно, как и озвучка: POST /voice-clones отвечает 202 с нашим id заявки, а клиент опрашивает GET /voice-clones/{id} не чаще, чем сказано в poll_after_seconds. Сборка голоса занимает минуты, а не секунды. Как только status стал completed, voice_id — это то, что вы передадите в POST /speech.

  • Два режима. Приватный клон собирается из вашего образца (multipart: mp3, wav, m4a или webm, до 15 МБ; формат проверяется по РЕАЛЬНЫМ байтам, а не по имени файла). Языковой вариант каталожного голоса заказывается в JSON — ссылку на файл мы намеренно не принимаем.
  • Языковой вариант — ОБЩИЙ: собранный голос увидят все пользователи сервиса, удалить его нельзя, поэтому заказ требует явного acknowledge_shared: true. Что вы получили на самом деле, всегда сказано в поле visibility — private или shared.
  • consent обязателен в обеих формах: заявление о праве на этот голос длиной 20–500 символов. Оно хранится вместе с заявкой — вне кабинета это единственный след того, кто заявил право. Клонируйте только свой голос или голос, на который у вас есть письменное разрешение.
  • Не каждый провайдер умеет каждый вид клона. Там, где понятия нет, ответ — 501 clone_not_supported_for_provider, причём ДО загрузки файла: ничего не отправлено и ничего не зарезервировано.
  • Приватного клона нет в публичном каталоге, и движка по умолчанию у него тоже нет: при синтезе им передавайте voice_engine явно.
  • DELETE /voice-clones/{id} освобождает слот у провайдера и идемпотентен (204, без тела). Пока провайдер держит заявку, придёт 409 clone_in_progress; общий языковой вариант не удаляется вовсе.
# Order a private clone from your own sample.
# The text fields MUST come before the file, or they never reach the server.
curl -X POST https://api.tonvio.net/api/v1/voice-clones \
  -H "Authorization: Bearer tvk_your_key_here" \
  -H "Idempotency-Key: another-unique-request-id" \
  -F "name=Narrator" \
  -F "consent=I confirm this is my own voice and I may clone it." \
  -F "languages=en,de" \
  -F "[email protected]"
# → 202 { "id": "CLONE_ID", "status": "queued", "visibility": "private",
#         "percent": 0, "poll_after_seconds": 15 }

# Poll until completed, then synthesize with the resulting voice_id.
# A private clone has no default engine, so pass voice_engine explicitly.
curl -X POST https://api.tonvio.net/api/v1/speech \
  -H "Authorization: Bearer tvk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "text": "Hello!", "voice_id": "VOICE_ID", "voice_engine": "ENGINE_ID" }'

Ошибки

У любого отказа один конверт: машинный код error плюс поля, которые несёт именно этот код, и честный HTTP-статус. Ветвитесь по коду, а не по формулировке message. Заголовок ответа X-Request-Id сохраняйте для поддержки. Полный список кодов по каждой ручке — в спецификации OpenAPI; ниже те, что встречаются чаще всего.

КодЧто значит
invalid_requestТело запроса невалидно — смотрите message.
missing_api_keyКлюч не передан.
invalid_api_keyКлюч неизвестен или отозван.
api_key_expiredУ ключа истёк срок.
api_key_frozenУ ключа сняты все права. Обратимо — обратитесь к владельцу сервиса.
insufficient_scopeУ ключа нет права на эту ручку; в required перечислено, чего хватило бы.
insufficient_balanceНе хватает купленных символов — need говорит, сколько стоит запрос.
rate_limitedСлишком часто для этого ключа. Подождите retry_after секунд.
too_many_jobsСвободных слотов нет; scope говорит, чей потолок сработал — account или key.
quota_exceededСуточный или месячный объём ключа исчерпан — см. period, used, limit и resets_at.
voice_not_allowedЭтим ключом такой голос использовать нельзя.
voice_warmingГолос прогревается — повторите чуть позже.
voice_language_not_availableГолос не говорит на запрошенном языке; в available перечислены те, на которых говорит.
voice_engine_requiredУ голоса нет движка по умолчанию — передайте voice_engine явно.
text_too_longТекст длиннее лимита тарифа на один запрос; в max сам лимит.
payload_too_largeТело запроса больше предела; в max_bytes сам предел.
idempotency_mismatchЭтот ключ идемпотентности уже использован с другими параметрами.
generation_unavailableГенерация временно недоступна — повторите после указанной задержки.
internal_errorНеожиданная ошибка сервера. Повторите позже и передайте в поддержку request_id.

Есть и второе семейство отказов — как раз то, с которым интеграция встречается чаще всего: запрос ПРИНЯТ (202), а не сложилось уже потом. Такие приезжают в error_code у GET /speech/{id} со статусом failed: queue_timeout, execution_timeout, provider_unavailable, voice_unavailable, synthesis_failed, assembly_failed, output_too_large и generation_failed на всё прочее. Зарезервированные кредиты возвращаются в каждом из этих случаев, а error_message — английская фраза, которую можно показать человеку.

Машинная спецификация

Весь контракт опубликован одним документом OpenAPI 3.1: каждая ручка, параметр, форма ответа, код ошибки и обе схемы аутентификации. Справочник выше написан из того же источника.

  • Сгенерируйте клиент на своём языке вместо ручных HTTP-вызовов.
  • Импортируйте в HTTP-клиент — все запросы будут заполнены заранее.
  • Отдайте нейросети: это самодостаточное описание сервиса.

Открыть openapi.json

Проверяется по мета-схеме OpenAPI 3.1 при каждом изменении.

Спецификация — обычный JSON: скормите её генератору клиента, HTTP-клиенту или нейросети — и всё заработает.

Что спрашивают инженеры первым делом

Документацию можно читать без аккаунта?

Да — эта страница и спецификация OpenAPI открыты. Аккаунт нужен только чтобы завести ключ и тратить кредиты, поэтому оценить интеграцию, сгенерировать клиент и разобрать контракт ошибок можно раньше, чем кто-нибудь что-нибудь подпишет.

API синхронный?

Синтез длинного текста занимает минуты, а запрос, висящий столько же, умирает в прокси или CDN, поэтому POST /speech сразу возвращает id задачи. Узнать исход можно тремя способами, от простого к удобному: опрашивать GET /speech/{id} не чаще, чем говорит его poll_after_seconds; добавить ?wait=60 и держать запрос открытым до конца работы (long polling); либо задать ключу адрес вебхука и получать события speech.completed и speech.failed. Вебхуки подписаны HMAC-SHA256 и повторяются при сбое, но это сигнал, а не хранилище: ссылка в теле живёт недолго, за свежей идите в GET /speech/{id}.

Сколько стоит вызов API?

Символы. Один символ текста — один кредит, и берутся они только из купленных пакетов: подписки и бонусы за API не платят. Читающие запросы бесплатны. Кредиты резервируются при приёме задачи и возвращаются при отказе или отмене; цены — на странице тарифов.

Какие лимиты по частоте?

По умолчанию 300 запросов в минуту на ключ, с запасом, который гасит короткие всплески; ключу можно назначить свою цифру. Сколько задач идёт одновременно, решают тариф и лимиты ключа, а ещё у ключа могут быть суточный и месячный капы символов. Действующие значения — в заголовках RateLimit-* и в GET /account.

Можно ли ограничить отдельный ключ?

Да. У ключа есть набор прав — например, только чтение статусов для мониторинга, — и ему можно назначить свою частоту, слоты, капы символов, разрешённых провайдеров, квоту клонов и срок действия. Ключ, у которого сняты все права, заморожен и отвечает 403, пока права не вернут.

Как сделать повторы безопасными?

Присылайте Idempotency-Key. Тот же ключ с теми же параметрами вернёт исходную задачу, а не создаст вторую, — и сетевой таймаут не превратится в двойное списание. У заказа клона заголовок обязателен: повтор заказа стоит слота у провайдера.

Голос можно клонировать через API?

Да, если у ключа есть право clone. Загрузите образец, опрашивайте заявку до статуса completed и пользуйтесь полученным voice_id как любым другим голосом. Клонируйте только свой голос или тот, на который у вас есть письменное разрешение: каждый заказ хранит ваше заявление о праве.

Сколько живёт ссылка на аудио?

Около шести часов. Забирайте файл, когда задача завершилась; если ссылка понадобится позже — запросите задачу ещё раз, и выдастся свежая. Готовое аудио хранится и в истории кабинета.

Начните с одного запроса

Заведите ключ в кабинете — первый вызов займёт минуту. Первая озвучка бесплатна, поэтому весь цикл (создать, опросить, скачать) можно проверить, ничего не потратив.

Впервые у нас? Начните с обзора озвучки текста нейросетью.

API озвучки текста — REST TTS API с клонированием голоса | tonvio