Overview
V2 is a direct synthesis endpoint. Unlike V1, it does not run the emotion planner or text normalization pipeline — it sends the text directly to the synthesis engine with emotion tags injected by the server's emotion planner.
This endpoint is ideal when you want:
- Direct access to the high-quality synthesis engine
- Custom emotion tagging logic on the client side
- Advanced synthesis parameters (speed, repetition_penalty, pitch_bias_st)
Base URL https://tts.girikon.ai
WebSocket WS wss://tts.girikon.ai/ws/tts/v2
Sample Rate 24000 Hz
Authentication
The WebSocket endpoint can be protected by an API key. Authentication is enabled
on the server when the TTS_API_KEYS environment variable is set
(comma-separated list of valid keys). When enabled, every connection MUST present
a valid key via one of the following methods (checked in this order):
- Query parameter
wss://tts.girikon.ai/ws/tts/v2?api_key=YOUR_KEY - Authorization header
Authorization: Bearer YOUR_KEY - Custom header
x-api-key: YOUR_KEY - First JSON message
{ "api_key": "YOUR_KEY", "text": "...", ... }
If authentication fails, the server replies with:
{ "event": "error", "message": "missing or invalid api key" }
and closes the socket with code 1008 (policy violation).
Connecting
- Open a WebSocket connection to
wss://tts.girikon.ai/ws/tts/v2(append?api_key=...if auth is enabled). - Send exactly one JSON message containing your request (see below).
- Receive a stream of JSON events.
Audio arrives in
chunk_audioevents as base64-encoded data. - The stream ends with a
doneevent, after which the server closes the socket.
Request Payload
The first (and only) JSON message you send must be a TTS request:
{
"text": "कृपया ध्यान दें, ट्रेन नंबर बारह सौ पाँच आ रही है। कृपया प्लेटफॉर्म नंबर तीन पर तैयार रहें। धन्यवाद।",
"voice": "Ankur",
"lang": "hi",
"repetition_penalty": 1.2,
"speed": 1.2,
"gain_db": 0.0,
"pitch_bias_st": 0.0,
"api_key": "YOUR_KEY"
}
| Field | Type | Description |
|---|---|---|
text required |
string | The text to synthesize. The server will automatically plan emotion tags per sentence and inject them as <emotion> tags before sending to the synthesis engine. |
voice required |
string | Voice alias (see Voice Aliases). Can also use "speaker" as an alias. |
lang optional |
string | Language code (e.g. "hi", "en"). Currently unused but retained for compatibility. |
repetition_penalty optional |
number | Repetition penalty. Defaults to 1.2. Range: 0.6–2.0. |
speed optional |
number | Speech speed multiplier. Defaults to 1.2. Range: 1.0–3.0. Higher values reduce latency but may sound unnatural. |
gain_db optional |
number | Output loudness adjustment in dB. Defaults to 0.0. Range: -20.0 to +20.0. |
pitch_bias_st optional |
number | Pitch shift in semitones. Defaults to 0.0. Range: -12.0 to +12.0. Use sparingly; beyond ±3 ST coloration becomes audible. |
api_key optional |
string | API key (only required if server has authentication enabled and you didn't supply it via query/header). |
The server automatically runs an emotion planner on your text, splitting it into sentences and tagging each with an emotion (e.g.,
<happy>, <sad>).
These tags are injected before the text is sent to the synthesis engine. You don't need to manually
add emotion tags — just provide the plain text.
Server Events
Every server message is a JSON object with an event field. You
will receive these in roughly the following order. Audio chunks may interleave
with plan / step_timings events as the pipeline pushes
sentences out as soon as they're synthesized.
- start — pipeline accepted the request. Contains engine metadata, voice, model, speed, pitch_bias_st, gain_db, language info, and supported emotions.
- plan — emotion plan for the text. Fields:
chunks(array of emotion-tagged segments),final_text(text with emotion tags injected),sentence_index. - format — sent once before the first audio chunk. Fields:
encoding("pcm_s16le"),sample_rate(24000),channels(1),bits_per_sample(16). - chunk_audio — one audio chunk. Fields:
index,encoding,audio_base64,emotion,intensity,gap_before_ms,voice. - latency — overall summary at end of stream. Includes
first_chunk_ms,total_ms,total_bytes,chunks,sample_rate. - done — pipeline finished, server is closing the socket.
- error — fatal error.
messagefield describes the cause.
Voice Aliases
Use these aliases for the voice field. The server maps them to synthesis engine models.
| Alias | Model | Voice Label |
|---|---|---|
"Ankur" | koyel-v2/male | Hindi (Male) |
"ankur" | koyel-v2/male | Hindi (Male) |
"male" | koyel-v2/male | Hindi (Male) |
"Shweta" | koyel-v2/female | Hindi (Female) |
"shweta" | koyel-v2/female | Hindi (Female) |
"female" | koyel-v2/female | Hindi (Female) |
Supported Emotions
The emotion planner labels each sentence with one of the following. These are the emotions the synthesis engine recognizes and applies during synthesis.
neutral happy sad anger fear surprised warmly excited sarcastic playfully laugh giggle afraid angry
JavaScript Example (browser)
const API_KEY = "YOUR_KEY";
const ws = new WebSocket(`wss://tts.girikon.ai/ws/tts/v2?api_key=${API_KEY}`);
ws.onopen = () => {
ws.send(JSON.stringify({
text: "कृपया ध्यान दें, ट्रेन नंबर बारह सौ पाँच आ रही है। कृपया प्लेटफॉर्म नंबर तीन पर तैयार रहें। धन्यवाद।",
voice: "Ankur",
speed: 1.2,
}));
};
let sampleRate = 24000;
const pcmQueue = [];
ws.onmessage = (msg) => {
const evt = JSON.parse(msg.data);
switch (evt.event) {
case "start":
console.log("Engine:", evt.engine);
console.log("Voice:", evt.voice);
console.log("Model:", evt.model);
console.log("Speed:", evt.speed);
console.log("Pitch bias:", evt.pitch_bias_st);
console.log("Gain:", evt.gain_db);
console.log("Language:", evt.language_name);
console.log("Supported emotions:", evt.supported_emotions);
break;
case "plan":
console.log("Plan chunks:", evt.chunks.length);
evt.chunks.forEach((c, i) => {
console.log(` [${i}] emotion=${c.emotion_v2 || "neutral"}`);
});
break;
case "format":
sampleRate = evt.sample_rate;
break;
case "chunk_audio":
const bytes = Uint8Array.from(atob(evt.audio_base64), c => c.charCodeAt(0));
const pcm = new Int16Array(bytes.buffer);
pcmQueue.push(pcm);
// ...feed into AudioContext / AudioWorklet for playback...
break;
case "latency":
console.log("Latency summary", evt);
break;
case "done":
console.log("Stream complete");
break;
case "error":
console.error("Server error:", evt.message);
break;
}
};
Python Example
import asyncio, json, base64, wave, websockets
API_KEY = "YOUR_KEY"
URL = f"wss://tts.girikon.ai/ws/tts/v2?api_key={API_KEY}"
async def main():
async with websockets.connect(URL) as ws:
await ws.send(json.dumps({
"text": "कृपया ध्यान दें, ट्रेन नंबर बारह सौ पाँच आ रही है। कृपया प्लेटफॉर्म नंबर तीन पर तैयार रहें। धन्यवाद।",
"voice": "Ankur",
"speed": 1.2,
}))
pcm_parts, sr = [], 24000
async for raw in ws:
evt = json.loads(raw)
if evt["event"] == "start":
print("Engine:", evt["engine"])
print("Voice:", evt["voice"])
print("Model:", evt["model"])
print("Speed:", evt["speed"])
print("Pitch bias:", evt["pitch_bias_st"])
print("Gain:", evt["gain_db"])
print("Language:", evt["language_name"])
elif evt["event"] == "format":
sr = evt["sample_rate"]
elif evt["event"] == "chunk_audio":
pcm_parts.append(base64.b64decode(evt["audio_base64"]))
elif evt["event"] == "done":
break
elif evt["event"] == "error":
raise RuntimeError(evt["message"])
with wave.open("out.wav", "wb") as w:
w.setnchannels(1); w.setsampwidth(2); w.setframerate(sr)
w.writeframes(b"".join(pcm_parts))
asyncio.run(main())
Errors
| Cause | Behavior |
|---|---|
| Invalid or missing API key | {"event":"error","message":"missing or invalid api key"}, then close code 1008. |
| Malformed JSON payload | {"event":"error","message":"..."}, then close. |
| Unknown voice | Server falls back to default voice ("Ankur") and continues. |
| Upstream synthesis error | {"event":"error","message":"..."} with synthesis error details, then close. |
| Empty text | {"event":"error","message":"input is required"}, then close. |
© Girikon — TTS Streaming API V2. For support, contact your account manager.