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

# Voice samples

> Generate a spoken sample of any voice from your own text.

Voice samples turn a short piece of your own text into speech in any
ThunderPhone voice — any [catalog voice](/api-reference/voices) or any of
your organization's ready [custom voices](/api-reference/custom-voices).
Use them to audition voices, preview a greeting before an agent goes live,
or let your users hear a voice say their own words.

Samples are meant for exactly that — sampling. Text is capped at 500
characters and generation is rate limited per organization. If you need
longer samples or a higher limit, contact [support@thunderphone.com](mailto:support@thunderphone.com).

<Note>
  Every endpoint on this page requires
  `Authorization: Bearer sk_live_YOUR_API_KEY`, except a signed sample URL.
  Resources are scoped to the organization associated with the credential.
</Note>

## Endpoints

| Method | Path                       | Description                                  |
| ------ | -------------------------- | -------------------------------------------- |
| `POST` | `/v1/voices/preview`       | Generate a sample and get a signed audio URL |
| `GET`  | `/v1/voices/previews/{id}` | Stream a generated sample's MP3              |

## Generate a sample

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.thunderphone.com/v1/voices/preview \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "voice": "sarah",
      "language": "en",
      "text": "Thanks for calling Northwind Dental. How can I help you today?"
    }'
  ```
</CodeGroup>

| Field      | Type   | Required | Description                                                                                                                                                                       |
| ---------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `voice`    | string | yes      | A voice `name` from [`GET /v1/voices`](/api-reference/agents#voices) — a catalog voice like `sarah`, or one of your ready custom voices like `custom:cv_2f6f90b0e9a34ee8b39be7d1` |
| `language` | string | yes      | Language code for the sample. For a custom voice, this must be the clone's language                                                                                               |
| `text`     | string | yes      | The text to speak, 1–500 characters                                                                                                                                               |

```json Response (200 OK) theme={null}
{
  "id": "vp_9c2f90b0e9a34ee8b39be7d1",
  "url": "https://api.thunderphone.com/v1/voices/previews/vp_9c2f90b0e9a34ee8b39be7d1?sig=...",
  "cached": false,
  "charged_cents": 3,
  "expires_at": "2026-08-07T14:12:08.317Z"
}
```

| Field           | Type      | Description                                                               |
| --------------- | --------- | ------------------------------------------------------------------------- |
| `id`            | string    | Identifier of the generated sample                                        |
| `url`           | string    | Signed URL that streams the sample's MP3 without authentication           |
| `cached`        | boolean   | `true` when the sample was served from cache instead of freshly generated |
| `charged_cents` | integer   | What this request cost: `3` for a fresh generation, `0` for a cache hit   |
| `expires_at`    | timestamp | ISO 8601 time the signed URL stops working                                |

## Pricing and caching

A fresh generation costs **3¢** from your prepaid balance. Repeating an
identical request — same voice, language, and text — is served from cache,
costs nothing, and returns `"cached": true` with a fresh signed URL. That
also means expired URLs are cheap to replace: re-send the same request and
you get a new one free.

Generation is limited to 30 fresh samples per organization per hour; cache
hits don't count against the limit.

## Fetch the audio

The `url` in the response is a signed link to:

`GET /v1/voices/previews/{id}?sig={signature}`

It streams `audio/mpeg` without an Authorization header, so you can hand it
straight to an `<audio>` element or a media player. Signed URLs are valid
for 7 days. You can also call the endpoint without `sig` while
authenticated:

<CodeGroup>
  ```bash cURL theme={null}
  curl \
    https://api.thunderphone.com/v1/voices/previews/vp_9c2f90b0e9a34ee8b39be7d1 \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
    --output sample.mp3
  ```
</CodeGroup>

An unsigned, unauthenticated request — or one with an invalid or expired
signature — returns `401 Unauthorized`. A sample generated for another
organization's custom voice returns `404 Not Found`.

## Errors

| Status | Code or response                                           | When                                                                        |
| ------ | ---------------------------------------------------------- | --------------------------------------------------------------------------- |
| `400`  | `{"voice":"Voice is required."}`                           | Voice is missing                                                            |
| `400`  | `{"language":"Language is required."}`                     | Language is missing                                                         |
| `400`  | `{"text":"Text must be between 1 and 500 characters."}`    | Text is empty or too long                                                   |
| `400`  | `{"voice":"Voice is not available."}`                      | Custom voice doesn't exist, isn't ready, or belongs to another organization |
| `400`  | `{"language":"Language is not available for this voice."}` | The voice doesn't speak that language                                       |
| `402`  | `insufficient_balance`                                     | Prepaid balance is below the sample price                                   |
| `422`  | `preview_unavailable_language`                             | Sample generation isn't available for this voice in that language yet       |
| `429`  | `preview_rate_limited`                                     | The organization used its 30 fresh generations this hour                    |

The `402`, `422`, and `429` responses share one shape:

```json theme={null}
{
  "code": "preview_rate_limited",
  "detail": "Sample generation limit reached. Try again later."
}
```

A `422` means the voice exists but ThunderPhone can't generate ad-hoc
samples for it in that language yet — the voice still works normally on
calls. Pick another voice or language for the sample.
