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. Поэтому сервер принимает задачу, отвечает сразу, а клиент опрашивает.
- 01
Создайте задачу
POST /speech с текстом и id голоса. Ответ — 202 с id задачи и статусом queued. Кредиты в этот момент резервируются, а не списываются.
- 02
Опрашивайте статус
GET /speech/{id} каждые 2–3 секунды. progress доходит до 100 только вместе со статусом completed — пока задача идёт, потолок 99, поэтому «100%» никогда не значит «почти готово».
- 03
Заберите аудио
При статусе completed в audio_url лежит ссылка на MP3. Ссылка короткоживущая (около шести часов) — запросите задачу ещё раз и получите свежую.
- 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 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 как любым другим голосом. Клонируйте только свой голос или тот, на который у вас есть письменное разрешение: каждый заказ хранит ваше заявление о праве.
Сколько живёт ссылка на аудио?
Около шести часов. Забирайте файл, когда задача завершилась; если ссылка понадобится позже — запросите задачу ещё раз, и выдастся свежая. Готовое аудио хранится и в истории кабинета.
Начните с одного запроса
Заведите ключ в кабинете — первый вызов займёт минуту. Первая озвучка бесплатна, поэтому весь цикл (создать, опросить, скачать) можно проверить, ничего не потратив.
Впервые у нас? Начните с обзора озвучки текста нейросетью.