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

# Text to speech

`POST /v1/audio/speech` converts text to 24 kHz mono audio with Qwen 3 TTS (`qwen3-tts`).

## Request

* `model` (string, required) — `qwen3-tts`.
* `voice` (string, required) — Voice id from [List voices](#list-voices).
* `input` (string, required) — Text to speak, up to 1000 characters.
* `response_format` (string, optional, default `wav`) — `wav`, or `pcm` for headerless signed 16-bit little-endian samples at 24 kHz.
* `stream` (boolean, optional, default `false`) — Writes the audio bytes as they are generated.

`qwen3-tts` rejects any other generation parameter, including `speed`, with 400 `unsupported_parameter`.

The response body is `audio/wav` or `audio/pcm`.

This example picks a featured voice that is ready for `qwen3-tts` and writes the streamed audio to `speech.wav`.

<CodeGroup>
  ```python Python theme={null}
  import os

  import requests

  headers = {"Authorization": f"Bearer {os.environ['HOPPER_API_KEY']}"}
  voices = requests.get(
      "https://api.withhopper.com/voices?scope=featured", headers=headers
  ).json()["voices"]
  voice_id = next(
      v["id"]
      for v in voices
      if any(p["model_id"] == "qwen3-tts" and p["status"] == "ready" for p in v["profiles"])
  )

  response = requests.post(
      "https://api.withhopper.com/v1/audio/speech",
      headers=headers,
      json={
          "model": "qwen3-tts",
          "voice": voice_id,
          "input": "Your appointment is confirmed for Thursday at ten.",
          "response_format": "wav",
          "stream": True,
      },
      stream=True,
  )
  with open("speech.wav", "wb") as f:
      for chunk in response.iter_content(chunk_size=4096):
          f.write(chunk)
  ```

  ```typescript TypeScript theme={null}
  import { writeFile } from "node:fs/promises";

  const auth = { Authorization: `Bearer ${process.env.HOPPER_API_KEY}` };
  const { voices } = await fetch(
    "https://api.withhopper.com/voices?scope=featured",
    { headers: auth },
  ).then((r) => r.json());
  const voice = voices.find((v) =>
    v.profiles.some((p) => p.model_id === "qwen3-tts" && p.status === "ready"),
  );

  const response = await fetch("https://api.withhopper.com/v1/audio/speech", {
    method: "POST",
    headers: { ...auth, "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "qwen3-tts",
      voice: voice.id,
      input: "Your appointment is confirmed for Thursday at ten.",
      response_format: "wav",
      stream: true,
    }),
  });
  await writeFile("speech.wav", Buffer.from(await response.arrayBuffer()));
  ```

  ```bash cURL theme={null}
  VOICE_ID=$(curl -s "https://api.withhopper.com/voices?scope=featured" \
    -H "Authorization: Bearer $HOPPER_API_KEY" \
    | jq -r 'first(.voices[] | select(any(.profiles[]; .model_id == "qwen3-tts" and .status == "ready")) | .id)')

  curl https://api.withhopper.com/v1/audio/speech \
    -H "Authorization: Bearer $HOPPER_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "qwen3-tts",
      "voice": "'"$VOICE_ID"'",
      "input": "Your appointment is confirmed for Thursday at ten.",
      "response_format": "wav",
      "stream": true
    }' \
    --output speech.wav
  ```
</CodeGroup>

## List voices

`GET /voices` returns the voices your key can use, newest first.

* `scope` (string, optional, default `all`) — `featured` for the voices Hopper publishes, `mine` for the voices your organization created.
* `limit` (integer, optional, default 50) — Between 1 and 200.
* `cursor` (string, optional) — `next_cursor` from the previous page.

```bash theme={null}
curl "https://api.withhopper.com/voices?scope=featured&limit=10" \
  -H "Authorization: Bearer $HOPPER_API_KEY"
```

The response is `{"voices": [...], "next_cursor": ...}`, where each voice carries:

* `id` (string) — Pass this as `voice` on `/v1/audio/speech`.
* `name` (string) — Display name.
* `language` (string) — Language of the voice.
* `profiles` (array) — One `{model_id, status}` per model the voice is prepared for; the voice works with that model once `status` is `ready`.

`GET /voices/{id}` returns one voice. `DELETE /voices/{id}` deletes a voice your organization created.

## Clone a voice

`POST /voices/clone` creates a voice from a single clip and returns 201 with the voice object. The request is `multipart/form-data`.

* `name` (string, required) — Name for the new voice.
* `clip` (file, required) — One file, wav, mp3, or flac, 5 to 30 seconds of one speaker. Clips with under 3 seconds of speech return 400 `reference_audio_too_short`.
* `transcript` (string, optional) — What is said in the clip.
* `model_id` (string, optional, default `qwen3-tts`) — Model to prepare the voice for.

```bash theme={null}
curl https://api.withhopper.com/voices/clone \
  -H "Authorization: Bearer $HOPPER_API_KEY" \
  -F name="Support agent" \
  -F clip=@sample.wav \
  -F transcript="Thanks for calling, how can I help?" \
  -F model_id=qwen3-tts
```

Pass the returned `id` as `voice` on `/v1/audio/speech`.

Error codes are listed in [Errors](/errors).
