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

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

Tonvio API: живая речь для вашего продукта

Один HTTP API, чтобы превращать текст в речь: более 18 000 голосов, теги эмоций, клонирование голоса и вебхуки. Весь справочник открыт — читайте здесь, скачайте его в Markdown или OpenAPI и оцените интеграцию ещё до регистрации.

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

Возьмите весь справочник с собой: файл Markdown — для команды или ИИ-ассистента, спецификацию OpenAPI — для генераторов кода и HTTP-клиентов.

Скачать OpenAPI (JSON)

Превращайте текст в живую речь по HTTP. Вы отправляете текст и голос — Tonvio возвращает MP3. Задачи выполняются асинхронно: создайте задачу, дождитесь, пока она завершится, и скачайте аудио. Всё, что описано ниже, работает с любым HTTP-клиентом, а весь справочник можно скачать как файл OpenAPI или одним файлом Markdown.

Базовый URL: https://api.tonvio.net/api/v1

Быстрый старт

  1. 1Создайте ключ API в личном кабинете: API-сервис → Ключи API. Ключ показывается один раз — сохраните его на своём сервере.
  2. 2Выберите голос через GET /voices и скопируйте его id.
  3. 3Создайте задачу через POST /speech: передайте текст, voice_id и, если нужно, language. id задачи придёт сразу.
  4. 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 — оба способа работают одинаково. Ключ — это пароль к вашему балансу: храните его на своём сервере и никогда не вставляйте в сайт или мобильное приложение.

HTTP
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
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); дальше задачи стартуют сразу.
originvendor — готовый голос из каталога; 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 символов). Используйте что-то одно.
JSON
{
  "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
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. Эти три статуса — конечные.

ПолеЗначение
statusqueued — ждёт свободного слота; processing — озвучивается; completed, failed, canceled — завершена.
progress0–99, пока задача выполняется; 100 — только когда аудио готово.
stageЧто происходит сейчас: queued (ждёт голосовой движок), cold_start (запускаются мощности), awaiting_voice (голос готовится к новому языку), rendering, stitching. Может быть null. Со временем могут появиться новые значения — незнакомые понимайте как «ещё в работе». Решайте по status, а не по stage.
eta_secondsСколько секунд примерно осталось, если это известно; иначе null.
poll_after_secondsСколько подождать перед следующим запросом. Когда задача завершена — null.
error_code, error_messageПочему задача не удалась — см. раздел «Ошибки».
JSON
// 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 одним запросом возвращает все незавершённые задачи ключа.
  • Совсем без опроса: настройте вебхук, и мы сами обратимся к вашему серверу.
cURL
# 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
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 /account400 text_too_long с max
Одновременные задачиСлоты вашего тарифа, общие с кабинетом; лишние задачи ждут в статусе queued429 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.
cURL
# 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Клон голоса создать не удалось.
HTTP
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 отправляет одну из них ещё раз.

cURL
# 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. 1Отправьте POST /voice-clones как форму (multipart/form-data) с заголовком Idempotency-Key (здесь он обязателен): name, consent, при желании languages и файл в поле audio — mp3, wav, m4a или webm, до 15 МБ. Текстовые поля ставьте перед файлом.
  2. 2Проверяйте GET /voice-clones/{id} не чаще, чем подсказывает poll_after_seconds. Создание голоса занимает несколько минут.
  3. 3Когда status станет completed, передавайте voice_id клона в POST /speech, как любой другой голос.
cURL
# 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
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 }

Ошибки запроса

СтатусКодЧто делать
400invalid_requestВ теле запроса ошибка — message подскажет, какая. Исправьте и отправьте снова.
400text_too_long, text_too_shortСократите текст (max) или добавьте слов.
401missing_api_key, invalid_api_key, api_key_expiredПроверьте ключ; если его отозвали или срок истёк, создайте новый.
402insufficient_balanceДокупите символы или оформите «Безлимит» (need, available).
403insufficient_scopeУ ключа нет нужного права — его название в required.
403voice_not_allowedЭтот голос использовать нельзя — выберите другой.
404not_foundДля этого ключа такой задачи или такого голоса нет.
409idempotency_mismatch, idempotency_key_consumedДля нового запроса используйте новый Idempotency-Key.
429rate_limited, create_busy, too_many_jobs, quota_exceededПодождите Retry-After (или до resets_at) и повторите.
429cancel_cooldownПриём новых задач приостановлен после множества отмен уже озвученной работы. Подождите Retry-After.
504create_outcome_unknownНе удалось подтвердить, создалась ли задача; её job_id есть в ответе. Повторите с тем же Idempotency-Key (это безопасно в любом случае) или проверьте GET /speech/{id}.
5xxgeneration_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? Начните с обзора озвучки текста.