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

# List voices

> List the voices available in your workspace.

Returns the voices in the workspace that owns the API key. Use a voice's `id` with the
[synthesize](/voice-api/synthesize-speech) endpoints.

#### Authorization

<ParamField header="X-API-Key" type="string" required>
  Your workspace API key (`flv_…`).
</ParamField>

#### Response

<ResponseField name="data.voices" type="Voice[]">
  An array of [Voice objects](/voice-api/voice-object). Only voices with status `ready` can be
  synthesized.
</ResponseField>

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices" \
    -H "X-API-Key: $FLAM_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch(
    `https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices`,
    { headers: { "X-API-Key": process.env.FLAM_API_KEY } },
  );
  const { data } = await res.json();
  console.log(data.voices);
  ```

  ```python Python theme={null}
  import os, requests

  res = requests.get(
      f"https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices",
      headers={"X-API-Key": os.environ["FLAM_API_KEY"]},
  )
  print(res.json()["data"]["voices"])
  ```
</CodeGroup>

```json Response theme={null}
{
  "status": 200,
  "data": {
    "voices": [
      {
        "id": "665f1a2b3c4d5e6f7a8b9c0d",
        "name": "Studio Narrator",
        "status": "ready",
        "primary_language": "en",
        "visibility": "public",
        "clone_method": "instant"
      }
    ]
  },
  "error": false
}
```

## Prompt

Copy this into your coding agent (Claude Code, Cursor, Codex…) to implement this endpoint.

```text theme={null}
You are implementing a typed client for one Finesse Voice API endpoint: "List voices".
Everything you need is in this prompt. Do not invent anything that is not specified here.

