# Svara TTS Full Documentation > The complete Svara developer documentation as a single Markdown file, for LLMs. > Live docs: https://platform.kenpathlabs.com/docs --- # Introduction Turn text into lifelike speech in 80 languages with one API call. ## Overview The Svara API turns text into lifelike spoken audio. One speech model handles 80 languages with natural code-switching, a growing voice library, zero-shot voice cloning, and token-level streaming. First audio typically arrives in a few hundred milliseconds. Use it to: - Give voice agents and chatbots a real voice, in your users’ language - Narrate articles, books, and study material at reading speed - Drive IVR and telephony flows with 8 kHz μ-law output, no transcoding - Speak an LLM’s answer while it is still being written, over WebSocket ## Base URL ```text https://platform.kenpathlabs.com/v1 ``` > During the private beta your workspace may be provisioned on a dedicated gateway URL; use the base URL shown in your [console settings](https://platform.kenpathlabs.com/dashboard/settings.md) if it differs. ## One API, three dialects The same server speaks three client protocols, so whatever you already use keeps working by changing only the base URL: - **OpenAI-style speech**: `POST /v1/audio/speech`, the primary, fully-featured surface. The official OpenAI SDKs work as-is. - **ElevenLabs-compatible**: `POST /v1/text-to-speech/{voice_id}` and friends, including timestamps and instant voice cloning. The official ElevenLabs SDKs work as-is. - **WebSocket input streaming**: send text fragments as they are produced (for example from an LLM) and receive continuous audio with seamless prosody. ## Your first call ```http POST /v1/audio/speech ``` ```bash curl -X POST https://platform.kenpathlabs.com/v1/audio/speech \ -H "Authorization: Bearer $SVARA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "voice": "3b5275e4f2", "input": "नमस्ते! Welcome to Svara.", "response_format": "mp3" }' --output hello.mp3 # voice 3b5275e4f2 = Tara ``` ```python from openai import OpenAI client = OpenAI(base_url="https://platform.kenpathlabs.com/v1", api_key=SVARA_API_KEY) audio = client.audio.speech.create( model="svara-1", voice="3b5275e4f2", # Tara input="नमस्ते! Welcome to Svara.", response_format="mp3", ) audio.write_to_file("hello.mp3") ``` ```javascript import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://platform.kenpathlabs.com/v1", apiKey: SVARA_API_KEY }); const res = await client.audio.speech.create({ model: "svara-1", voice: "3b5275e4f2", // Tara input: "नमस्ते! Welcome to Svara.", response_format: "mp3", }); await Bun.write("hello.mp3", await res.arrayBuffer()); ``` That mixed Hindi-English input is intentional: send raw text in any supported language, code-switching included, and the model handles it. No SSML, no phoneme markup. ## Next steps - [Quickstart](https://platform.kenpathlabs.com/quickstart.md): key, first request, and streaming in five minutes - [Text to speech](https://platform.kenpathlabs.com/text-to-speech.md): every request parameter explained - [Input streaming](https://platform.kenpathlabs.com/input-streaming.md): wire an LLM’s output straight into speech - [SDKs & compatibility](https://platform.kenpathlabs.com/sdks.md): using the OpenAI and ElevenLabs SDKs > A machine-readable OpenAPI spec is served live at `https://platform.kenpathlabs.com/openapi.json`: handy for generating typed clients. --- # Quickstart From API key to spoken audio in under five minutes. ## Get an API key Create a key in the [console under API keys](https://platform.kenpathlabs.com/dashboard/keys.md). Keys look like `sk_live_…` and are shown once at creation; store them in a secret manager or environment variable, never in client code or version control. ```bash export SVARA_API_KEY="sk_live_..." ``` ## Make your first request The fastest path is the OpenAI SDK you may already have installed: point it at the Svara base URL: ```bash pip install openai ``` ```python import os from openai import OpenAI client = OpenAI( base_url="https://platform.kenpathlabs.com/v1", api_key=os.environ["SVARA_API_KEY"], ) audio = client.audio.speech.create( model="svara-1", voice="3b5275e4f2", # Tara input="The quick brown fox jumps over the lazy dog.", response_format="mp3", ) audio.write_to_file("out.mp3") ``` ```javascript import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://platform.kenpathlabs.com/v1", apiKey: process.env.SVARA_API_KEY, }); const res = await client.audio.speech.create({ model: "svara-1", voice: "3b5275e4f2", // Tara input: "The quick brown fox jumps over the lazy dog.", response_format: "mp3", }); await Bun.write("out.mp3", await res.arrayBuffer()); ``` ```bash curl -X POST https://platform.kenpathlabs.com/v1/audio/speech \ -H "Authorization: Bearer $SVARA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"voice": "3b5275e4f2", "input": "The quick brown fox jumps over the lazy dog.", "response_format": "mp3"}' \ --output out.mp3 # voice 3b5275e4f2 = Tara ``` ## Stream it Buffered audio makes long text feel broken; stream by default. Set `stream: true` and consume chunks as they arrive; with `pcm` output the first bytes land in a few hundred milliseconds: ```python with client.audio.speech.with_streaming_response.create( model="svara-1", voice="3b5275e4f2", # Tara input="This sentence starts playing before it finishes generating.", response_format="pcm", # 24 kHz, 16-bit LE, mono extra_body={"stream": True}, ) as response: for chunk in response.iter_bytes(): player.feed(chunk) # your audio sink ``` ```bash curl -N -X POST https://platform.kenpathlabs.com/v1/audio/speech \ -H "Authorization: Bearer $SVARA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"voice": "3b5275e4f2", "stream": true, "response_format": "pcm", "input": "This sentence starts playing before it finishes generating."}' \ | play -t raw -r 24000 -e signed -b 16 -c 1 - # sox; voice 3b5275e4f2 = Tara ``` Full recipes for browsers, native apps, and telephony are on the [Streaming](https://platform.kenpathlabs.com/streaming.md) page. ## Explore voices and languages These endpoints are public, no key required: - `GET /v1/voices`: the current voice roster, each with a `preview_url` - `GET /v1/languages`: all 80 supported languages, for building pickers - `GET /v1/models`: available models > Prefer clicking to curling? The [console playground](https://platform.kenpathlabs.com/dashboard/playground.md) lets you try voices, languages, and multi-speaker scenes without writing code. --- # Authentication Two header styles, one key. Use whichever your client already speaks. ## Auth headers Every authenticated request carries your API key in a header. Both conventions are accepted everywhere, on every endpoint. Use whichever your client library already sends: ``` curl https://platform.kenpathlabs.com/v1/audio/speech \ -H "Authorization: Bearer $SVARA_API_KEY" \ ... ``` ``` curl https://platform.kenpathlabs.com/v1/audio/speech \ -H "xi-api-key: $SVARA_API_KEY" \ ... ``` WebSocket connections authenticate the same way: send the header on the upgrade request. Discovery endpoints (`/v1/voices`, `/v1/languages`, `/v1/models`) are public and need no key. > API keys are server-side credentials. Don’t embed them in web or mobile clients. Proxy TTS calls through your backend, or scope a low-limit key per environment. ## Managing keys Keys are created and revoked in the [console under API keys](https://platform.kenpathlabs.com/dashboard/keys.md). Each key: - is shown in full exactly once, at creation (we store only a hash) - carries its own plan: concurrency, requests-per-minute, and monthly character limits are per key (see [Rate limits](https://platform.kenpathlabs.com/rate-limits.md)) - reports its own usage; the [usage page](https://platform.kenpathlabs.com/dashboard/usage.md) breaks down requests, characters, and audio minutes per key - can be revoked instantly, without affecting your other keys A common pattern: one key per environment (`prod`, `staging`) or per integration, named accordingly, so usage attribution and revocation stay surgical. ## Auth errors Authentication failures return `401` with a structured body: ```json { "detail": { "status": "invalid_api_key", "message": "API key invalid or revoked." } } ``` | Parameter | Type | Default | Description | |---|---|---|---| | `missing_api_key` | 401 | - | No key found in either header. Send xi-api-key or Authorization: Bearer. | | `invalid_api_key` | 401 | - | The key doesn’t exist or has been revoked. | These shapes match what the official ElevenLabs SDKs expect, so their built-in error handling works unchanged. --- # Text to speech The core endpoint: text in, audio out, in any of eight formats. ## Endpoint ```http POST /v1/audio/speech ``` The core endpoint: JSON in, audio bytes out. It handles plain synthesis, streaming, all output formats, and voice cloning through one request shape. The response body is the raw audio in your requested `response_format` (or a chunked stream when `stream: true`). ## Request parameters Only `input` is required. Everything else has a serving-tuned default: **omit any parameter you don’t explicitly need** rather than hardcoding a value, so your integration inherits improvements automatically. | Parameter | Type | Default | Description | |---|---|---|---| | `input` | string | `required` | Text to synthesize (max 5,000 chars). Raw graphemes in any supported language; code-switching within one string is fine. No SSML or phonemes. | | `voice` | string | `-` | Library voice id from [GET /v1/voices](https://platform.kenpathlabs.com/voices.md): a 10-character hex code, e.g. `3b5275e4f2` (Tara). Ignored in `voice_clone` mode. | | `model` | string | `svara-1` | Optional. Any value is accepted (OpenAI-SDK compatibility); generation always uses the current Svara model. | | `response_format` | enum | `wav` | One of `mp3 opus aac flac wav pcm ulaw alaw`. See Output formats below. | | `sample_rate` | int | `24000` | Output rate in Hz: `8000 16000 22050 24000 32000 44100 48000`. Codec is 24 kHz native; others resampled in-process. | | `stream` | bool | `false` | Stream the audio as it’s generated. See [Streaming](https://platform.kenpathlabs.com/streaming.md). | | `lang` | string | `auto` | Language hint in any form (`hi`, `hin`, `hindi`, `hi-IN`). Enables number/unit normalization. Omit to auto-detect from script. | | `mode` | enum | `tts` | `tts` or `voice_clone`. See [Voice cloning](https://platform.kenpathlabs.com/voice-cloning.md). | | `bitrate_kbps` | int | - | For lossy formats (mp3/opus/aac). Defaults: mp3 128, opus 64, aac 96. | | `temperature, top_p, top_k, min_p, repetition_penalty, presence_penalty` | float | - | Sampling knobs. Defaults are mode-aware and serving-tuned; leave them unset unless you have a measured reason. | > Using an OpenAI SDK? The extended fields (`stream`, `mode`, `lang`, `sample_rate`, sampling knobs) ride in the SDK’s `extra_body` parameter; see [SDKs](https://platform.kenpathlabs.com/sdks.md). ## Output formats | Parameter | Type | Default | Description | |---|---|---|---| | `mp3` | lossy | - | Universal, small. Default 128 kbps. Best for web delivery and storage. | | `opus` | lossy | - | Efficient at low bitrates; great for real-time voice and WebRTC. | | `aac` | lossy | - | Apple-ecosystem friendly. | | `flac` | lossless | - | Archival / further processing without generation loss. | | `wav` | pcm container | - | Uncompressed, widely readable. The default. | | `pcm` | raw | - | Headerless 16-bit LE mono at your sample_rate. Lowest-latency streaming: feed straight to an audio sink. | | `ulaw / alaw` | telephony | - | 8-bit companded for telephony; pair with sample_rate 8000. | ## Languages & normalization The model covers 80 languages and switches between them mid-sentence. Send `lang` when you know it (any ISO form works) and the server normalizes numbers, dates, currency, and units into spoken form for that language. When you don’t know it, send nothing; the script is auto-detected. Don’t expose a “normalize” toggle in your UI; leave it on and let the server own it. Drive language pickers from `GET /v1/languages`. ## Examples ``` curl -X POST https://platform.kenpathlabs.com/v1/audio/speech \ -H "Authorization: Bearer $SVARA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "voice": "837faa4892", "input": "Your appointment is confirmed for 3 PM tomorrow.", "response_format": "ulaw", "sample_rate": 8000 }' --output prompt.ulaw # voice 837faa4892 = Veer ``` ``` curl -X POST https://platform.kenpathlabs.com/v1/audio/speech \ -H "Authorization: Bearer $SVARA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "voice": "8589140057", "lang": "hi", "input": "आपका बिल ₹1,250 है और 15 अगस्त तक देय है।", "response_format": "mp3" }' --output bill.mp3 # voice 8589140057 = Aanya ``` ```python audio = client.audio.speech.create( model="svara-1", voice="837faa4892", # Veer input="Your appointment is confirmed for 3 PM tomorrow.", response_format="mp3", extra_body={"lang": "en"}, ) audio.write_to_file("prompt.mp3") ``` --- # Streaming First audio in a few hundred milliseconds. Stream by default. ## How it works Set `stream: true` on `POST /v1/audio/speech` and the server returns audio in a chunked HTTP response as it’s generated, rather than buffering the whole clip first. For anything longer than a sentence this is the difference between “instant” and “broken”. Stream by default. (If you’re feeding text in incrementally, for example from an LLM, use [input streaming](https://platform.kenpathlabs.com/input-streaming.md) over WebSocket instead.) ## Formats & latency `pcm` is the lowest-latency path: headerless 16-bit LE mono at your `sample_rate` (24 kHz default), playable the instant bytes arrive. Compressed formats (`mp3`, `opus`) stream too, but add a little container overhead. > Negotiate capabilities from `GET /health`: the `engine` and `stream_formats` fields tell you exactly what the current deployment streams. Don’t assume; read it. ## Browser playback Two approaches depending on format. For `pcm`, queue decoded buffers onto an `AudioContext` with a small (~150 ms) jitter buffer: ``` const res = await fetch("/api/tts", { // your backend proxy method: "POST", body: JSON.stringify({ voice: "3b5275e4f2", stream: true, // voice: Tara response_format: "pcm", input: text }), }); const ctx = new AudioContext({ sampleRate: 24000 }); const reader = res.body.getReader(); let playAt = ctx.currentTime + 0.15; // jitter buffer for (;;) { const { value, done } = await reader.read(); if (done) break; const pcm = new Int16Array(value.buffer); const buf = ctx.createBuffer(1, pcm.length, 24000); const ch = buf.getChannelData(0); for (let i = 0; i < pcm.length; i++) ch[i] = pcm[i] / 32768; const src = ctx.createBufferSource(); src.buffer = buf; src.connect(ctx.destination); src.start(playAt); playAt += buf.duration; } ``` ``` const audio = new Audio(); const ms = new MediaSource(); audio.src = URL.createObjectURL(ms); audio.play(); ms.addEventListener("sourceopen", async () => { const sb = ms.addSourceBuffer("audio/mpeg"); const reader = res.body.getReader(); // mp3 stream from your proxy for (;;) { const { value, done } = await reader.read(); if (done) { ms.endOfStream(); break; } await new Promise((r) => { sb.addEventListener("updateend", r, { once: true }); sb.appendBuffer(value); }); } }); ``` > Keep your API key on the server. The browser should call your backend, which holds the key and proxies to Svara. ## Native & server playback On native platforms, request `pcm` and write bytes to your audio sink as they arrive; first bytes land in a few hundred milliseconds on GPU serving. Re-align to 2-byte frames across chunk boundaries (a chunk can split a sample). ```python import pyaudio pa = pyaudio.PyAudio() out = pa.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True) with client.audio.speech.with_streaming_response.create( model="svara-1", voice="3b5275e4f2", input=text, # voice: Tara response_format="pcm", extra_body={"stream": True}, ) as response: tail = b"" for chunk in response.iter_bytes(): data = tail + chunk n = len(data) - (len(data) % 2) # whole samples only out.write(data[:n]); tail = data[n:] ``` ## Telephony For phone systems, request companded 8 kHz audio directly; no transcoding step in your media server: - `{ "response_format": "ulaw", "sample_rate": 8000 }`: North American / μ-law - `{ "response_format": "alaw", "sample_rate": 8000 }`: European / A-law Resampling runs in-process and scales: 8 kHz streams are cheap, so this fans out to many concurrent calls. --- # Input streaming Pipe an LLM's token stream straight in. Speech starts before the sentence exists. ## Why input streaming The classic LLM-to-voice pipeline buffers the model’s output into full sentences before synthesizing; the user hears nothing until the first sentence completes. Svara doesn’t need the full sentence. The model was trained on interleaved text/audio chunks where each chunk is generated with the complete history of everything already said plus a ~3-word lookahead peek of what comes next. A live pipeline therefore only holds back about three words: - **Speech starts mid-sentence**, roughly ten words into the stream: measured 0.86 s to first audio versus 1.92 s for sentence buffering on the same simulated LLM stream - **Prosody stays continuous across chunk seams**: every chunk hears its past, so there are no restarts or intonation resets at chunk boundaries - With a real LLM (streaming at typical API cadence), audio began ~450 ms after the LLM’s first token Two WebSocket endpoints expose this: a native one (recommended) and an ElevenLabs-compatible one for existing EL integrations. Both authenticate with your usual header on the upgrade request; see [Authentication](https://platform.kenpathlabs.com/authentication.md). > Browsers can’t set headers on WebSocket connections, so connect from your backend and relay audio to the client. (This is the right shape anyway; your API key shouldn’t ship to browsers.) ## Native WebSocket ```http WS /v1/audio/speech/stream-input ``` Connect with query parameters, send text fragments as JSON messages the moment they’re produced, and read binary frames back: raw PCM, 16-bit LE mono at 24 kHz. The server chunks at safe boundaries and keeps rolling text-and-audio context, so chunk N+1 continues chunk N’s prosody. Don’t pre-chunk or sentence-split yourself. | Parameter | Type | Default | Description | |---|---|---|---| | `voice` | query | `-` | Library voice id (10-character hex) from GET /v1/voices. | | `lang` | query | - | Language hint: any form works (`hi`, `hin`, `hindi`, `hi-IN`). Omit to auto-detect from script. | | `mode` | query | `sentence` | Chunking strategy: `sentence` (waits for sentence boundaries) or `eager` (word-level, lowest latency; see below). | | `temperature …` | query | - | Sampling knobs ride as query params too. Omit them; the server fills serving-tuned defaults. | ### Messages you send | Parameter | Type | Default | Description | |---|---|---|---| | `{"text": "fragment "}` | JSON | - | A text fragment, as small as a single token delta. Send them as fast as they arrive. | | `{"flush": true}` | JSON | - | Force everything buffered out as audio now (for example at the end of an LLM paragraph). | | `{"text": ""}` | JSON | - | End of stream. The server synthesizes the remainder, sends `{"type": "done"}`, and closes. | ```bash pip install websockets ``` ```python import asyncio, json, os, websockets from openai import AsyncOpenAI llm = AsyncOpenAI() URL = "wss://platform.kenpathlabs.com/v1/audio/speech/stream-input?voice=3b5275e4f2&mode=eager" # voice: Tara async def speak(prompt: str): headers = {"Authorization": f"Bearer {os.environ['SVARA_API_KEY']}"} async with websockets.connect(URL, additional_headers=headers) as ws: async def feed(): stream = await llm.chat.completions.create( model="gpt-4o-mini", stream=True, messages=[{"role": "user", "content": prompt}]) async for event in stream: delta = event.choices[0].delta.content or "" if delta: await ws.send(json.dumps({"text": delta})) await ws.send(json.dumps({"text": ""})) # end of stream feeder = asyncio.create_task(feed()) async for frame in ws: # binary = PCM16 @ 24 kHz if isinstance(frame, bytes): player.feed(frame) # your audio sink elif json.loads(frame).get("type") == "done": break await feeder asyncio.run(speak("Explain how rainbows form, in three sentences.")) ``` ```javascript import WebSocket from "ws"; const ws = new WebSocket( "wss://platform.kenpathlabs.com/v1/audio/speech/stream-input?voice=3b5275e4f2&mode=eager", // voice: Tara { headers: { Authorization: `Bearer ${process.env.SVARA_API_KEY}` } }, ); ws.on("open", async () => { for await (const delta of llmTokenStream()) { // your LLM stream ws.send(JSON.stringify({ text: delta })); } ws.send(JSON.stringify({ text: "" })); // end of stream }); ws.on("message", (data, isBinary) => { if (isBinary) player.feed(data); // PCM16 @ 24 kHz mono else if (JSON.parse(data).type === "done") ws.close(); }); ``` ## Eager mode `mode=eager` is the lowest-latency configuration and maps directly onto how the model was trained. The server buffers incoming deltas at word granularity (mid-word token deltas are handled), and as soon as `chunk_words + peek_words` complete words exist it synthesizes the first `chunk_words` with the next few words attached as the lookahead peek: | Parameter | Type | Default | Description | |---|---|---|---| | `chunk_words` | query | `10` | Words per synthesis chunk. Smaller = earlier first audio, more chunk seams (they’re seamless, but each chunk has scheduling overhead). | | `peek_words` | query | `3` | Lookahead words held back so each chunk knows what’s coming. The trained sweet spot is ~3; the final chunk goes out with no peek, which is the model’s end-of-utterance signal. | With defaults, speech starts roughly 13 words into the LLM’s output; typically well before the first sentence ends. ## ElevenLabs-compatible WebSocket ```http WS /v1/text-to-speech/{voice_id}/stream-input ``` A drop-in implementation of the ElevenLabs realtime protocol, for existing EL integrations: the official EL SDKs’ WebSocket clients work unmodified. The full protocol is supported: - **BOS**: `{"text": " ", "generation_config": {"chunk_length_schedule": [120,160,250,290]}}` (schedule values clamped 50-500; `auto_mode` supported) - **Text**: `{"text": "fragment ", "try_trigger_generation": true}`, plus `{"flush": true}`; a bare space is a keepalive - **EOS**: `{"text": ""}` - **Audio frames**: `{"audio": "", "alignment": …, "normalizedAlignment": …}` in the negotiated `output_format` (query param, e.g. `pcm_24000`, `mp3_44100_128`), then `{"isFinal": true}` and a clean close (code 1000) - `inactivity_timeout` query param: 20 s default, 180 s max Character timings in `alignment` are chunk-relative and approximate: chunk boundaries are sample-exact, characters uniform within a chunk. ## Tips - **Forward raw deltas.** Don’t sentence-split, batch, or “clean up” the LLM stream client-side; the server’s chunker is the one that knows the model’s trained format. - **Buffer ~150 ms on playback.** A small jitter buffer on your audio sink absorbs network variance without hurting perceived latency. - **Keep the socket warm for turns, not sessions.** Open the connection when a reply starts, close on `{"type": "done"}`. Each connection is one utterance with one prosodic arc. - **Concurrent conversations need concurrent streams**: each open socket counts against your plan’s concurrency limit (see [Rate limits](https://platform.kenpathlabs.com/rate-limits.md)). --- # Voice cloning Clone any voice from a few seconds of reference audio. ## Encode once, reuse forever Cloning is zero-shot: give the model a few seconds of reference speech and it speaks new text in that voice, in any supported language. The reference is turned into compact codec codes once; cache those and skip re-encoding on every request. ```http POST /v1/audio/encode ``` ```bash curl -X POST https://platform.kenpathlabs.com/v1/audio/encode \ -H "Authorization: Bearer $SVARA_API_KEY" \ -F "file=@reference.wav" \ > reference_codes.json # { "codes": [ ... ] } - cache this ``` > Use 6-15 seconds of clean, single-speaker speech with little background noise. Quality of the clone tracks quality of the reference. ## Cloning per request Pass `mode: "voice_clone"` with the cached `reference_codes` (fast path), or send `reference_audio` as base64 to encode inline. In clone mode the `voice` field is ignored. ```python import json codes = json.load(open("reference_codes.json"))["codes"] audio = client.audio.speech.create( model="svara-1", input="This is my cloned voice, now speaking in a new language.", response_format="mp3", extra_body={"mode": "voice_clone", "reference_codes": codes}, ) audio.write_to_file("cloned.mp3") ``` ```bash curl -X POST https://platform.kenpathlabs.com/v1/audio/speech \ -H "Authorization: Bearer $SVARA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "mode": "voice_clone", "reference_audio": "'"$(base64 -w0 reference.wav)"'", "input": "Cloned from a raw reference clip.", "response_format": "mp3" }' --output cloned.mp3 ``` ## Saved custom voices Prefer to register a reusable named voice? The ElevenLabs-compatible instant-voice-cloning route persists the clone server-side, after which it works by id or name in any TTS call on either dialect: ```python from elevenlabs.client import ElevenLabs el = ElevenLabs(base_url="https://platform.kenpathlabs.com", api_key=SVARA_API_KEY) v = el.voices.ivc.create(name="My Voice", files=[open("reference.wav", "rb")]) el.text_to_speech.convert(voice_id=v.voice_id, text="Saved and reusable.") ``` Manage saved clones (including in the browser) from the [console Voices page](https://platform.kenpathlabs.com/dashboard/voices.md). ## Quality tips - **Don’t lower the cloning temperature.** The default is tuned to be collapse-proof; reducing it breaks long-form and cross-lingual cloning. Leave sampling knobs unset. - **Cache `reference_codes` client-side** and reuse across requests; encoding is the only per-reference cost. - **One speaker per reference.** Overlapping voices or music in the clip degrade the clone. - Cloned voices carry across languages: a reference recorded in one language can speak any of the 56. --- # Voices & languages A curated voice library across 80 languages, plus your own clones. ## The voice roster The voice library is curated and growing, spanning ages, timbres, and languages. Fetch the roster at runtime rather than hardcoding a list; store each voice’s `voice_id` and display its `name`. ```http GET /v1/voices ``` ```json { "voices": [ { "voice_id": "3b5275e4f2", "name": "Tara", "category": "premade", "description": "The first voice Svara learned to love. Warm Hindi storytelling with a patient, late-evening calm.", "labels": { "language": "hi", "use_case": "narrative_story" }, "preview_url": "/v1/voices/3b5275e4f2/preview" }, ... ] } ``` - `voice_id` is a short 10-character hex code; it is the only way to address a voice in a TTS call, on either dialect - library voices are `category: premade`; your clones are `cloned` - to clone your own: `POST /v1/audio/encode` with a 5 to 20 second clip, then generate with `mode: voice_clone` and the returned `reference_codes`. See [Voice cloning](https://platform.kenpathlabs.com/voice-cloning.md). > Browsing rather than integrating? The [console Voices page](https://platform.kenpathlabs.com/dashboard/voices.md) lets you audition every voice and filter by language and gender. ## Voice previews Every voice has a bundled preview clip; use it instead of live-synthesizing sample audio (it’s instant and free): ```http GET /v1/voices/{voice_id}/preview ``` The `preview_url` is included in each voice object from `/v1/voices`, so you rarely construct this URL by hand. ## Languages The model handles 80 languages and code-switches between them within a single request. Drive language pickers from the languages endpoint: ```http GET /v1/languages ``` When calling TTS, the `lang` hint accepts any ISO form: `hi`, `hin`, `hindi`, `hi-IN` all resolve. Sending it enables number and unit normalization; omit it to auto-detect from the script. See [Text to speech](https://platform.kenpathlabs.com/text-to-speech.md) for details. --- # Rate limits & errors Plan limits, 429 semantics, and every error shape you can receive. ## Plan limits Limits attach to each **API key**, on three independent axes: concurrent streams, requests per minute, and characters per calendar month. Your key’s exact limits are shown in the [console settings](https://platform.kenpathlabs.com/dashboard/settings.md). | Plan | Concurrent streams | Requests / min | Characters / month | |---|---|---|---| | Free | 25 | 3,000 | 5,000,000 | | Starter | 50 | 10,000 | 50,000,000 | | Pro | 100 | 30,000 | 500,000,000 | | Scale | 250 | 100,000 | unlimited | | Internal | 1,000 | 500,000 | unlimited | > These are **private-beta limits**, deliberately generous so you can build and load-test without friction. They will tighten and re-tier before general availability; we’ll give notice before changing anything that affects you. - **Concurrent streams**: in-flight requests at once. Each open connection (including a WebSocket) holds one slot; slots free on completion, and a crashed connection’s slot self-frees on a short TTL. - **Requests per minute**: fixed 60-second window, counted per key. - **Characters per month**: summed `input` length over the calendar month (UTC). `unlimited` plans aren’t metered against a quota. ## Handling 429s When a limit is hit you get `429` with a machine-readable `status` and, where applicable, a `Retry-After` header (seconds). Successful responses also carry your remaining budget: | Parameter | Type | Default | Description | |---|---|---|---| | `x-ratelimit-remaining-requests` | header | - | Requests left in the current minute window. | | `x-ratelimit-remaining-streams` | header | - | Concurrency slots free for this key right now. | | `x-ratelimit-remaining-characters` | header | - | Characters left this month (absent on unlimited plans). | Back off and retry on `rate_limit_exceeded` and `too_many_concurrent_requests` (retry after ~1 s for concurrency). Treat `insufficient_quota` as terminal until the month rolls over or your plan changes; don’t retry-storm it. ## Error catalogue All errors share one shape, so a single handler covers them: `{ "detail": { "status": "...", "message": "..." } }`. ``` { "detail": { "status": "rate_limit_exceeded", "message": "Request rate limit exceeded." } } ``` | Parameter | Type | Default | Description | |---|---|---|---| | `rate_limit_exceeded` | 429 | - | Too many requests this minute. Respect Retry-After. | | `too_many_concurrent_requests` | 429 | - | All concurrency slots for this key are in use. Retry in ~1 s. | | `insufficient_quota` | 429 | - | Monthly character quota exhausted. Terminal until reset/upgrade. | | `missing_api_key / invalid_api_key` | 401 | - | Auth problem; see [Authentication](https://platform.kenpathlabs.com/authentication.md). | > These `status` values intentionally match the OpenAI and ElevenLabs error vocabularies, so those SDKs’ built-in retry logic works against Svara unchanged. --- # SDKs & compatibility The official OpenAI and ElevenLabs SDKs work unmodified. Change one URL. ## OpenAI SDKs The official OpenAI Python and JavaScript SDKs work against Svara unmodified: point `base_url` at Svara and pass your key. Svara-specific fields ride in `extra_body` (Python) or as extra properties (JS). ```bash pip install openai ``` ```python from openai import OpenAI client = OpenAI(base_url="https://platform.kenpathlabs.com/v1", api_key=SVARA_API_KEY) audio = client.audio.speech.create( model="svara-1", voice="3b5275e4f2", # Tara input="Namaste!", response_format="mp3", extra_body={"lang": "hindi", "stream": False}, # svara extensions ) audio.write_to_file("out.mp3") ``` ```javascript import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://platform.kenpathlabs.com/v1", apiKey: SVARA_API_KEY }); const res = await client.audio.speech.create({ model: "svara-1", voice: "3b5275e4f2", // Tara input: "Namaste!", response_format: "mp3", // @ts-expect-error - svara extension lang: "hindi", }); ``` ## ElevenLabs SDKs The official ElevenLabs SDKs also work unmodified, including instant voice cloning and the realtime WebSocket. Pass any non-empty `api_key`; its presence is also how `GET /v1/models` decides to answer in the ElevenLabs array shape. ```bash pip install elevenlabs ``` ```python from elevenlabs.client import ElevenLabs client = ElevenLabs(base_url="https://platform.kenpathlabs.com", api_key=SVARA_API_KEY) audio = client.text_to_speech.convert( voice_id="3b5275e4f2", # Tara text="Namaste!", model_id="eleven_multilingual_v2", # accepted, ignored output_format="mp3_44100_128", ) ``` ```javascript import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js"; const client = new ElevenLabsClient({ baseUrl: "https://platform.kenpathlabs.com", apiKey: SVARA_API_KEY }); const stream = await client.textToSpeech.stream("3b5275e4f2", { text: "Namaste!" }); // voice: Tara ``` ## Base URL conventions > Mind the `/v1`: **OpenAI SDKs include it** in the base URL (`https://platform.kenpathlabs.com/v1`), while **ElevenLabs SDKs do not** (`https://platform.kenpathlabs.com`); they append `v1/…` themselves. ## Compatibility surface What maps, and what to expect: - **Full support**: TTS + streaming, all output formats and rates, timestamps, the realtime WebSocket, voices list/search/clone/edit, models, languages. - **Accepted and ignored**: `voice_settings` (stability/similarity/style/speed), `seed`, `model_id`, pronunciation dictionaries, `previous_text`/`next_text`, request stitching. These don’t map onto this model; requests including them still succeed. - **Stubbed**: user/subscription and voice-settings endpoints return valid, permissive shapes so SDK flows don’t break. A machine-readable OpenAPI document is served live at `https://platform.kenpathlabs.com/openapi.json`: generate typed first-party clients from it (Fern, Stainless, Scalar) when you want them. Native Svara SDKs aren’t published yet: both official SDKs already cover the REST surface, and input streaming is a short WebSocket wrapper ([see the examples](https://platform.kenpathlabs.com/input-streaming.md)). --- # API reference Every endpoint at a glance. Every endpoint at a glance. Full request parameters live on the capability pages; the live OpenAPI spec is at `https://platform.kenpathlabs.com/openapi.json`. ## Speech ```http POST /v1/audio/speech ``` Primary TTS: text in, audio out. Handles streaming and voice cloning via `stream` / `mode`. See [Text to speech](https://platform.kenpathlabs.com/text-to-speech.md). ```http POST /v1/audio/encode ``` Encode a reference clip to reusable codec codes for cloning. See [Voice cloning](https://platform.kenpathlabs.com/voice-cloning.md). ```http POST /v1/text-to-speech/{voice_id} ``` ```http POST /v1/text-to-speech/{voice_id}/stream ``` ```http POST /v1/text-to-speech/{voice_id}/with-timestamps ``` ElevenLabs-compatible synthesis, incl. streaming and character timestamps. `output_format` like `mp3_44100_128`, `pcm_24000`. See [SDKs](https://platform.kenpathlabs.com/sdks.md). ## WebSocket ```http WS /v1/audio/speech/stream-input ``` Native input streaming with `mode=eager`. Send text fragments, receive PCM. See [Input streaming](https://platform.kenpathlabs.com/input-streaming.md). ```http WS /v1/text-to-speech/{voice_id}/stream-input ``` ElevenLabs-compatible realtime WebSocket (BOS/text/flush/EOS protocol). ## Voices ```http GET /v1/voices ``` ```http GET /v2/voices ``` ```http GET /v1/voices/{voice_id} ``` ```http GET /v1/voices/{voice_id}/preview ``` Roster, search/paging, single voice, and bundled preview clip. ```http POST /v1/voices/add ``` ```http POST /v1/voices/{voice_id}/edit ``` ```http GET /v1/voices/{voice_id}/settings ``` Instant voice cloning (persisted), edit, and settings stubs. See [Voice cloning](https://platform.kenpathlabs.com/voice-cloning.md). ## Utility ```http GET /health ``` Capability negotiation: `engine`, `stream_formats`, `default_voice`. No auth. ```http GET /v1/languages ``` ```http GET /v1/models ``` Supported languages and models. No auth.