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:

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

  1. Query parameter  wss://tts.girikon.ai/ws/tts/v2?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/v2 (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:

{
  "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"
}
FieldTypeDescription
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).
Emotion Tagging
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.

Voice Aliases

Use these aliases for the voice field. The server maps them to synthesis engine models.

AliasModelVoice Label
"Ankur"koyel-v2/maleHindi (Male)
"ankur"koyel-v2/maleHindi (Male)
"male"koyel-v2/maleHindi (Male)
"Shweta"koyel-v2/femaleHindi (Female)
"shweta"koyel-v2/femaleHindi (Female)
"female"koyel-v2/femaleHindi (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

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