WHAT TO BUILD
- A function listVoices() (name it per the project's conventions) that returns the voices in the
  workspace that owns the API key, as typed Voice objects.
- A tiny helper isVoiceReady(voice) that returns voice.status === "ready" (only ready voices can
  synthesize).
- Detect the stack first: look for package.json, tsconfig.json, pyproject.toml,
  requirements.txt, go.mod, Gemfile, composer.json and similar. Use the project's language, HTTP
  client, module layout, error-handling conventions and test runner. If there is no project yet or
  the stack is unclear, use TypeScript on Node 18+ with the built-in fetch and no new dependencies.
- If the project already has a Finesse API client module, add this to it (reuse its auth, base
  URL, error type and retry helper) instead of creating a parallel one.
- Keep the base URL (https://fi.production.flamapis.com) in one constant or config value.

ENDPOINT
- Method: GET
- URL: https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices
- No path, query or body parameters. No pagination parameters are documented: make one call and
  return the array.

AUTHENTICATION
- Send the header  X-API-Key: <workspace API key>  on every request. Keys start with "flv_".
- Read the key from the environment variable FLAM_API_KEY (or the project's existing secrets or
  config mechanism). Fail fast with a clear error if it is missing, before making any request.
- Never hard-code, log or commit the key, and never ship it in browser/client-side code: call this
  from server-side code only. The key can synthesize with every voice in its workspace.
- A key is bound to one workspace. Referencing a voice or job from another workspace returns 404.

RESPONSE ENVELOPE
JSON responses use one envelope. On success "error" is false and the payload is in "data":

    {
      "status": 200,
      "data": { "...": "endpoint payload" },
      "error": false
    }

On failure "error" is true, "data" is omitted and "message" carries the detail:

    {
      "status": 404,
      "error": true,
      "message": "Voice not found"
    }

SUCCESS RESPONSE (200, application/json)
The voices are in data.voices. Return that array (empty if the workspace has none).

    {
      "status": 200,
      "data": {
        "voices": [
          {
            "id": "665f1a2b3c4d5e6f7a8b9c0d",
            "name": "Studio Narrator",
            "status": "ready",
            "primary_language": "en",
            "visibility": "public",
            "clone_method": "instant"
          }
        ]
      },
      "error": false
    }

VOICE OBJECT (define a type for it; timestamps are Unix epoch MILLISECONDS)
- id: string. Unique voice id, 24-character hex. Used by the synthesize endpoints.
- name: string. Display name.
- description: string, optional.
- status: string. Only "ready" voices can synthesize. A voice being created reports "processing",
  "failed" if creation did not succeed, and a newly created voice with no audio yet is "draft".
  Type it as a string union of these values but tolerate other strings (treat them as not usable).
- clone_method: "instant" | "professional" | "design" | "remix".
- visibility: "private" | "org" | "public".
- primary_language: string, BCP-47 (e.g. "en"). Used when a synthesis request omits language.
- labels: array of { key: string, value: string }, optional. Common keys: accent, gender, age,
  use_case. Example: [{ "key": "accent", "value": "american" }]
- settings: object, optional, present only when configured. Synthesis defaults: stability,
  similarity, style, speed, pitch, volume, use_speaker_boost, emotion, engine_params.
- sample_audio_url: string, optional. Public URL of a short preview clip.
- base_voice_id: string, optional. Present when derived from another voice (clone_method "remix").
- created_at, updated_at: integers (epoch ms).
Optional fields may be absent. Responses may contain extra fields: ignore them, never fail on them.

Example:

    {
      "id": "665f1a2b3c4d5e6f7a8b9c0d",
      "name": "Studio Narrator",
      "description": "Warm, neutral narration voice.",
      "status": "ready",
      "clone_method": "instant",
      "visibility": "public",
      "primary_language": "en",
      "labels": [
        { "key": "accent", "value": "american" },
        { "key": "gender", "value": "female" }
      ],
      "sample_audio_url": "https://storage.googleapis.com/.../sample.wav",
      "created_at": 1717939200000,
      "updated_at": 1717939260000
    }

ERRORS
- 401: X-API-Key missing, malformed, invalid or revoked. Throw an auth error; do not retry.
- 429: rate limited. Retry with backoff (see RETRIES).
- 502: upstream temporarily unavailable. Retry with backoff.
- Anything else non-2xx: throw with the envelope message.
- Surface failures as one typed error (for example FinesseApiError) carrying: HTTP status, message
  (the envelope "message" or the flat "error" string), optional feature / unit / limit / used, a
  retryable flag, and the raw body for debugging.
- Parse error bodies defensively: if a body is not valid JSON, keep the raw text in the error.

RETRIES
- Retry only 429 (rate limited), 502 (provider unavailable or warming up) and network errors or
  timeouts, with exponential backoff plus jitter (for example about 1s, 2s, 4s; at most 3 retries),
  then surface the error.
- Never retry 400, 401, 402, 403, 404 or 422: they fail the same way again.

ACCEPTANCE CRITERIA
- listVoices() sends GET to the exact URL above with the X-API-Key header and returns a typed
  Voice[] taken from data.voices.
- A missing FLAM_API_KEY fails before any network call, with a clear message.
- 401 surfaces as a distinct, non-retried error; 429/502 are retried with backoff, then surfaced.
- Unknown response fields do not break parsing; optional fields are typed as optional.
- The key never appears in logs, errors or source control.

TEST PLAN
- Unit tests with mocked HTTP in the project's test runner: 200 with voices; 200 with an empty
  array; 401 envelope; 429 followed by 200 (asserts the retry); assert the exact URL, method and
  X-API-Key header.
- A smoke script (for example scripts/finesse-list-voices-smoke.ts) that reads FLAM_API_KEY, calls
  listVoices(), prints id, name, status and primary_language for each voice plus the number of
  ready voices, and exits non-zero on error. Run it manually; do not wire it into CI.

RULES
- Implement only what this spec describes. Do not invent parameters, fields, headers, endpoints or
  error codes. If something is ambiguous, missing, or conflicts with the existing codebase, stop
  and ask me before guessing.
- Match the project's existing style and conventions; do not refactor unrelated code.
```


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