Overview
V3 is a direct endpoint to the Koyal V3 upstream service. Unlike V1 and V2, it does not run emotion planning or text normalization — it sends the text directly to Koyal V3 with optional instructions. This is the most "passthrough" endpoint, giving you maximum control over the synthesis parameters.
Use V3 when you want:
- Direct access to Koyal V3's latest model
- Custom instructions/prompts for the model
- Minimal server-side processing (just normalization)
- Full control over response format (PCM or WAV)
Base URL https://tts.girikon.ai
WebSocket WS wss://tts.girikon.ai/ws/tts/v3
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/v3?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/v3(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:
{
"input": "कृपया ध्यान दें, ट्रेन नंबर बारह सौ पाँच आ रही है। कृपया प्लेटफॉर्म नंबर तीन पर तैयार रहें। धन्यवाद।",
"voice": "shivani",
"instructions": "Speak in a calm, reassuring tone.",
"response_format": "pcm",
"lang": "hi",
"api_key": "YOUR_KEY"
}
| Field | Type | Description |
|---|---|---|
input required |
string | Preferred field name for the text to synthesize. The text will be normalized (numbers, dates, abbreviations) before being sent to Koyal V3. |
text optional |
string | Alias for input. Use input for consistency. |
voice required |
string | Voice alias (see Voice Aliases). Defaults to "shivani" if not provided. |
instructions optional |
string | Optional instructions/prompts for the Koyal V3 model. This can guide the model's tone, style, or delivery. Example: "Speak in a calm, reassuring tone." |
response_format optional |
string | Response format. Defaults to "pcm" ("pcm_s16le"). Must be either "pcm" or "pcm_s16le". |
lang optional |
string | Language code (e.g. "hi", "en"). Used for text normalization. Defaults to "hi". |
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 splits the input text into segments based on:
- Maximum characters per segment:
280(configurable viaORPHEUS_V3_MAX_SEGMENT_CHARS) - Maximum words per segment:
80(configurable viaORPHEUS_V3_MAX_SEGMENT_WORDS) - Minimum words to retry on error:
18(configurable viaORPHEUS_V3_RETRY_MIN_WORDS)
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
as segments are synthesized and streamed.
- start — pipeline accepted the request. Fields:
engine("koyal-v3"),voice,response_format,segments(count). - plan — text segmentation info. Fields:
text(normalized input),segments(array of{ index, text }),word_count(total words). - 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 (per segment). Fields:
index,sub_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 Koyal V3 upstream voices.
| Alias | Koyal V3 Voice |
|---|---|
"shivani" | shivani |
"muskaan" | muskaan |
"riya" | riya |
"tara" | tara |
"anika" | anika |
"alok" | alok |
JavaScript Example (browser)
const API_KEY = "YOUR_KEY";
const ws = new WebSocket(`wss://tts.girikon.ai/ws/tts/v3?api_key=${API_KEY}`);
ws.onopen = () => {
ws.send(JSON.stringify({
input: "कृपया ध्यान दें, ट्रेन नंबर बारह सौ पाँच आ रही है। कृपया प्लेटफॉर्म नंबर तीन पर तैयार रहें। धन्यवाद।",
voice: "shivani",
instructions: "Speak in a calm, reassuring tone.",
}));
};
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("Format:", evt.response_format);
console.log("Segments:", evt.segments);
break;
case "plan":
console.log("Normalized text:", evt.text);
console.log("Segments:", evt.segments.length);
evt.segments.forEach((s, i) => {
console.log(` [${i}] ${s.text.substring(0, 50)}...`);
});
console.log("Word count:", evt.word_count);
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/v3?api_key={API_KEY}"
async def main():
async with websockets.connect(URL) as ws:
await ws.send(json.dumps({
"input": "कृपया ध्यान दें, ट्रेन नंबर बारह सौ पाँच आ रही है। कृपया प्लेटफॉर्म नंबर तीन पर तैयार रहें। धन्यवाद।",
"voice": "shivani",
"instructions": "Speak in a calm, reassuring tone.",
}))
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("Format:", evt["response_format"])
print("Segments:", evt["segments"])
elif evt["event"] == "plan":
print("Normalized text:", evt["text"])
print("Segments:", len(evt["segments"]))
for i, seg in enumerate(evt["segments"]):
print(f" [{i}] {seg['text'][:50]}...")
print("Word count:", evt["word_count"])
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. |
| Missing input text | {"event":"error","message":"input is required"}, then close. |
| Empty text after normalization | {"event":"error","message":"input is empty after normalization"}, then close. |
| Invalid response_format | Server defaults to "pcm" and continues. |
| Unknown voice | Server falls back to default voice ("shivani") and continues. |
| Koyal V3 upstream error | {"event":"error","message":"..."} with upstream error details, then close. |
© Girikon — TTS Streaming API V3. For support, contact your account manager.