Comsync Voice API
Streaming text-to-speech for Hindi, Hinglish and English, built for voice agents. Send text, get natural speech back while it is still being generated.
| Base URL | https://api.trycomsync.com |
| Audio | 24,000 Hz, mono, 16-bit. Raw PCM, WAV or MP3. |
| Languages | Hindi in Devanagari, English in Latin script, mixed freely in one sentence. |
| Region | Mumbai, India |
Write Hindi in Devanagari (आपका order), not in Latin letters (aapka order). The API converts common romanized Hindi automatically, but Devanagari always sounds best. If your LLM writes the reply, tell it to use Devanagari for Hindi.
Authentication
Every request needs an API key, sent as a bearer token. Request a key if you don't have one yet.
Authorization: Bearer $COMSYNC_TTS_API_KEYBrowsers can't set headers on WebSockets, so the streaming endpoint also accepts ?api_key= in the URL. Keep keys on your server; never ship them in a public web page or app.
Quickstart
Generate your first clip. The response streams as it is produced, so you can start playing before the sentence is finished.
curl https://api.trycomsync.com/v1/audio/speech \
-H "Authorization: Bearer $COMSYNC_TTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input": "नमस्ते! आपका appointment confirm हो गया है।", "voice": "siya_lively_best"}' \
-o hello.mp3
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.trycomsync.com/v1", api_key=os.environ["COMSYNC_TTS_API_KEY"])
with client.audio.speech.with_streaming_response.create(
model="comsync-run11",
voice="siya_lively_best",
input="नमस्ते! आपका appointment confirm हो गया है।",
) as response:
response.stream_to_file("hello.mp3")
import fs from "node:fs";
const res = await fetch("https://api.trycomsync.com/v1/audio/speech", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.COMSYNC_TTS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ input: "नमस्ते! आपका appointment confirm हो गया है।", voice: "siya_lively_best" }),
});
await fs.promises.writeFile("hello.mp3", Buffer.from(await res.arrayBuffer()));
LiveKit plugin
The plugin streams your LLM's text into Comsync Voice as it is written and plays audio from the first phrase. When the caller interrupts, it closes the stream and generation stops on our side within one audio frame.
- Download
livekit_comsync.pyand put it next to your agent. It needslivekit-agents1.2 or later. - Set
COMSYNC_TTS_API_KEYin your agent's environment. - Pass it as the session's TTS:
from livekit.agents import AgentSession
from livekit_comsync import TTS
session = AgentSession(
stt=...,
llm=...,
tts=TTS(voice="siya_lively_best"), # reads COMSYNC_TTS_API_KEY
)| Option | Default | Description |
|---|---|---|
api_key | $COMSYNC_TTS_API_KEY | Your API key. |
voice | siya_lively_best | Any id from Voices. Change it mid-call with tts.update_options(voice=...). |
speed | 1.0 | 0.5 to 2.0. Pitch stays natural. |
temperature | 0.3 | Lower is steadier; higher varies delivery but makes more mistakes. |
base_url | https://api.trycomsync.com | Only change this if we give you a dedicated endpoint. |
OpenAI-compatible clients
POST /v1/audio/speech follows OpenAI's speech API, so OpenAI SDKs and LiveKit's OpenAI plugin work by changing the base URL. The plugin speaks each sentence once the LLM has finished it, rather than streaming text in, so first audio is a little later than with the Comsync plugin.
import os
from livekit.plugins import openai
tts = openai.TTS(
base_url="https://api.trycomsync.com/v1",
api_key=os.environ["COMSYNC_TTS_API_KEY"],
voice="siya_lively_best",
response_format="pcm",
)pcm. MP3 works, but the decoder waits for about 240 ms of audio before playing. Both the default gpt-4o-mini-tts model setting (server-sent events) and tts-1 (raw audio) are supported; the model value is ignored.Create speech
POST/v1/audio/speechGenerates speech from text and streams it back as it is produced.
| Field | Type | Description |
|---|---|---|
input required | string | Text to speak, up to 4,000 characters. Long text is split at sentence ends and spoken in order. |
voice | string | Voice id. Default siya_lively_best. OpenAI voice names such as alloy use the default voice. |
response_format | string | mp3 (default), wav or pcm (raw 16-bit little-endian samples). |
speed | number | 0.5 to 2.0. Default 1.0. |
temperature | number | 0.05 to 1.5. Default 0.3. |
stream_format | string | audio (default) streams bytes; sse sends speech.audio.delta events with base64 audio, then speech.audio.done. |
model, instructions | string | Accepted for compatibility and ignored. |
Responses include X-Request-Id, X-Sample-Rate and X-Channels headers. Quote the request id if you contact support about a clip.
Streaming WebSocket
WSS/v1/streamFor voice agents: send text as your LLM produces it and receive audio as soon as each phrase is ready. One connection can serve a whole call, turn after turn.
{"type": "ready", "sample_rate": 24000, "encoding": "pcm_s16le"}{"type": "config", "voice": "neha_lib"}{"type": "text", "text": "जी बिल्कुल, "}, repeated as the LLM writessegment_start, binary audio frames, segment_end for each phrase{"type": "flush"} when the LLM finishes its reply{"type": "done", "ttfa_ms": 240} after the last audio of the turnMessages you send
| Type | What it does |
|---|---|
config | Sets voice, temperature or speed for text that follows. Optional. |
text | Adds text in chunks of any size. Speech starts when a phrase is complete: at a sentence end, or at a comma once the first phrase is long enough. |
flush | Ends the turn. Remaining text is spoken, then the server sends done. |
cancel | Barge-in. Stops audio immediately and drops queued text; the server replies cancelled. Clear your playback buffer too. |
close | Ends the session. |
Messages you receive
| Type | Meaning |
|---|---|
| binary frame | Raw PCM audio, 24 kHz mono 16-bit little-endian. Play frames in order. |
segment_start / segment_end | Mark each phrase, with its text and audio length. Useful for captions. |
done | All audio for the flushed turn has been sent. Includes ttfa_ms. |
error | Something went wrong with one phrase; the session stays open. code says what. |
Voices
Each voice is set by a short reference recording and tuned for conversational calls. Play any of them below; GET /v1/voices returns the full list of 24 ids with names and descriptions.
| Voice id | Name | Sounds | Listen |
|---|---|---|---|
| Loading voices | |||
Errors
Errors use OpenAI's shape: {"error": {"message", "type", "code"}}. The message always says what to change.
| Status | Meaning | What to do |
|---|---|---|
| 400 | Invalid request, such as an unknown voice or empty text | Fix the field named in the message. |
| 401 | Missing or invalid API key | Check the Authorization header. |
| 429 | Your key is at its concurrent-request limit | Wait for a request to finish, or ask us to raise the limit. |
| 503 | All voice workers are busy | Retry after a short pause with backoff. |
WebSocket connections are refused with close code 4401 for a bad key and 4429 when the key is at its limit.
Limits and performance
- First audio: about 200 ms on the server, about 300 ms end to end from Indian networks.
- Generation speed: about 2.4× faster than real time per stream, so playback never waits mid-sentence.
- Concurrency: each key has a limit on simultaneous requests, agreed when you get access. Ask us before running load tests.
- Input: up to 4,000 characters per HTTP request. For conversations, send one reply at a time over the WebSocket.
- Accuracy: 1.4% character error rate on our Hindi test set, measured with an independent speech recogniser.
Support
Questions, higher limits or a dedicated deployment: email ojas.jain@comsync.in with your request id if you have one.
Comsync Voice