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:

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):

  1. Query parameter  wss://tts.girikon.ai/ws/tts/v3?api_key=YOUR_KEY
  2. Authorization header  Authorization: Bearer YOUR_KEY
  3. Custom header  x-api-key: YOUR_KEY
  4. First JSON message  { "api_key": "YOUR_KEY", "text": "...", ... }
Browsers cannot set custom headers on a WebSocket handshake, so for browser clients use the query-parameter or the first-message methods. Server-side clients (Node, Python, curl) can use any method.

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

  1. Open a WebSocket connection to wss://tts.girikon.ai/ws/tts/v3 (append ?api_key=... if auth is enabled).
  2. Send exactly one JSON message containing your request (see below).
  3. Receive a stream of JSON events. Audio arrives in chunk_audio events as base64-encoded data.
  4. The stream ends with a done event, 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"
}
FieldTypeDescription
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).
Segmentation
The server automatically splits the input text into segments based on:
  • Maximum characters per segment: 280 (configurable via ORPHEUS_V3_MAX_SEGMENT_CHARS)
  • Maximum words per segment: 80 (configurable via ORPHEUS_V3_MAX_SEGMENT_WORDS)
  • Minimum words to retry on error: 18 (configurable via ORPHEUS_V3_RETRY_MIN_WORDS)
This ensures each segment is small enough for low-latency streaming while maintaining semantic coherence.

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.

Voice Aliases

Use these aliases for the voice field. The server maps them to Koyal V3 upstream voices.

AliasKoyal 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

CauseBehavior
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.