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
| Parameter | Type | Description |
|---|---|---|
language | string | BCP 47 tag. Returns voices tagged for that language. |
gender | string | male or female. Additional values are not settled. |
limit | integer | Page size. See /docs/api-pagination for defaults and caps. |
cursor | string | Opaque 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
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier. Use this when assigning a voice. |
name | string | Display name. Not unique and not stable enough to key on. |
gender | string | Perceived gender of the voice. |
language | string | Primary language tag the voice was built for. |
accent | string | Accent label. Free-form, useful for display and filtering by eye. |
preview_url | string | Audio 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
| Code | Meaning |
|---|---|
200 | Success. |
401 | Missing or invalid key. See /docs/authentication. |
403 | Key is valid but lacks access to this resource. |
404 | Voice ID does not exist or has been withdrawn. |
429 | Rate 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.