API озвучки текста
Tonvio API: живая речь для вашего продукта
Один HTTP API, чтобы превращать текст в речь: более 18 000 голосов, теги эмоций, клонирование голоса и вебхуки. Весь справочник открыт — читайте здесь, скачайте его в Markdown или OpenAPI и оцените интеграцию ещё до регистрации.
API работает на том же движке, что и кабинет: те же голоса и возможности генерации, та же очередь, тот же готовый MP3.
Возьмите весь справочник с собой: файл Markdown — для команды или ИИ-ассистента, спецификацию OpenAPI — для генераторов кода и HTTP-клиентов.
Превращайте текст в живую речь по HTTP. Вы отправляете текст и голос — Tonvio возвращает MP3. Задачи выполняются асинхронно: создайте задачу, дождитесь, пока она завершится, и скачайте аудио. Всё, что описано ниже, работает с любым HTTP-клиентом, а весь справочник можно скачать как файл OpenAPI или одним файлом Markdown.
Базовый URL: https://api.tonvio.net/api/v1
Быстрый старт
- 1Создайте ключ API в личном кабинете: API-сервис → Ключи API. Ключ показывается один раз — сохраните его на своём сервере.
- 2Выберите голос через
GET /voicesи скопируйте егоid. - 3Создайте задачу через
POST /speech: передайте текст,voice_idи, если нужно,language.idзадачи придёт сразу. - 4Вызывайте
GET /speech/{id}?wait=60, покаstatusне станетcompleted, а затем скачайтеaudio_url.
# 1. Pick a voice
curl "https://api.tonvio.net/api/v1/voices?language=en&limit=3" \
-H "Authorization: Bearer tvk_your_key_here"
# 2. Create a job (save the "id" from the answer)
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: 7d0c3c1e-5b8a-4f61-9a43-2f1c9e0b6d11" \
-d '{ "text": "Hello! This is my first voiceover.", "voice_id": "voc_2072b6f1a9c4", "language": "en" }'
# → 202 { "id": "JOB_ID", "status": "queued", "characters": 34, "poll_after_seconds": 10 }
# 3. Wait for it: the server holds this request until the job is done (up to 60 s).
# Repeat while "status" is "queued" or "processing".
curl "https://api.tonvio.net/api/v1/speech/JOB_ID?wait=60" \
-H "Authorization: Bearer tvk_your_key_here"
# → { "status": "completed", "progress": 100, "audio_url": "https://cdn.tonvio.net/…" }
# 4. Download the MP3 (the link is valid for about 6 hours)
curl -L -o voiceover.mp3 "AUDIO_URL"Передавайте заголовок Idempotency-Key с каждым POST /speech и при повторе отправляйте то же значение. Тогда потерянный ответ или сбой сети никогда не создаст вторую задачу — и не спишет за неё кредиты.
Аутентификация
Передайте ключ в заголовке Authorization: Bearer или в X-API-Key — оба способа работают одинаково. Ключ — это пароль к вашему балансу: храните его на своём сервере и никогда не вставляйте в сайт или мобильное приложение.
Authorization: Bearer tvk_your_key_here
# or
X-API-Key: tvk_your_key_hereУ каждого ключа есть права. Новый ключ получает speech, voices и account; для клонирования голоса нужно право clone, его выдают отдельно. Запрос без нужного права вернёт 403 insufficient_scope.
| Право | Что разрешает |
|---|---|
speech | Создавать, отменять и удалять задачи (включает speech:read). |
speech:read | Только читать задачи — для систем мониторинга, которые не должны тратить деньги. |
voices | Просматривать голоса. |
account | Смотреть баланс, лимиты и расход; управлять доставкой вебхуков. |
clone | Создавать и удалять клоны голоса. |
Голоса
GET /voices возвращает все голоса, которые вам доступны, — их больше 18 000. Фильтруйте по language (например, en, pt-BR), gender и q (поиск по имени и описанию), а страницы листайте с помощью limit (до 200, по умолчанию 50) и offset. В ответе есть total.
curl "https://api.tonvio.net/api/v1/voices?language=en&gender=female&limit=2" \
-H "Authorization: Bearer tvk_your_key_here"
{
"total": 4120,
"limit": 2,
"offset": 0,
"voices": [
{
"id": "voc_2072b6f1a9c4",
"name": "Marcus",
"language": "en",
"languages": ["en", "de"],
"languages_on_demand": ["fr", "es"],
"gender": "male",
"age": "middle_aged",
"accent": "american",
"description": "Calm, warm narrator",
"preview_url": "https://cdn.tonvio.net/voice-previews/…/sample.mp3",
"origin": "vendor"
},
…
]
}| Поле | Значение |
|---|---|
id | То, что вы передаёте как voice_id в POST /speech. Используйте его ровно в том виде, в каком он пришёл. |
name, description, gender, age, accent | Кто этот голос: имя, описание, пол, возраст, акцент. Любое из этих полей может быть null. |
preview_url | Короткий образец, чтобы послушать голос. |
language | Родной язык голоса. |
languages | Языки, на которых голос говорит сразу. |
languages_on_demand | Языки, на которых голос тоже может говорить. Первая задача на таком языке ждёт несколько минут, пока голос готовится (стадия awaiting_voice); дальше задачи стартуют сразу. |
origin | vendor — готовый голос из каталога; recording — голос, клонированный по записи. |
GET /voices/{id} возвращает один голос. Некоторые другие поля в ответе оставлены для старых интеграций — на них можно не обращать внимания.
Синтез речи
POST /speech принимает задачу и сразу отвечает 202 Accepted — с id задачи и заголовком Location, который на неё указывает.
| Поле | Значение | |
|---|---|---|
text | обязательно | Что произнести. До limits.max_chars_per_request символов — см. GET /account (зависит от тарифа). В тексте должна быть хотя бы одна буква или цифра. |
voice_id | обязательно | id голоса из GET /voices или voice_id вашего собственного клона. |
language | необязательно | Язык текста — код BCP 47: en, de, pt-BR. Если его не указать, язык определится по тексту. |
settings.voice.speed | необязательно | Скорость речи от 0.5 до 2, по умолчанию 1. Высота голоса при этом не меняется. |
settings.voice.pauses | необязательно | Паузы в миллисекундах (0–5000) после sentence (предложения), paragraph (абзаца), clause (части предложения) или word (слова). Можно задать любые из них. Абзацы отделяются пустой строкой. |
idempotency_key | необязательно | То же, что заголовок Idempotency-Key (до 128 символов). Используйте что-то одно. |
{
"text": "Welcome back. [pause] Today we have big news! [excited] Let's begin.",
"voice_id": "voc_2072b6f1a9c4",
"language": "en",
"settings": {
"voice": {
"speed": 1.1,
"pauses": { "sentence": 350, "paragraph": 800 }
}
}
}Эмоции и звуки
Вставляйте короткие ремарки на английском в квадратных скобках прямо в текст: [whisper], [laughing], [sigh], [pause], [excited], [clearing throat], [emphasis], [shouting]. Манеру можно описать и своими словами, до 64 символов: [whisper in small voice]. Скобки, которые начинаются с цифры или написаны не латиницей, — [1], [глава 2] — читаются вслух как обычный текст. Теги — часть текста, они тоже считаются символами.
Ответ
HTTP/1.1 202 Accepted
Location: /api/v1/speech/0b3a7c11-8f2d-4e55-9a10-6c7b8d9e0f12
{
"id": "0b3a7c11-8f2d-4e55-9a10-6c7b8d9e0f12",
"status": "queued",
"characters": 68,
"paid_with": "credits",
"queue_position": 0,
"poll_after_seconds": 10
}characters— сколько символов зарезервировано под эту задачу.queue_position— сколько ваших задач ждёт в очереди перед этой.poll_after_seconds— когда впервые запросить статус задачи.settings_warnings— появляется, только если что-то из отправленного не применилось: с именем поля и причиной. Например, скорость вне диапазона 0.5–2.
Старые интеграции могут по-прежнему присылать voice_engine, model, settings.stability, settings.settings_preset, settings.delay_between_chunks, settings.voice.chunking или settings.voice.delivery. Эти поля принимаются ради совместимости, на звук не влияют и перечисляются в settings_warnings.
Статус и прогресс задачи
GET /speech/{id} возвращает текущее состояние задачи. Задача проходит путь queued → processing → completed. Ещё она может закончиться статусом failed или canceled. Эти три статуса — конечные.
| Поле | Значение |
|---|---|
status | queued — ждёт свободного слота; processing — озвучивается; completed, failed, canceled — завершена. |
progress | 0–99, пока задача выполняется; 100 — только когда аудио готово. |
stage | Что происходит сейчас: queued (ждёт голосовой движок), cold_start (запускаются мощности), awaiting_voice (голос готовится к новому языку), rendering, stitching. Может быть null. Со временем могут появиться новые значения — незнакомые понимайте как «ещё в работе». Решайте по status, а не по stage. |
eta_seconds | Сколько секунд примерно осталось, если это известно; иначе null. |
poll_after_seconds | Сколько подождать перед следующим запросом. Когда задача завершена — null. |
error_code, error_message | Почему задача не удалась — см. раздел «Ошибки». |
// While it is running
{
"id": "0b3a7c11-8f2d-4e55-9a10-6c7b8d9e0f12",
"status": "processing",
"progress": 42,
"stage": "rendering",
"eta_seconds": 35,
"characters": 68,
"credits_charged": null,
"paid_with": "credits",
"audio_url": null,
"poll_after_seconds": 7
}
// When it is ready
{
"id": "0b3a7c11-8f2d-4e55-9a10-6c7b8d9e0f12",
"status": "completed",
"progress": 100,
"characters": 68,
"credits_charged": 68,
"paid_with": "credits",
"language": "en",
"audio_url": "https://cdn.tonvio.net/…",
"audio_expires_at": "2026-11-06T10:12:03.000Z",
"duration_seconds": 5.8,
"poll_after_seconds": null
}
// If it failed (the reserved characters are already back on your balance)
{
"status": "failed",
"error_code": "voice_language_mismatch",
"error_message": "The provider detected the text language as \"fr\", but this voice speaks only: en, de. …",
"error_details": { "text_language": "fr", "voice_languages": ["en", "de"] },
"poll_after_seconds": null
}Как ждать
- Проще всего:
GET /speech/{id}?wait=60. Сервер держит запрос, пока задача не завершится или не пройдёт 60 секунд, а затем отвечает текущим состоянием. Повторяйте, пока статус не станет конечным. - Обычный опрос: спрашивайте снова через
poll_after_seconds— и никогда по короткому фиксированному таймеру. - Много задач сразу:
GET /speech?status=queued,processingодним запросом возвращает все незавершённые задачи ключа. - Совсем без опроса: настройте вебхук, и мы сами обратимся к вашему серверу.
# Long polling: the server answers as soon as the job finishes, or after 60 s at most.
curl "https://api.tonvio.net/api/v1/speech/JOB_ID?wait=60" -H "Authorization: Bearer tvk_your_key_here"
# Or list everything still running in one request
curl "https://api.tonvio.net/api/v1/speech?status=queued,processing&limit=100" -H "Authorization: Bearer tvk_your_key_here"Короткий текст обычно готов за секунды. После затишья первая задача может ждать 15–20 минут, пока запускаются мощности (стадия cold_start), а длинная книга займёт больше времени. Не считайте задачу пропавшей раньше чем через два часа.
Получение аудио
- Когда
statusравенcompleted, вaudio_urlлежит ссылка на MP3 (44,1 кГц, 192 кбит/с).duration_seconds— длительность записи. - Ссылка работает около 6 часов. Каждый
GET /speech/{id}возвращает свежую ссылку, так что, когда она понадобится, просто запросите задачу ещё раз. - Мы храним файл 30 дней после завершения задачи — точная дата в
audio_expires_at. После этогоaudio_urlравенnull, аaudio_expired—true. Скачайте файл и храните свою копию.
Символы и оплата
- 1 символ текста = 1 кредит. Считается всё, что есть в
text: буквы, пробелы, знаки препинания, переносы строк и теги в скобках. Большинство эмодзи считаются за 2. - Пока у аккаунта действует подписка «Безлимит», API покрыт ею: задачи не тратят кредиты и показывают
paid_with: subscription. Когда подписка закончится, следующие задачи оплачиваются кредитами (paid_with: credits); когда это случится, показываетcredits.plan_period_endвGET /account. - Сервис может работать в режиме «только кредиты»: тогда подписка API не покрывает и каждая задача оплачивается кредитами. Какой режим действует, показывает
credits.billing_modeвGET /account(subscriptionилиcredits_only); покрыты ли задачи этого ключа подпиской прямо сейчас —credits.plan_covers_api. - Без подписки символы резервируются, когда задача принята (
charactersв ответе), и списываются, когда она завершена (credits_charged). - Если задача не удалась, резерв возвращается целиком. Если вы отменили задачу до завершения, он тоже возвращается.
- Повтор с тем же
Idempotency-Keyникогда не резервирует символы второй раз. Запрос, отклонённый с ошибкой, ничего не резервирует. - Кредиты для API берутся из купленных пакетов символов. Бонусные символы (приветственный бонус, акции) тратятся только в кабинете, если в вашем договоре не сказано иначе.
GET /accountпоказывает, сколько этот ключ может потратить прямо сейчас:credits.api_spendable_chars(null, пока API покрывает подписка). Если баланса не хватает,POST /speechвернёт402 insufficient_balanceс полямиneedиavailable.- Запросы задач, голосов и данных аккаунта никогда не тратят символы.
curl https://api.tonvio.net/api/v1/account -H "Authorization: Bearer tvk_your_key_here"
{
"credits": { "api_spendable_chars": 250000, "plan_unlimited": false, "spend_bucket": "purchased", … },
// With an active Unlimited subscription: "api_spendable_chars": null, "plan_unlimited": true,
// "plan_period_end": "2026-11-08T10:00:00.000Z" — jobs spend no credits until then
// In credits-only mode ("billing_mode": "credits_only") a subscription does not cover the API:
// "plan_covers_api": false and "api_spendable_chars" is always a number
"limits": { "max_chars_per_request": 200000, "active_slots": 3, "rate_per_min": 300, … },
"usage": { "key": { "active_jobs": 1, "queued_jobs": 0, "chars_today": 18340, … } },
…
}Лимиты
| Лимит | Значение | Если превышен |
|---|---|---|
| Запросы | 300 в минуту на ключ (короткие всплески допустимы) | 429 rate_limited — подождите Retry-After секунд |
| Текст одной задачи | limits.max_chars_per_request в GET /account | 400 text_too_long с max |
| Одновременные задачи | Слоты вашего тарифа, общие с кабинетом; лишние задачи ждут в статусе queued | 429 too_many_jobs, когда очередь заполнена, — подождите Retry-After |
| Символы в день / в месяц | Только если заданы для ключа (сутки и месяцы по UTC) | 429 quota_exceeded с resets_at |
В ответах есть заголовки RateLimit-Limit, RateLimit-Remaining и RateLimit-Reset. Все числа, которые действуют для вашего ключа, — в GET /account.
Безопасные повторы
Сеть иногда подводит. Чтобы повтор был безопасным, передайте Idempotency-Key: <any unique string> (любая уникальная строка) вместе с POST /speech, а при повторе отправьте тот же ключ.
- Тот же ключ, тот же запрос → вы получите исходную задачу (
200), а не новую. Ничего не спишется дважды. - Тот же ключ, другой запрос →
409 idempotency_mismatch. - Тот же ключ после того, как вы удалили эту задачу, →
409 idempotency_key_consumed. Возьмите новый ключ. - Без ключа каждый вызов создаёт новую задачу. Ключи хранятся 90 дней.
Повторяйте только ответы 429 и 5xx — после паузы из Retry-After. Другие ошибки 4xx при повторе не изменятся: сначала исправьте запрос.
Отмена и удаление
POST /speech/{id}/cancelостанавливает задачу в статусеqueuedилиprocessing. Зарезервированные символы возвращаются.DELETE /speech/{id}удаляет задачу и её аудио, а если задача ещё выполняется — сначала отменяет её.- Когда звук уже полностью озвучен и осталось только сохранить файл, задачу не остановить: вы получите
409 not_cancellableили409 finalizing, и задача завершится как обычно — с аудио и обычным списанием.409 job_busyзначит: попробуйте снова через несколько секунд. - Отмена не отменяет уже озвученную работу. Если за сутки отменено больше миллиона уже озвученных символов, приём новых задач приостанавливается до конца этих суток:
429 cancel_cooldownсRetry-After.
# Stop a job that is still queued or running — the reserved characters come back
curl -X POST https://api.tonvio.net/api/v1/speech/JOB_ID/cancel -H "Authorization: Bearer tvk_your_key_here"
# → { "id": "JOB_ID", "status": "canceled" }
# Delete a job and its audio (a running job is canceled first)
curl -X DELETE https://api.tonvio.net/api/v1/speech/JOB_ID -H "Authorization: Bearer tvk_your_key_here"
# → { "id": "JOB_ID", "deleted": true, "canceled": false, "idempotency_key_reusable": false }Вебхуки
Не нужно спрашивать о задаче — мы сами сообщим. Настройте вебхук для ключа в личном кабинете (API-сервис → ваш ключ → Вебхук): адрес https, нужные события и секрет для подписи, который показывается один раз.
| Событие | Когда |
|---|---|
speech.completed | Задача завершена; в data есть audio_url. |
speech.failed | Задача не удалась; в data есть error_code и error_message. |
clone.completed | Клон голоса готов; в data есть voice_id. |
clone.failed | Клон голоса создать не удалось. |
POST /your-endpoint HTTP/1.1
Host: your-server.example
Content-Type: application/json
User-Agent: tonvio-webhooks/1
X-Tonvio-Event: speech.completed
X-Tonvio-Delivery: 0f9d5f2e-4d1e-4a2b-9a3c-1f2e3d4c5b6a
X-Tonvio-Attempt: 1
X-Tonvio-Timestamp: 1789000000
X-Tonvio-Signature: t=1789000000,v1=6b1c9f0e…
{
"id": "0f9d5f2e-4d1e-4a2b-9a3c-1f2e3d4c5b6a",
"schema_version": 1,
"event": "speech.completed",
"object_id": "0b3a7c11-8f2d-4e55-9a10-6c7b8d9e0f12",
"sent_at": "2026-10-07T10:12:03.114Z",
"data": {
"id": "0b3a7c11-8f2d-4e55-9a10-6c7b8d9e0f12",
"status": "completed",
"characters": 68,
"credits_charged": 68,
"paid_with": "credits",
"audio_url": "https://cdn.tonvio.net/…",
"error_code": null,
"error_message": null
}
}Проверка подписи
Каждая доставка подписана: X-Tonvio-Signature: t=<time>,v1=<signature>, где подпись — HMAC-SHA256 от <t>.<raw body> с вашим секретом, в hex. Проверяйте сырые байты (а не заново закодированный JSON), отклоняйте доставки, у которых t расходится с вашими часами больше чем на 5 минут, и сравнивайте подписи за постоянное время.
import crypto from "node:crypto";
import express from "express";
const SECRET = process.env.TONVIO_WEBHOOK_SECRET; // whsec_… shown once when you set it
const TOLERANCE_SECONDS = 300;
// Verify the RAW bytes. Parsing JSON and serializing it back changes the bytes,
// and the signature will then never match — this is the #1 integration mistake.
function verify(rawBody, header) {
const parts = {};
for (const piece of String(header || "").split(",")) {
const i = piece.indexOf("=");
if (i > 0) parts[piece.slice(0, i).trim()] = piece.slice(i + 1).trim();
}
const t = Number(parts.t);
if (!Number.isFinite(t) || !parts.v1) return false;
// Replay window: the timestamp is part of the signed string, so it cannot be moved.
if (Math.abs(Math.floor(Date.now() / 1000) - t) > TOLERANCE_SECONDS) return false;
const expected = crypto
.createHmac("sha256", SECRET)
.update(`${t}.${rawBody}`, "utf8")
.digest("hex");
const a = Buffer.from(expected, "utf8");
const b = Buffer.from(parts.v1, "utf8");
// Constant-time compare: === on a hex string leaks the common prefix.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
const app = express();
app.post("/tonvio-hook", express.raw({ type: "application/json" }), (req, res) => {
const raw = req.body.toString("utf8");
if (!verify(raw, req.get("X-Tonvio-Signature"))) return res.sendStatus(401);
const event = JSON.parse(raw);
// Deduplicate by event.id: a retry repeats the SAME delivery id and the same body.
// Answer 2xx first and do the slow work afterwards — we wait 10 s, then retry.
res.sendStatus(200);
queue.push(event);
});Правила доставки
- Ответьте любым
2xxв течение 10 секунд, а долгую работу делайте уже после ответа. - Если мы не получим
2xx, будем повторять: до 5 попыток примерно за 2,6 часа. Чтобы остановить повторы, ответьте410 Gone. - Доставка может прийти дважды и не по порядку. Пропускайте те, чей id вы уже обработали (
X-Tonvio-Delivery). - При отмене задачи событие не отправляется — отмену вы запустили сами.
Отладка
POST /webhooks/test отправляет на ваш адрес тестовое событие и показывает, что он ответил. GET /webhooks/deliveries возвращает список настоящих доставок с точным телом, которое было подписано, а POST /webhooks/deliveries/{id}/requeue отправляет одну из них ещё раз.
# Where this key delivers and whether a signing secret is set
curl https://api.tonvio.net/api/v1/webhooks -H "Authorization: Bearer tvk_your_key_here"
# Send yourself a test event; 200 comes back even when your server answered 500 — read result.ok
curl -X POST https://api.tonvio.net/api/v1/webhooks/test -H "Authorization: Bearer tvk_your_key_here"
# → { "result": { "ok": false, "status": 500, "signed": true, "response_snippet": "…", "duration_ms": 214 } }
# The log of real deliveries, with the exact body that was signed
curl "https://api.tonvio.net/api/v1/webhooks/deliveries?state=dead&limit=20" -H "Authorization: Bearer tvk_your_key_here"
# Send one delivery again after you fixed your server
curl -X POST https://api.tonvio.net/api/v1/webhooks/deliveries/DELIVERY_ID/requeue -H "Authorization: Bearer tvk_your_key_here"Клонирование голоса
Превратите запись своего голоса в личный голос, которым можете пользоваться только вы. Ключу нужно право clone.
- 1Отправьте
POST /voice-clonesкак форму (multipart/form-data) с заголовкомIdempotency-Key(здесь он обязателен):name,consent, при желанииlanguagesи файл в полеaudio— mp3, wav, m4a или webm, до 15 МБ. Текстовые поля ставьте перед файлом. - 2Проверяйте
GET /voice-clones/{id}не чаще, чем подсказываетpoll_after_seconds. Создание голоса занимает несколько минут. - 3Когда
statusстанетcompleted, передавайтеvoice_idклона вPOST /speech, как любой другой голос.
# 1. Order a private clone from your own recording.
# Text fields MUST come before the file, otherwise 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: 4c2f8e9a-1b7d-4e3a-8c6f-5d9b0a1e2f34" \
-F "name=My narrator" \
-F "consent=I confirm this is my own voice and I agree to have it cloned." \
-F "languages=en" \
-F "[email protected];type=audio/mpeg"
# → 202 { "id": "CLONE_ID", "status": "queued", "visibility": "private", "poll_after_seconds": 15 }
# 2. Check on it no more often than poll_after_seconds says; building takes minutes
curl https://api.tonvio.net/api/v1/voice-clones/CLONE_ID -H "Authorization: Bearer tvk_your_key_here"
# → { "status": "completed", "percent": 100, "voice_id": "VOICE_ID" }
# 3. Use it like any other voice
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: 9e1d7b3c-2a4f-4c8e-b6d0-3f5a7c9e1b2d" \
-d '{ "text": "Hello, this is my cloned voice.", "voice_id": "VOICE_ID" }'consent— заявление длиной 20–500 символов о том, что у вас есть право на этот голос. Мы храним его вместе с запросом. Клонируйте только свой голос или голос, на использование которого у вас есть письменное разрешение.- Сколько клонов может создать ключ, указано в
limits.clone_limitв ответеGET /account. DELETE /voice-clones/{id}удаляет клон, который больше не нужен.
Ошибки
Все ошибки устроены одинаково: HTTP-статус и JSON-тело с машиночитаемым кодом error, а у некоторых кодов — ещё и дополнительные поля. Решайте, что делать, по коду, а не по тексту. В каждом ответе есть заголовок X-Request-Id — указывайте его, когда пишете в поддержку.
HTTP/1.1 402 Payment Required
X-Request-Id: req-8f1c2d
{ "error": "insufficient_balance", "feature": "api", "need": 120000, "available": 45000 }
HTTP/1.1 429 Too Many Requests
Retry-After: 10
{ "error": "too_many_jobs", "scope": "account", "limit": 3, "retry_after": 10 }Ошибки запроса
| Статус | Код | Что делать |
|---|---|---|
| 400 | invalid_request | В теле запроса ошибка — message подскажет, какая. Исправьте и отправьте снова. |
| 400 | text_too_long, text_too_short | Сократите текст (max) или добавьте слов. |
| 401 | missing_api_key, invalid_api_key, api_key_expired | Проверьте ключ; если его отозвали или срок истёк, создайте новый. |
| 402 | insufficient_balance | Докупите символы или оформите «Безлимит» (need, available). |
| 403 | insufficient_scope | У ключа нет нужного права — его название в required. |
| 403 | voice_not_allowed | Этот голос использовать нельзя — выберите другой. |
| 404 | not_found | Для этого ключа такой задачи или такого голоса нет. |
| 409 | idempotency_mismatch, idempotency_key_consumed | Для нового запроса используйте новый Idempotency-Key. |
| 429 | rate_limited, create_busy, too_many_jobs, quota_exceeded | Подождите Retry-After (или до resets_at) и повторите. |
| 429 | cancel_cooldown | Приём новых задач приостановлен после множества отмен уже озвученной работы. Подождите Retry-After. |
| 504 | create_outcome_unknown | Не удалось подтвердить, создалась ли задача; её job_id есть в ответе. Повторите с тем же Idempotency-Key (это безопасно в любом случае) или проверьте GET /speech/{id}. |
| 5xx | generation_unavailable, service_busy, service_unavailable, delivery_unavailable, internal_error | Временная ошибка. Повторите после Retry-After с тем же Idempotency-Key. |
Когда задача не удалась
Задачу могут принять, а выполнить потом не получится. Тогда GET /speech/{id} (и вебхук speech.failed) вернут status: failed, error_code и error_message, который можно показать людям. Зарезервированные символы возвращаются всегда.
| `error_code` | Значение | Повторять? |
|---|---|---|
voice_language_mismatch | Текст написан на языке, на котором этот голос не говорит (язык определён по тексту). В error_details указаны оба языка. | Нет — выберите голос, который говорит на этом языке. |
voice_language_not_available | Голос сейчас не может говорить на этом языке (или у него нет версии для запрошенного). | Нет — выберите другой голос (или подождите, если error_details.variant_building равно true). |
voice_not_found | Голос больше недоступен. | Нет — выберите другой голос. |
text_rejected | В тексте нет слов для озвучки или есть символы, которые нельзя прочитать вслух. | Нет — исправьте текст. |
synthesis_failed | Озвучка не удалась по временной причине. | Да, позже. |
queue_timeout, execution_timeout, provider_unavailable, voice_unavailable | Временно не хватает мощностей. | Да, позже. |
assembly_failed | Не удалось сохранить аудио. | Да. |
output_too_large | Аудиофайл получился бы слишком большим. | Разбейте текст на несколько задач. |
generation_failed | Всё остальное. | Один раз; если ошибка повторится, напишите в поддержку и укажите id задачи. |
Все методы API
| Метод | Путь | Что делает | Право |
|---|---|---|---|
POST | /speech | Создать задачу | speech |
GET | /speech/{id} | Статус задачи и ссылка на аудио (?wait= — чтобы подождать) | speech:read |
GET | /speech | Задачи этого ключа, сначала новые (status, limit, cursor) | speech:read |
POST | /speech/{id}/cancel | Отменить задачу | speech |
DELETE | /speech/{id} | Удалить задачу и её аудио | speech |
GET | /voices | Голоса (language, gender, q, limit, offset) | voices |
GET | /voices/{id} | Один голос | voices |
GET | /voices/{id}/preview.mp3 | Послушать образец голоса (работает без ключа) | — |
GET | /account | Баланс, лимиты и расход этого ключа | account |
GET | /webhooks | Настройки вебхука этого ключа | account |
POST | /webhooks/test | Отправить тестовое событие | account |
GET | /webhooks/deliveries | Журнал доставок | account |
POST | /webhooks/deliveries/{id}/requeue | Доставить ещё раз | account |
POST | /voice-clones | Клонировать голос по записи | clone |
GET | /voice-clones/{id} | Статус клона | clone |
GET | /voice-clones | Клоны этого ключа | clone |
DELETE | /voice-clones/{id} | Удалить клон | clone |
Файл OpenAPI описывает каждый метод, поле и код ошибки — включая несколько, оставленных только для старых интеграций (например, /voices/{id}/warm). Загрузите его в генератор кода, Postman или ИИ-ассистент.
Стабильность
- Версия 1 только растёт: мы добавляем поля, значения и методы, но никогда не удаляем и не переименовываем то, что здесь описано.
- Поэтому пропускайте незнакомые поля, а неизвестные значения
stageилиerror_codeобрабатывайте как общие. - Все даты — в формате ISO 8601, в UTC. Ответы никогда не кешируются (
Cache-Control: no-store).
Что разработчики спрашивают первым делом
Можно прочитать документацию без аккаунта?
Да. Эта страница, файл Markdown и спецификация OpenAPI открыты всем. Аккаунт нужен, только чтобы создать ключ и тратить символы.
API синхронный?
Нет — озвучка занимает от секунд до минут, поэтому POST /speech сразу возвращает id задачи. Дальше либо вызывайте GET /speech/{id}?wait=60 — ответ придёт, как только задача готова, — либо спрашивайте через poll_after_seconds, либо настройте вебхук, и мы позвоним вам сами.
Сколько стоит вызов API?
С активной подпиской «Безлимит» — ничего: API покрывает подписка. Без неё вы платите символами: один символ текста — один кредит, из купленных пакетов символов. Символы резервируются, когда задача принята, списываются, когда она готова, и возвращаются полностью, если она упала или вы её отменили. Чтение задач и голосов бесплатно. Цены — на странице тарифов.
Сколько голосов и языков?
Более 18 000 голосов. Фильтруйте GET /voices по языку: у каждого голоса указаны языки, на которых он говорит сразу, и те, которые он освоит при первой задаче.
Можно управлять эмоциями и паузами?
Да. Пишите указания вроде [whisper], [laughing] или [pause] в квадратных скобках прямо в тексте, а скорость речи и паузы задавайте в settings.voice.
Какие лимиты на запросы?
По умолчанию 300 запросов в минуту на ключ, короткие всплески допустимы. Сколько задач идёт одновременно и какой длины может быть один текст, зависит от тарифа; точные цифры для вашего ключа — в GET /account.
Как сделать повторы безопасными?
Отправляйте заголовок Idempotency-Key с каждым POST /speech и повторяйте с тем же ключом. Тот же ключ вернёт исходную задачу, а не создаст и не оплатит вторую.
Можно клонировать голос через API?
Да, если у ключа есть право clone: загрузите запись своего голоса, подождите несколько минут и используйте новый voice_id как любой другой голос.
Сколько хранится аудио?
Ссылка на скачивание работает около 6 часов, и каждый запрос статуса выдаёт свежую. Сам файл хранится 30 дней — скачайте его и храните свою копию.
Начните с одного запроса
Создайте ключ в кабинете — и до первой озвучки останется минута. С «Безлимитом» API покрывает подписка, без него вы платите только за озвученные символы.
Впервые в Tonvio? Начните с обзора озвучки текста.