Skip to main content

API reference

Voices

Listing available voices.

Beta. This endpoint is not final. Field names and response shapes can change before general availability.

Overview

A voice is the text-to-speech identity your Finn speaks with. Voices are managed by Finn, not by your workspace, so this section of the API is read-only. You list voices, pick an ID, and assign that ID to a Finn through /docs/api-finns or in the dashboard.

If you are choosing a voice for the first time, read /docs/agents-voice for guidance on matching a voice to a use case, and /docs/agents-multilingual for how voice selection interacts with language.

List voices

curl https://api.hirefinn.ai/v1/voices \
  -H "Authorization: Bearer $FINN_API_KEY"
{
  "object": "list",
  "data": [
    {
      "id": "voice_8f2a1c",
      "name": "Aria",
      "gender": "female",
      "language": "en-US",
      "accent": "american",
      "preview_url": "https://cdn.hirefinn.ai/voice-previews/voice_8f2a1c.mp3"
    },
    {
      "id": "voice_31bd07",
      "name": "Kabir",
      "gender": "male",
      "language": "hi-IN",
      "accent": "indian",
      "preview_url": "https://cdn.hirefinn.ai/voice-previews/voice_31bd07.mp3"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Query parameters

ParameterTypeDescription
languagestringBCP 47 tag. Returns voices tagged for that language.
genderstringmale or female. Additional values are not settled.
limitintegerPage size. See /docs/api-pagination for defaults and caps.
cursorstringOpaque cursor from a prior response.

Filters combine with AND. An unknown filter value returns an empty data array, not an error, so a typo in a language tag looks the same as a language with no voices. Check the tag before assuming coverage is missing.

Voice object

FieldTypeDescription
idstringStable identifier. Use this when assigning a voice.
namestringDisplay name. Not unique and not stable enough to key on.
genderstringPerceived gender of the voice.
languagestringPrimary language tag the voice was built for.
accentstringAccent label. Free-form, useful for display and filtering by eye.
preview_urlstringAudio sample. Time-limited, so do not store it.

Whether the object also carries provider, latency tier, or per-minute cost fields is not settled. Do not build against fields you see in a response but do not see documented here.

Retrieve one voice

curl https://api.hirefinn.ai/v1/voices/voice_8f2a1c \
  -H "Authorization: Bearer $FINN_API_KEY"

Returns a single voice object. A voice ID that does not exist returns 404. A voice that was withdrawn after you saved its ID also returns 404, which is the case worth handling: withdrawal is the reason a previously valid ID stops working.

Assigning a voice

Voice IDs are not validated at write time in every path. If you assign an ID that does not exist, the failure can surface at call time as a call that never produces audio rather than as a rejected API write. Verify the ID with a retrieve call before assigning it.

import os
import requests

BASE = "https://api.hirefinn.ai/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['FINN_API_KEY']}"}

def find_voice(language, gender=None):
    params = {"language": language}
    if gender:
        params["gender"] = gender
    r = requests.get(f"{BASE}/voices", headers=HEADERS, params=params)
    r.raise_for_status()
    voices = r.json()["data"]
    if not voices:
        raise RuntimeError(f"no voices for language={language} gender={gender}")
    return voices[0]

voice = find_voice("en-US", "female")
print(voice["id"], voice["name"])
const BASE = "https://api.hirefinn.ai/v1";

type Voice = {
  id: string;
  name: string;
  gender: string;
  language: string;
  accent: string;
  preview_url: string;
};

async function listVoices(language?: string): Promise<Voice[]> {
  const url = new URL(`${BASE}/voices`);
  if (language) url.searchParams.set("language", language);

  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.FINN_API_KEY}` },
  });
  if (!res.ok) {
    throw new Error(`list voices failed: ${res.status} ${await res.text()}`);
  }
  const body = (await res.json()) as { data: Voice[] };
  return body.data;
}

listVoices("en-US").then((voices) => {
  for (const v of voices) console.log(v.id, v.name, v.accent);
});

Caching

The voice catalogue changes rarely. Fetching it on every request wastes latency and rate-limit budget. Cache the list and refresh on a schedule rather than per call.

Cache the ID, not the name or the preview_url. Names can be reused across voices and preview URLs expire.

Status codes

CodeMeaning
200Success.
401Missing or invalid key. See /docs/authentication.
403Key is valid but lacks access to this resource.
404Voice ID does not exist or has been withdrawn.
429Rate limited. See /docs/api-rate-limits.

Error bodies follow the shape in /docs/api-errors. Shared conventions for headers, IDs, and timestamps are in /docs/api-conventions.

What this endpoint does not do

You cannot create, clone, edit, or delete voices through the API. Whether voice cloning will be exposed through a public endpoint is not settled. If you need a voice that is not in the catalogue, contact /docs/support.