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)rtt_… in your dashboard.X-Api-Key header of every request.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
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.
POST /v1/synthesize accepts JSON and returns a binary audio file (mp3, wav, or ogg).
| Field | Type | Description |
|---|---|---|
text | string | Text to synthesize (up to 5000 characters) |
voice | string | Voice ID, e.g. natasha |
format | string | mp3 (default), wav, ogg |
sample_rate | int | Sample rate, e.g. 24000 |
speed | float | Speech speed (default 1.0) |
emotion | string | neutral, joy, sad, angry |
normalize | bool | Spell numbers out in words (true by default) |
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
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)
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()))
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());
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 -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
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.
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
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"}'
The async POST /v1/transcribe accepts multipart/form-data and immediately returns job_id. Fetch the result by polling GET /v1/transcribe/{job_id}.
| Field | Type | Description |
|---|---|---|
audio | file | Audio file (wav, mp3, ogg, flac up to 25 MB / 15 min) |
language | string | ISO 639-1 language code (e.g. ru, en, de). Optional — auto-detected when omitted. |
diarization | bool | Speaker separation (requires a plan with diarization) |
keyterms | string | Comma-separated terms to boost recognition (keyterm prompting) |
webhookUrl | string | URL 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`).
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_…"
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": […] }
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}] }
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": […] }
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 }
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 }
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:
| Type | Event | Description |
|---|---|---|
session | started / ended | Session lifecycle (+`session_id`, `audio_seconds`). |
vad | speech_started / speech_ended | Turn detection: speech start and end. |
partial | — | Interim transcript during speech. |
final | — | Final transcript of a finished utterance. |
error | — | Session 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).
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" }));
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())
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`.
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_…"
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 are returned in RFC 7807 (Problem Details) format with an extra code.
| Code | HTTP | Description |
|---|---|---|
text_too_long | 413 | Text exceeds the limit |
text_invalid_characters | 400 | Text contains characters unsupported by the chosen voice |
quota_exceeded | 429 | Plan limit exhausted |
streaming_forbidden | 403 | Streaming is not available on the current plan |
audio_invalid | 400 | Invalid audio file |
audio_too_large | 413 | Audio file exceeds the allowed size |
job_not_found | 404 | Job not found |
engine_unavailable | 503 | Inference is temporarily unavailable |
premium_voice_forbidden | 403 | Premium engine is not available on the current plan |
clone_forbidden | 403 | Cloning is not available on the current plan |
diarization_forbidden | 403 | Diarization is not available on the current plan |
subtitles_unavailable | 409 | Subtitles unavailable: the job is not completed yet |
audio_too_long | 413 | Audio exceeds the allowed duration |
Official Python wrapper: synthesis, streaming, transcription, analysis, text intelligence, and batches.
pip install russiantts
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`.
Official TypeScript/JavaScript wrapper. Zero dependencies, Node.js 18+.
npm install russiantts
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.