> ## Documentation Index
> Fetch the complete documentation index at: https://rimelabs-docs-coda-websocket-reference.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# TTS in five minutes

> Generate your first Rime TTS audio clip in five minutes using cURL, Python, JavaScript, or TypeScript.

Generate a WAV file with one authenticated request to Rime's text-to-speech API. Choose a language tab, copy the complete script, and run it from your terminal.

## Prerequisites

You need:

* **A Rime API token:** Create a free [Rime account](https://app.rime.ai/signup/) and copy your API key from the [API Tokens](https://app.rime.ai/tokens/) page.
* A language runtime, depending on which tab you follow:
  * **cURL**: a terminal with cURL installed (included with macOS and most Linux distributions).
  * **Python**: [Python 3.10](https://www.python.org/downloads/release/python-3100/) or later.
  * **JavaScript**: [Node.js 18](https://nodejs.org/) or later.
  * **TypeScript**: Node.js 18+ plus [tsx](https://github.com/privatenumber/tsx) (`npm install -g tsx`).

Each code block is tabbed by language. Pick the same language in every block.

<Info>
  These examples call Rime's HTTPS API with a standard library or built-in `fetch`. Rime does not publish an npm or PyPI SDK. Framework-specific starters are available for [Next.js](/docs/voice-agent-nextjs), [Vite](/docs/voice-agent-vite), [Express](/docs/voice-agent-express), [plain Node](/docs/voice-agent-node), and [FastAPI](/docs/voice-agent-fastapi).
</Info>

Set your API key in the environment before running an example:

<CodeGroup>
  ```bash macOS and Linux theme={null}
  export RIME_API_KEY="your_api_key_here"
  ```

  ```powershell Windows PowerShell theme={null}
  $env:RIME_API_KEY = "your_api_key_here"
  ```
</CodeGroup>

## Copy the request

Create a file called `rime_hello_world.py`, `rime_hello_world.js`, or `rime_hello_world.ts` (or run the cURL version directly in your terminal) and paste the full script:

<Accordion title="Full script (copy/paste)">
  <CodeGroup>
    ```bash cURL theme={null}
    curl --request POST \
      --url https://users.rime.ai/v1/rime-tts \
      --header "Authorization: Bearer $RIME_API_KEY" \
      --header 'Content-Type: application/json' \
      --header 'Accept: audio/wav' \
      --fail \
      --show-error \
      --output output.wav \
      --data '{
        "text": "Hello! This is Rime speaking.",
        "speaker": "celeste",
        "modelId": "coda"
      }'
    ```

    ```python Python theme={null}
    import json
    import os
    import urllib.request

    RIME_API_KEY = os.environ["RIME_API_KEY"]

    headers = {
        "Accept": "audio/wav",
        "Authorization": f"Bearer {RIME_API_KEY}",
        "Content-Type": "application/json"
    }

    payload = {
        "text": "Hello! This is Rime speaking.",
        "speaker": "celeste",
        "modelId": "coda"
    }

    data = json.dumps(payload).encode("utf-8")

    request = urllib.request.Request(
        "https://users.rime.ai/v1/rime-tts",
        data=data,
        headers=headers,
        method="POST"
    )

    with urllib.request.urlopen(request) as response:
        with open("output.wav", "wb") as f:
            while chunk := response.read(4096):
                f.write(chunk)

    print("Audio saved to output.wav")
    ```

    ```javascript JavaScript theme={null}
    const fs = require("fs");
    const { Readable } = require("stream");
    const { pipeline } = require("stream/promises");

    const RIME_API_KEY = process.env.RIME_API_KEY;
    if (!RIME_API_KEY) {
        throw new Error("Set RIME_API_KEY before running this script.");
    }

    const headers = {
        "Accept": "audio/wav",
        "Authorization": `Bearer ${RIME_API_KEY}`,
        "Content-Type": "application/json"
    };

    const payload = {
        text: "Hello! This is Rime speaking.",
        speaker: "celeste",
        modelId: "coda"
    };

    async function generateSpeech() {
        const response = await fetch("https://users.rime.ai/v1/rime-tts", {
            method: "POST",
            headers: headers,
            body: JSON.stringify(payload)
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        if (!response.body) {
            throw new Error("The response did not include an audio body.");
        }

        await pipeline(
            Readable.fromWeb(response.body),
            fs.createWriteStream("output.wav")
        );
        console.log("Audio saved to output.wav");
    }

    generateSpeech();
    ```

    ```typescript TypeScript theme={null}
    import * as fs from "fs";
    import { Readable } from "stream";
    import { pipeline } from "stream/promises";

    const RIME_API_KEY = process.env.RIME_API_KEY;
    if (!RIME_API_KEY) {
        throw new Error("Set RIME_API_KEY before running this script.");
    }

    const headers: Record<string, string> = {
        "Accept": "audio/wav",
        "Authorization": `Bearer ${RIME_API_KEY}`,
        "Content-Type": "application/json"
    };

    const payload = {
        text: "Hello! This is Rime speaking.",
        speaker: "celeste",
        modelId: "coda"
    };

    async function generateSpeech(): Promise<void> {
        const response = await fetch("https://users.rime.ai/v1/rime-tts", {
            method: "POST",
            headers: headers,
            body: JSON.stringify(payload)
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        if (!response.body) {
            throw new Error("The response did not include an audio body.");
        }

        await pipeline(
            Readable.fromWeb(response.body),
            fs.createWriteStream("output.wav")
        );
        console.log("Audio saved to output.wav");
    }

    generateSpeech();
    ```
  </CodeGroup>
</Accordion>

## Run it

The cURL tab runs the request directly. For Python, JavaScript, or TypeScript, run:

<CodeGroup>
  ```bash Python theme={null}
  python rime_hello_world.py
  ```

  ```bash JavaScript theme={null}
  node rime_hello_world.js
  ```

  ```bash TypeScript theme={null}
  npx tsx rime_hello_world.ts
  ```
</CodeGroup>

A successful request writes `output.wav` and prints:

```bash theme={null}
Audio saved to output.wav
```

## Confirm it worked

A `200` and a file on disk are not proof of audio. An unrecognized `Accept` header returns a `200` JSON response with an `audioContent` field. The file is not empty, but no audio player will open it. The cURL example uses `--fail --show-error` so HTTP errors stop the command instead of being saved as `output.wav`.

Check the file before you trust it:

```bash theme={null}
ls -l output.wav      # expect tens of kilobytes, not a few hundred bytes
file output.wav       # expect: RIFF (little-endian) data, WAVE audio
head -c 4 output.wav  # expect: RIFF
```

Then play it:

<CodeGroup>
  ```bash macOS theme={null}
  afplay output.wav
  ```

  ```bash Linux theme={null}
  aplay output.wav
  ```

  ```bash Windows theme={null}
  start output.wav
  ```
</CodeGroup>

Hearing speech is the success condition. If `head -c 4` prints `{` you received JSON, so check your `Accept` header.

## If it did not work

Everything here fails before any audio is produced. For the complete list of statuses and messages, see the [error reference](/docs/errors).

| What you see                                 | Cause                                                                          | Fix                                                                                                                     |
| :------------------------------------------- | :----------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- |
| The client reports HTTP 401                  | The API key is missing or invalid, or the account has no active subscription   | Confirm `RIME_API_KEY` is set, recopy it from the [API Tokens page](https://app.rime.ai/tokens/), and check the account |
| The client reports HTTP 400                  | A required field is missing or invalid                                         | Check `speaker`, `text`, `modelId`, and `lang` against the [error reference](/docs/errors)                              |
| The client reports that the text is too long | `text` exceeded the configured limit                                           | Split the text across requests                                                                                          |
| File starts with `{"audioContent"`           | The `Accept` header was missing or unrecognized                                | Set `Accept` to a supported value such as `audio/wav`. See [Streaming formats](/docs/streaming)                         |
| File is 0 bytes                              | The request never completed                                                    | Confirm the URL is `https://users.rime.ai/v1/rime-tts` and reachable from your network                                  |
| Audio plays but the voice is wrong           | The `speaker`, `modelId`, and `lang` combination is not one the catalog serves | An unsupported combination is not reliably rejected. Verify the voice against the [Coda catalog](/docs/voices-coda)     |

<Accordion title="How the request works">
  ### Build the request step by step

  Create a file called `rime_hello_world.py`, `rime_hello_world.js`, or `rime_hello_world.ts` and import the required library modules:

  <CodeGroup>
    ```python Python theme={null}
    import json
    import os
    import urllib.request
    ```

    ```javascript JavaScript theme={null}
    const fs = require("fs");
    const { Readable } = require("stream");
    const { pipeline } = require("stream/promises");
    ```

    ```typescript TypeScript theme={null}
    import * as fs from "fs";
    import { Readable } from "stream";
    import { pipeline } from "stream/promises";
    ```
  </CodeGroup>

  Set the request headers with your Rime API key and the expected audio format:

  <CodeGroup>
    ```python Python theme={null}
    RIME_API_KEY = os.environ["RIME_API_KEY"]

    headers = {
        "Accept": "audio/wav",
        "Authorization": f"Bearer {RIME_API_KEY}",
        "Content-Type": "application/json"
    }
    ```

    ```javascript JavaScript theme={null}
    const RIME_API_KEY = process.env.RIME_API_KEY;
    if (!RIME_API_KEY) {
        throw new Error("Set RIME_API_KEY before running this script.");
    }

    const headers = {
        "Accept": "audio/wav",
        "Authorization": `Bearer ${RIME_API_KEY}`,
        "Content-Type": "application/json"
    };
    ```

    ```typescript TypeScript theme={null}
    const RIME_API_KEY = process.env.RIME_API_KEY;
    if (!RIME_API_KEY) {
        throw new Error("Set RIME_API_KEY before running this script.");
    }

    const headers: Record<string, string> = {
        "Accept": "audio/wav",
        "Authorization": `Bearer ${RIME_API_KEY}`,
        "Content-Type": "application/json"
    };
    ```
  </CodeGroup>

  Set the text, speaker, and model in the request body:

  <CodeGroup>
    ```python Python theme={null}
    payload = {
        "text": "Hello! This is Rime speaking.",
        "speaker": "celeste",
        "modelId": "coda"
    }
    ```

    ```javascript JavaScript theme={null}
    const payload = {
        text: "Hello! This is Rime speaking.",
        speaker: "celeste",
        modelId: "coda"
    };
    ```

    ```typescript TypeScript theme={null}
    const payload = {
        text: "Hello! This is Rime speaking.",
        speaker: "celeste",
        modelId: "coda"
    };
    ```
  </CodeGroup>

  This payload includes the three required parameters:

  * `text` is the content to synthesize.
  * `speaker` selects a voice from the [voice catalog](/docs/voices).
  * `modelId` selects the model. Use `coda` for the full Coda voice lineup or `mistv3` for the lowest time to first audio.

  The [Coda API reference](/api-reference/coda/http) lists the optional request parameters.

  Send the `POST` request and write the streamed audio response to a file:

  <CodeGroup>
    ```python Python theme={null}
    data = json.dumps(payload).encode("utf-8")

    request = urllib.request.Request(
        "https://users.rime.ai/v1/rime-tts",
        data=data,
        headers=headers,
        method="POST"
    )

    with urllib.request.urlopen(request) as response:
        with open("output.wav", "wb") as f:
            while chunk := response.read(4096):
                f.write(chunk)

    print("Audio saved to output.wav")
    ```

    ```javascript JavaScript theme={null}
    async function generateSpeech() {
        const response = await fetch("https://users.rime.ai/v1/rime-tts", {
            method: "POST",
            headers: headers,
            body: JSON.stringify(payload)
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        if (!response.body) {
            throw new Error("The response did not include an audio body.");
        }

        await pipeline(
            Readable.fromWeb(response.body),
            fs.createWriteStream("output.wav")
        );
        console.log("Audio saved to output.wav");
    }

    generateSpeech();
    ```

    ```typescript TypeScript theme={null}
    async function generateSpeech(): Promise<void> {
        const response = await fetch("https://users.rime.ai/v1/rime-tts", {
            method: "POST",
            headers: headers,
            body: JSON.stringify(payload)
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        if (!response.body) {
            throw new Error("The response did not include an audio body.");
        }

        await pipeline(
            Readable.fromWeb(response.body),
            fs.createWriteStream("output.wav")
        );
        console.log("Audio saved to output.wav");
    }

    generateSpeech();
    ```
  </CodeGroup>

  These examples stream the response but write each chunk to disk. Interactive applications can play chunks as they arrive so speech begins before the complete response is generated. The [LiveKit quickstart](/docs/quickstart-livekit) shows this pattern in a conversational agent.
</Accordion>

## Choose a voice

Change the `speaker` parameter to use another voice:

<CodeGroup>
  ```python Python theme={null}
  payload = {
      "text": "Hello! This is Rime speaking.",
      "speaker": "orion",  # Try different voices here
      "modelId": "coda"
  }
  ```

  ```javascript JavaScript theme={null}
  const payload = {
      text: "Hello! This is Rime speaking.",
      speaker: "orion",  // Try different voices here
      modelId: "coda"
  };
  ```

  ```typescript TypeScript theme={null}
  const payload = {
      text: "Hello! This is Rime speaking.",
      speaker: "orion",  // Try different voices here
      modelId: "coda"
  };
  ```
</CodeGroup>

Browse all available voices on the [Voices](/docs/voices) page.

## Custom pronunciation

<Note>Custom pronunciation is supported on **Mist v1, Mist v2, and English Mist v3**. Coda and non-English Mist v3 do not support [`phonemizeBetweenBrackets`](/docs/custom-pronunciation).</Note>

The `mistv2` model lets you specify the pronunciation of brand names or uncommon words using the [Rime phonetic alphabet](/platform/rime-phonetic-alphabet). Add the custom pronunciation in curly brackets and set [`phonemizeBetweenBrackets`](/docs/custom-pronunciation) to `true`:

<CodeGroup>
  ```python Python theme={null}
  payload = {
      "text": "Welcome to {r1Ym} labs.",
      "speaker": "peak",
      "modelId": "mistv2",
      "phonemizeBetweenBrackets": True
  }
  ```

  ```javascript JavaScript theme={null}
  const payload = {
      text: "Welcome to {r1Ym} labs.",
      speaker: "peak",
      modelId: "mistv2",
      phonemizeBetweenBrackets: true
  };
  ```

  ```typescript TypeScript theme={null}
  const payload = {
      text: "Welcome to {r1Ym} labs.",
      speaker: "peak",
      modelId: "mistv2",
      phonemizeBetweenBrackets: true
  };
  ```
</CodeGroup>

See the [Rime phonetic alphabet](/platform/rime-phonetic-alphabet) for the full symbol reference, and [Pronunciation control](/platform/pronunciation-control) for an overview of all the ways to control pronunciation.

## Production choices

The [LiveKit quickstart](/docs/quickstart-livekit) extends the same streaming API into a real-time voice agent. These references cover the main model, voice, latency, and transport decisions:

<Columns cols={2}>
  <Card title="Models" icon="microchip" href="/docs/models">
    Compare Coda (flagship) and Mist v3 (fast)
  </Card>

  <Card title="Voices" icon="waveform" href="/docs/voices">
    Browse all available voice options
  </Card>

  <Card title="Latency" icon="gauge-high" href="/docs/latency">
    Optimize for real-time performance
  </Card>

  <Card title="Coda Streaming API" icon="bolt" href="/api-reference/coda/http">
    Stream audio with Coda, Rime's flagship model
  </Card>
</Columns>
