> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chariot.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Speech to text quickstart

> Stream audio to Chariot over a WebSocket and get a live transcript in a few minutes.

This guide streams a WAV file to the [streaming WebSocket](/guides/speech-to-text/websocket) the way a live microphone would, prints the text as it is recognized, and then prints the final transcript.

## Prerequisites

**1. Create an API key**

Sign in to the [Chariot dashboard](https://platform.chariot.in) and generate an API key. Speech to text uses the same key and `chariotai-api-key` header as text to speech.

**2. Prepare an audio file**

Use a **16-bit mono WAV** file recorded at 8, 16, 22.05, 24, 32, 44.1, or 48 kHz. The examples read the sample rate from the file and declare it with `audio_format`, for example `pcm_16000` for 16 kHz.

**3. Install a WebSocket client**

Python: `pip install websockets` (version 14 or later). Node.js: `npm install ws` (Node.js 18 or later).

## Stream a file

```python Python theme={null}
import asyncio
import json
import wave

import websockets

API_KEY = "YOUR_API_KEY"
AUDIO_FILE = "speech.wav"  # 16-bit mono WAV


async def main():
    with wave.open(AUDIO_FILE, "rb") as wav:
        if wav.getnchannels() != 1 or wav.getsampwidth() != 2:
            raise SystemExit("Use a 16-bit mono WAV file")
        sample_rate = wav.getframerate()
        pcm = wav.readframes(wav.getnframes())

    url = (
        "wss://api.chariot.in/v1/asr/ws"
        f"?audio_format=pcm_{sample_rate}&language=en"
    )
    headers = {"chariotai-api-key": API_KEY}

    async with websockets.connect(url, additional_headers=headers) as ws:
        # The first event is session.started, or an error if the session
        # could not start.
        started = json.loads(await ws.recv())
        if started["type"] != "session.started":
            raise SystemExit(f"Could not start: {started}")
        print("Session:", started["session_id"])

        async def send_audio():
            chunk = sample_rate * 2 // 10  # 100 ms of 16-bit audio
            try:
                for i in range(0, len(pcm), chunk):
                    await ws.send(pcm[i : i + chunk])  # binary frame
                    await asyncio.sleep(0.1)  # pace like a live microphone
                await ws.send(json.dumps({"type": "input.done"}))
            except websockets.ConnectionClosed:
                pass

        sender = asyncio.create_task(send_audio())

        async for message in ws:
            event = json.loads(message)
            if event["type"] == "transcript.delta":
                print(event["text"], end="", flush=True)
            elif event["type"] == "transcript.done":
                print("\n\nFinal transcript:", event["text"])
                print("Audio seconds:", event["audio_seconds"])
                break
            elif event["type"] == "error":
                print("\nError:", event)
                if "code" in event:
                    break  # the server closes the session after this

        await sender


asyncio.run(main())
```

```javascript Node.js theme={null}
import { readFile } from "node:fs/promises";
import WebSocket from "ws";

const API_KEY = "YOUR_API_KEY";
const AUDIO_FILE = "speech.wav"; // 16-bit mono WAV

// Read the sample rate and the raw PCM samples from a WAV file.
function readWav(buf) {
  let offset = 12;
  let fmt;
  while (offset + 8 <= buf.length) {
    const id = buf.toString("ascii", offset, offset + 4);
    const size = buf.readUInt32LE(offset + 4);
    const body = offset + 8;
    if (id === "fmt ") {
      fmt = {
        channels: buf.readUInt16LE(body + 2),
        sampleRate: buf.readUInt32LE(body + 4),
        bits: buf.readUInt16LE(body + 14),
      };
    } else if (id === "data") {
      if (!fmt || fmt.channels !== 1 || fmt.bits !== 16) {
        throw new Error("Use a 16-bit mono WAV file");
      }
      return { sampleRate: fmt.sampleRate, pcm: buf.subarray(body, body + size) };
    }
    offset = body + size + (size % 2);
  }
  throw new Error("No audio data found");
}

const { sampleRate, pcm } = readWav(await readFile(AUDIO_FILE));
const url =
  "wss://api.chariot.in/v1/asr/ws" +
  `?audio_format=pcm_${sampleRate}&language=en`;

const ws = new WebSocket(url, { headers: { "chariotai-api-key": API_KEY } });
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function sendAudio() {
  const chunk = (sampleRate * 2) / 10; // 100 ms of 16-bit audio
  for (let i = 0; i < pcm.length; i += chunk) {
    if (ws.readyState !== WebSocket.OPEN) return;
    ws.send(pcm.subarray(i, i + chunk)); // binary frame
    await sleep(100); // pace like a live microphone
  }
  ws.send(JSON.stringify({ type: "input.done" }));
}

ws.on("message", (data) => {
  const event = JSON.parse(data.toString());
  switch (event.type) {
    case "session.started":
      console.log("Session:", event.session_id);
      sendAudio();
      break;
    case "transcript.delta":
      process.stdout.write(event.text);
      break;
    case "transcript.done":
      console.log("\n\nFinal transcript:", event.text);
      console.log("Audio seconds:", event.audio_seconds);
      break;
    case "error":
      console.error("\nError:", event);
      break;
  }
});

ws.on("close", (code, reason) => {
  console.log(`Closed: ${code} ${reason.toString()}`);
});
```

Save the Node.js example as `transcribe.mjs` so it can use `import` and top-level `await`.

## What happens

**1. The session starts**

After the connection opens, the server sends `session.started` with a `session_id` and the settings in effect. Start sending audio when it arrives.

**2. You stream audio**

Each binary frame carries raw 16-bit little-endian samples in the declared `audio_format`. There is no JSON envelope and no base64.

**3. Text arrives as it is recognized**

`transcript.delta` events carry new text. Append each one to show a live transcript.

**4. You finish**

`input.done` tells the server no more audio is coming. It transcribes what is left, sends `transcript.done` with the complete transcript, and closes the connection with code `1000`.

The session costs 1.5 credits per second of audio you sent. A 60-second file costs 90 credits.

## Next steps

> **[Streaming WebSocket](/guides/speech-to-text/websocket)**
>
> Every parameter, event, and close code.

> **[Languages, modes, and styles](/guides/speech-to-text/modes-and-languages)**
>
> Pick a language hint, a latency mode, and an output style.

> **[Audio formats](/guides/speech-to-text/audio-formats)**
>
> Microphone, telephony, and file audio.

> **[Session history](/guides/speech-to-text/history)**
>
> Look up past sessions and their transcripts.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.