Documentation

Speech synthesis, transcription, and audio analysis via a single REST API. Key in the header; result as a binary audio file or JSON.

Open Swagger (OpenAPI)

Quick start

  1. Sign up and get an API key in the form rtt_… in your dashboard.
  2. Send the key in the X-Api-Key header of every request.
  3. Base URL is your deployment domain. The examples below use https://api.example.com.
curl -X POST https://api.example.com/v1/synthesize \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: rtt_…" \
  -d '{"text":"Привет, мир!","voice":"natasha","format":"mp3"}' \
  --output hello.mp3

Authentication

All endpoints /v1/* (except the voice catalog) require an API key in the X-Api-Key.

X-Api-Key: rtt_…

Also supported: Authorization: Bearer rtt_… and X-RapidAPI-Key for RapidAPI integration.

Speech synthesis

POST /v1/synthesize accepts JSON and returns a binary audio file (mp3, wav, or ogg).

FieldTypeDescription
textstringText to synthesize (up to 5000 characters)
voicestringVoice ID, e.g. natasha
formatstringmp3 (default), wav, ogg
sample_rateintSample rate, e.g. 24000
speedfloatSpeech speed (default 1.0)
emotionstringneutral, joy, sad, angry
normalizeboolSpell numbers out in words (true by default)

cURL

curl -X POST https://api.example.com/v1/synthesize \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: rtt_…" \
  -d '{"text":"Добрый день!","voice":"natasha","format":"mp3","emotion":"joy"}' \
  --output speech.mp3

Python

import httpx

response = httpx.post(
    "https://api.example.com/v1/synthesize",
    headers={"X-Api-Key": "rtt_…"},
    json={
        "text": "Добрый день!",
        "voice": "natasha",
        "format": "mp3",
        "emotion": "joy",
    },
    timeout=60.0,
)
response.raise_for_status()
with open("speech.mp3", "wb") as f:
    f.write(response.content)

JavaScript

const response = await fetch("https://api.example.com/v1/synthesize", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Api-Key": "rtt_…",
  },
  body: JSON.stringify({ text: "Добрый день!", voice: "natasha", format: "mp3" }),
});

if (!response.ok) throw new Error(await response.text());
const blob = await response.blob();
// Browser: URL.createObjectURL(blob) → play it in an audio tag
// Node.js: fs.writeFileSync("speech.mp3", Buffer.from(await blob.arrayBuffer()))

C# (.NET)

using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.example.com/v1/synthesize");
request.Headers.Add("X-Api-Key", "rtt_…");
request.Content = new StringContent(
    """{"text":"Добрый день!","voice":"natasha","format":"mp3"}""",
    System.Text.Encoding.UTF8, "application/json");

using var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
await File.WriteAllBytesAsync("speech.mp3", await response.Content.ReadAsByteArrayAsync());

Chunk streaming

POST /v1/synthesize/stream returns audio chunks as they are generated — playback starts before the whole text is synthesized. Available on the Pro and Business; Free and Basic receive 403 streaming_forbidden.

The request body is the same as POST /v1/synthesize. The response is an audio/mpeg stream without a Content-Length: each chunk is written as soon as it is synthesized.

cURL

curl -X POST https://api.example.com/v1/synthesize/stream \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: rtt_…" \
  -d '{"text":"Первое предложение. Второе предложение. Третье предложение.","voice":"natasha","format":"mp3"}' \
  --no-buffer \
  --output speech_stream.mp3

Python

import httpx

with httpx.stream(
    "POST",
    "https://api.example.com/v1/synthesize/stream",
    headers={"X-Api-Key": "rtt_…"},
    json={"text": "Первое предложение. Второе предложение.", "voice": "natasha", "format": "mp3"},
    timeout=60.0,
) as response:
    response.raise_for_status()
    with open("speech_stream.mp3", "wb") as f:
        for chunk in response.iter_bytes():
            f.write(chunk)

MP3 streams sentence by sentence (MP3 frames concatenate cleanly); WAV/OGG are encoded whole and sliced into chunks so the stream stays a valid container.

Voices

GET /v1/voices returns the voice list without authentication; GET /v1/voices/{id} returns a single voice.

Available, for example: natasha, dmitri, elena, sergey, xenia, aidar, kseniya, eugene, baya.

curl https://api.example.com/v1/voices

Voice cloning

POST /v1/voices/clone creates a cloned voice from 1–3 samples of the same speaker. The returned `id` is used as `voice` with `model=premium`.

Samples: wav/mp3/ogg/flac/m4a/aac up to 10 MB each, clean voice without noise or music. The premium XTTS engine is available on Pro and Business plans.

curl -X POST https://api.example.com/v1/voices/clone \
  -H "X-Api-Key: rtt_…" \
  -F "name=my_voice" \
  -F "samples=@voice_1.wav" \
  -F "samples=@voice_2.wav" \
  -F "samples=@voice_3.wav"

# → 201 { "id": "clone_…", "name": "my_voice", "language": "ru",
#         "sample_count": 3, "created_at": "…" }

# GET    /v1/voices/clone
# GET    /v1/voices/clone/{id}
# DELETE /v1/voices/clone/{id}   # → 204

Use the cloned voice in POST /v1/synthesize and POST /v1/synthesize/stream:

curl -X POST https://api.example.com/v1/synthesize \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: rtt_…" \
  -d '{"text":"Привет!","voice":"clone_…","model":"premium","format":"mp3"}'

Transcription

The async POST /v1/transcribe accepts multipart/form-data and immediately returns job_id. Fetch the result by polling GET /v1/transcribe/{job_id}.

FieldTypeDescription
audiofileAudio file (wav, mp3, ogg, flac up to 25 MB / 15 min)
languagestringISO 639-1 language code (e.g. ru, en, de). Optional — auto-detected when omitted.
diarizationboolSpeaker separation (requires a plan with diarization)
keytermsstringComma-separated terms to boost recognition (keyterm prompting)
webhookUrlstringURL to notify when ready

Supported language codes (ISO 639-1): ru, en, de, es, fr, uk, kk, uz, zh and more — the full list is in Swagger.

# Start a job
curl -X POST https://api.example.com/v1/transcribe \
  -H "X-Api-Key: rtt_…" \
  -F "audio=@meeting.mp3" \
  -F "language=ru" \
  -F "diarization=true"

# → 202 { "job_id": "…", "status": "queued" }

# Fetch result
curl https://api.example.com/v1/transcribe/{job_id} \
  -H "X-Api-Key: rtt_…"

For short files (up to 3 minutes) there is a synchronous option — POST /v1/transcribe/sync, which returns the result immediately.

You can pass a comma-separated `keyterms` field — the model will recognize those terms more reliably (keyterm prompting). The `segments` response now includes word-level timestamps `words` (`word`, `start`, `end`, `confidence`).

Subtitles

GET /v1/transcribe/{job_id}/subtitles exports a completed transcript as WebVTT or SubRip subtitles.

The `format` parameter is `vtt` (default) or `srt`.

If the job is not completed yet, `409 subtitles_unavailable` is returned.

curl -OJ https://api.example.com/v1/transcribe/{job_id}/subtitles?format=srt \
  -H "X-Api-Key: rtt_…"

Audio analysis

POST /v1/analyze returns the transcript, per-segment emotions, and keywords.

The parameter language is an ISO 639-1 language code (e.g. ru, en); when omitted, the language is auto-detected. ISO 639-1 language codes are supported.

The comma-separated `keyterms` field boosts recall of domain-specific terms.

curl -X POST https://api.example.com/v1/analyze \
  -H "X-Api-Key: rtt_…" \
  -F "audio=@call.mp3" \
  -F "emotions=true" \
  -F "keywords=true"

# → { "transcript": "…", "segments": […], "keywords": […] }

Topics

POST /v1/analyze/topics classifies text into topics without uploading audio, returning a list of topics with relevance scores (0..1).

curl -X POST https://api.example.com/v1/analyze/topics \
  -H "X-Api-Key: rtt_…" \
  -H "Content-Type: application/json" \
  -d '{"text": "Запустили стартап и вывели продукт на рынок."}'

# → { "topics": [{"topic": "business", "score": 0.09}] }

Summarization

POST /v1/analyze/summarize produces an extractive summary of text without uploading audio. The `max_sentences` parameter (1..10, default 3) controls the number of sentences.

curl -X POST https://api.example.com/v1/analyze/summarize \
  -H "X-Api-Key: rtt_…" \
  -H "Content-Type: application/json" \
  -d '{"text": "Первое предложение. Второе. Третье. Четвёртое.", "max_sentences": 2}'

# → { "summary": "…", "sentences": […] }

Language detection

POST /v1/detect-language detects the language of a text snippet without uploading audio, returning the ISO 639-1 code and a confidence score (0..1).

Useful before choosing a voice or forcing a transcription language.

curl -X POST https://api.example.com/v1/detect-language \
  -H "X-Api-Key: rtt_…" \
  -H "Content-Type: application/json" \
  -d '{"text": "Привет! Как дела?"}'

# → { "language": "ru", "confidence": 0.99 }

PII redaction

POST /v1/redact replaces personal data in text with type labels and returns the masked text plus the found entities with their offsets. No audio upload required.

Names, organisations, locations, dates and amounts (NER) are masked, as well as phone numbers, emails and card numbers (regex).

curl -X POST https://api.example.com/v1/redact \
  -H "X-Api-Key: rtt_…" \
  -H "Content-Type: application/json" \
  -d '{"text": "Иван позвонил на +7 900 123-45-67 из Москвы."}'

# → { "redacted_text": "[PER] позвонил на [PHONE] из [LOC].", "entities": […], "count": 3 }

Streaming STT

WS /v1/transcribe/stream streams real-time transcription over WebSocket (Pro/Business plans). Send raw PCM16 little-endian mono 16 kHz frames as binary messages; the server replies with JSON events: `session`, `vad`, `partial` and `final`.

Free and Basic plans receive `403 streaming_forbidden`.

Authentication: key in the `?api_key=…` query string or the `X-Api-Key` header (browsers cannot set headers on a WS handshake).

Client → server: binary PCM16 frames (little-endian, mono, 16 kHz) and a text `{"type":"stop"}` frame to finalize.

Server → client — JSON events:

TypeEventDescription
sessionstarted / endedSession lifecycle (+`session_id`, `audio_seconds`).
vadspeech_started / speech_endedTurn detection: speech start and end.
partialInterim transcript during speech.
finalFinal transcript of a finished utterance.
errorSession error (`quota_exceeded`, `stream_failed`).

Query parameters: `language`, `keyterms` (comma-separated), `interim=true|false`.

Quota is charged by transmitted audio (1 sec = 32,000 PCM16 bytes).

JavaScript

const ws = new WebSocket("wss://api.example.com/v1/transcribe/stream?api_key=rtt_…&language=ru");

ws.binaryType = "arraybuffer";
ws.onmessage = (event) => console.log(JSON.parse(event.data));

ws.send(pcm16Bytes);
ws.send(JSON.stringify({ type: "stop" }));

Python

import asyncio, json, websockets

async def main():
    async with websockets.connect(
        "wss://api.example.com/v1/transcribe/stream?api_key=rtt_…"
    ) as ws:
        await ws.send(open("speech.raw", "rb").read())
        await ws.send(json.dumps({"type": "stop"}))
        async for message in ws:
            print(json.loads(message))

asyncio.run(main())

VAD / turn detection

POST /v1/vad detects speech segments (Silero VAD) in an uploaded audio file and returns their start/end times. The WebSocket variant streams live `speech_started` / `speech_ended` events — the building block for voice agents.

curl -X POST https://api.example.com/v1/vad \
  -H "X-Api-Key: rtt_…" \
  -F "audio=@meeting.mp3"

# → { "segments": [{"start": 0.1, "end": 2.4}, {"start": 3.0, "end": 5.2}],
#     "speech_ratio": 0.68, "duration_seconds": 5.2, "processing_time_ms": 12 }

WS /v1/vad/stream— streaming turn detection: VAD events only, no recognition.

const ws = new WebSocket("wss://api.example.com/v1/vad/stream?api_key=rtt_…");
ws.binaryType = "arraybuffer";
ws.onmessage = (event) => console.log(JSON.parse(event.data));

// ← {"type":"vad","event":"speech_started","start":1.20}
// ← {"type":"vad","event":"speech_ended","start":1.20,"end":4.85}

Convert to PCM16: `ffmpeg -i in.mp3 -ar 16000 -ac 1 -f s16le out.raw`.

Batch

POST /v1/batch/synthesize and POST /v1/batch/analyze queue batch processing: up to 20 synthesis tasks or up to 10 analysis tasks (inline base64 audio). They return `202` with a `batch_id`.

Fetch results by polling: GET /v1/batch/{batch_id}.

# Batch synthesis
curl -X POST https://api.example.com/v1/batch/synthesize \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: rtt_…" \
  -d '{"items":[{"text":"Первый текст","voice":"natasha"},{"text":"Второй текст","voice":"dmitri"}]}'

# → 202 { "batch_id": "…", "status": "queued", "item_count": 2 }

# Batch analysis
curl -X POST https://api.example.com/v1/batch/analyze \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: rtt_…" \
  -d '{"items":[{"audio":"BASE64…","language":"ru"}]}'

# Result
curl https://api.example.com/v1/batch/{batch_id} \
  -H "X-Api-Key: rtt_…"

Quotas and limits

GET /v1/usage shows the remaining quota for your plan: synthesis characters, transcription minutes, and analysis requests.

curl https://api.example.com/v1/usage \
  -H "X-Api-Key: rtt_…"

Errors

Errors are returned in RFC 7807 (Problem Details) format with an extra code.

CodeHTTPDescription
text_too_long413Text exceeds the limit
text_invalid_characters400Text contains characters unsupported by the chosen voice
quota_exceeded429Plan limit exhausted
streaming_forbidden403Streaming is not available on the current plan
audio_invalid400Invalid audio file
audio_too_large413Audio file exceeds the allowed size
job_not_found404Job not found
engine_unavailable503Inference is temporarily unavailable
premium_voice_forbidden403Premium engine is not available on the current plan
clone_forbidden403Cloning is not available on the current plan
diarization_forbidden403Diarization is not available on the current plan
subtitles_unavailable409Subtitles unavailable: the job is not completed yet
audio_too_long413Audio exceeds the allowed duration

Python SDK

Official Python wrapper: synthesis, streaming, transcription, analysis, text intelligence, and batches.

Install

pip install russiantts

Quick start

from russiantts import RussianTtsClient

client = RussianTtsClient(api_key="rtt_…")

audio = client.synthesize("Привет! Это синтез русской речи.", voice="natasha", format="mp3")
open("speech.mp3", "wb").write(audio)

for chunk in client.synthesize_stream("Первое предложение. Второе."):
    pass

job = client.transcribe("audio.wav", keyterms=["диагноз"])
result = client.get_transcription_job(job["job_id"])
while result["status"] not in ("completed", "failed"):
    result = client.get_transcription_job(job["job_id"])

transcript = client.transcribe_sync("audio.wav")
analysis = client.analyze_sync("audio.wav")
lang = client.detect_language("Как дела?")
topics = client.topics("Нейросети и алгоритмы")
summary = client.summarize("Длинный текст.", max_sentences=3)
redacted = client.redact("Иван позвонил на +7 900 123-45-67 из Москвы.")

batch = client.batch_synthesize([{"text": "Первый текст", "voice": "natasha"}])
status = client.get_batch(batch["batch_id"])

Options: `api_key` (required), `base_url` (default `https://ttsapi.ru`), `timeout` (120.0 s). Errors raise `RussianTtsError` with `.status`, `.code`, `.message`.

TypeScript SDK

Official TypeScript/JavaScript wrapper. Zero dependencies, Node.js 18+.

Install

npm install russiantts

Quick start

import { RussianTtsClient } from "russiantts";
import { writeFile } from "node:fs/promises";

const client = new RussianTtsClient({ apiKey: "rtt_…" });

const audio = await client.synthesize("Привет! Это синтез русской речи.", {
  voice: "natasha",
  format: "mp3",
});
await writeFile("speech.mp3", audio);

for await (const chunk of client.synthesizeStream("Первое предложение. Второе.")) {
  // …
}

const job = await client.transcribe("audio.wav", { keyterms: ["диагноз"] });
let result = await client.getTranscriptionJob(job.job_id);
while (!["completed", "failed"].includes(result.status)) {
  await new Promise((r) => setTimeout(r, 1000));
  result = await client.getTranscriptionJob(job.job_id);
}

const transcript = await client.transcribeSync("audio.wav");
const analysis = await client.analyzeSync("audio.wav");
const lang = await client.detectLanguage("Как дела?");
const topics = await client.topics("Нейросети и алгоритмы");
const summary = await client.summarize("Длинный текст.", undefined, 3);
const redacted = await client.redact("Иван позвонил на +7 900 123-45-67 из Москвы.");

const batch = await client.batchSynthesize([{ text: "Первый текст", voice: "natasha" }]);
const status = await client.getBatch(batch.batch_id);

Options: `apiKey` (required), `baseUrl` (default `https://ttsapi.ru`), `timeoutMs` (120000). Errors reject with `RussianTtsError` (`.status`, `.code`, `.message`).

WebSocket endpoints (streaming STT, VAD) are consumed directly with a WS client — see the sections above.