> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rime.ai/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.

By the end of this page, you'll have a WAV file of a Rime voice reading a short sentence, made with one 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`. No Rime package is required.

  Rime publishes Python [`rime-api`](https://pypi.org/project/rime-api/) and JavaScript [`@rimelabs/api`](https://www.npmjs.com/package/@rimelabs/api) with generated TTS message definitions for the standalone engine. See [package installation and scope](/docs/on-prem/protocols#python-and-javascript-packages).

  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>

The scripts read your key from the `RIME_API_KEY` environment variable, which keeps it out of files you might share or commit. Set it in the terminal you'll run them from:

<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

Each script sends "Hello! This is Rime speaking." to the `celeste` voice and saves the audio to `output.wav`, so you can replay it as many times as you need while you check it. One sentence is short enough to hear the whole result.

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

Play the file. If you hear the sentence, your key, the request, and the voice all worked.

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

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

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

A file on disk isn't proof of audio. If the player won't open it, check what the file contains:

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

A file without the `RIFF` header usually means the request reached the API with no `Accept` header or a wildcard such as `*/*`, for example because a proxy or client wrapper dropped the header. For Coda, the API then streams raw 16-bit PCM (`audio/pcm`) with no WAV header, and players won't open it. The cURL example uses `--fail --show-error` so HTTP errors stop the command instead of being saved as `output.wav`.

## If it did not work

Find the row that matches what you see. 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 |
| The client reports HTTP 406 | `Accept` names a format the API can't stream | Set `Accept` to a supported value such as `audio/wav`. The response body lists the supported types |
| File won't play and doesn't start with `RIFF` | The request arrived with no `Accept` header or a wildcard `Accept`, so the API returned raw PCM | Set `Accept` to `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

Keep the same sentence and change only `speaker`. With the words held constant, it's easier to hear what the new voice changes:

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

The [Voices](/docs/voices) page lists starter voices and the full catalog. Each voice speaks one language. If you choose a voice in another language, give it text in that language and add `lang` with the voice's language tag.

## Custom pronunciation

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

English Mist v3 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": "mistv3",
      "phonemizeBetweenBrackets": True
  }
  ```

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

  ```typescript TypeScript theme={null}
  const payload = {
      text: "Welcome to {r1Ym} labs.",
      speaker: "peak",
      modelId: "mistv3",
      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>


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