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

# Voice search and preview

> Build an in-app voice picker — search the library, render sample audio, and let users pick a voice ID.

If your product lets users pick a voice, you'll want to render a searchable list with audio previews. The Supertone API supports this directly: `search_voices` returns voices that match your filters along with pre-rendered sample clips you can play in the browser.

## Python — filter and print samples

```python theme={"dark"}
import os
from supertone import Supertone

with Supertone(api_key=os.environ["SUPERTONE_API_KEY"]) as client:
    result = client.voices.search_voices(
        language="en,ko",
        style="happy,neutral",
        gender="female",
        age="young-adult",
        page_size=20,
    )

    for voice in result.items or []:
        sample = next(
            (s for s in (voice.samples or []) if s.language == "en"),
            None,
        )
        print(f"{voice.voice_id}\t{voice.name}\t{sample.url if sample else '(no en sample)'}")
```

## TypeScript — render a picker

```typescript theme={"dark"}
import { Supertone } from "@supertone/supertone";

const client = new Supertone({ apiKey: process.env.SUPERTONE_API_KEY });

async function loadVoices() {
  return client.voices.searchVoices({
    language: "en,ko",
    style: "happy,neutral",
    gender: "female",
    age: "young-adult",
    pageSize: 20,
  });
}

const result = await loadVoices();

for (const voice of result.items ?? []) {
  const sample = voice.samples?.find((s) => s.language === "en");
  console.log(
    voice.voiceId,
    voice.name,
    sample?.url ?? "(no en sample)",
  );
}
```

## Render a picker in the browser

If you're showing voices in a web UI, fetch the list from your backend (never call the Supertone API directly from the browser with your key), then render the sample URLs as `<audio>` elements.

```html theme={"dark"}
<!-- After your backend returns the search result as JSON -->
<ul id="voice-picker"></ul>

<script>
  async function renderVoices() {
    const res = await fetch("/api/voices?language=en&style=happy");
    const { items } = await res.json();
    const ul = document.getElementById("voice-picker");

    for (const voice of items) {
      const sample = voice.samples?.find((s) => s.language === "en");
      const li = document.createElement("li");
      li.innerHTML = `
        <button data-voice-id="${voice.voiceId}">${voice.name}</button>
        ${sample ? `<audio controls src="${sample.url}"></audio>` : ""}
      `;
      ul.appendChild(li);
    }
  }

  renderVoices();
</script>
```

Tip: the `samples` field is keyed by `(language, style, model)` — pick the sample that matches the combination your app actually uses, so the preview matches production output.

## Pagination

`search_voices` returns up to `page_size` voices (default 20, max 100) plus a `next_page_token` if there are more. Pass that token back to fetch the next page:

<Tabs>
  <Tab title="Python">
    ```python theme={"dark"}
    page = client.voices.search_voices(language="en", page_size=50)
    while True:
        for voice in page.items or []:
            handle(voice)
        if not page.next_page_token:
            break
        page = client.voices.search_voices(
            language="en",
            page_size=50,
            next_page_token=page.next_page_token,
        )
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"dark"}
    let page = await client.voices.searchVoices({ language: "en", pageSize: 50 });
    while (true) {
      for (const voice of page.items ?? []) {
        handle(voice);
      }
      if (!page.nextPageToken) break;
      page = await client.voices.searchVoices({
        language: "en",
        pageSize: 50,
        nextPageToken: page.nextPageToken,
      });
    }
    ```
  </Tab>
</Tabs>

## Tips

* **Multiple values are OR-ed.** `language=ko,en` returns voices that support **either**. Use multiple filters together for AND-style narrowing.
* **No `sort` parameter.** Results come back in their natural order; if you need custom ordering, sort client-side.
* **Cache responses.** The voice library doesn't change often. Cache lists in your backend (15–60 minutes is reasonable) to reduce calls and improve picker latency.

## Related

<CardGroup cols={2}>
  <Card title="Voices" icon="users" href="/en/docs/core-concepts/voices">
    Conceptual overview of voice IDs and the voice object.
  </Card>

  <Card title="Search voices" icon="magnifying-glass" href="/en/api-reference/endpoints/search-voices">
    Full filter parameter reference.
  </Card>
</CardGroup>
