> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://onepin.ai/docs/reference/api-reference/voices/list/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://onepin.ai/_mcp/server. # List Voices GET https://api.onepin.ai/api/v1/voices List TTS voices available to the current workspace. Every filter accepts repeat-key OR semantics: `?gender=female&gender=neutral&category=narration&source=platform&source=workspace`. Filters combine across fields with AND; within a field, values OR. Hybrid search: when `search` is a non-empty string, semantic search is enabled for the deployment (`VOICE_SEMANTIC_SEARCH_ENABLED`), and the text embedder is available, `search` runs a hybrid of SEMANTIC relevance — meaning, not keywords, so "cheerful" also surfaces "bright/upbeat" voices, over the platform voices that carry a profile embedding — AND a keyword arm that matches the voice NAME (plus descriptor/tags), which also surfaces voices with no embedding and workspace-owned voices in scope, so a voice literally named by the query is found. Both arms honor the same filters and are fused into one ranking. In that mode `sort`/`order` are IGNORED (relevance order wins). If semantic search is disabled, the embedder is unavailable, or the embed call fails, `search` transparently falls back to the lexical name/tag/descriptor match described below — the response shape (`ApiCountedListResponse[VoiceOut]`) is identical either way. `language` matches a voice when any of its declared locales matches any requested value. A voice with no declared locales matches NO `language` filter — it must positively declare a locale to surface under it. This holds for platform and user-uploaded voices alike: an unclassified platform voice (catalog gap) is not treated as general-use, and a user-uploaded/cloned voice with no locale stays "language unknown" pending clone-flow detection. Passing `language` also fills `language_sample_url` on every row that has a clip in it — the same audio `GET /voices/{voice_id}/preview?language=` serves, so a caller auditioning a shortlist can play the locale-correct take straight from the list instead of a request per voice. `language_sample_locale` reports the region actually served. Both are null without the filter; `sample_url` is unaffected and still does not follow it. Platform voices are restricted to the officially supported locales: a platform voice that declares no official locale is not returned, and the locale arrays on the voices that are returned (`supported_languages`, `model_capabilities[].supported_languages`, `preview_locales`) list only official locales. A bare family code counts as official when the family is supported (`ko` qualifies because `ko-kr` is), matching the `language` filter above. Voices your workspace owns — imported, recorded, or uploaded — are exempt from both the exclusion and the narrowing: they routinely carry no declared locale at all, and hiding them would remove a customer's own voices from their own list. Multi-sort: `sort` and `order` are parallel lists. `?sort=uses_count&sort=name&order=desc&order=asc` orders primarily by uses\_count DESC, secondarily by name ASC. When `order` is shorter than `sort`, missing entries default per-field: `name=asc, created_at=desc, uses_count=desc`. When `sort` is omitted, list defaults to newest-first (or most-recently-favorited-first if `favorites_only=true`). Every sort path appends `Voice.id ASC` as a deterministic tiebreaker for pagination stability. Reference: https://onepin.ai/docs/reference/api-reference/voices/list ## Authentication - `Authorization` header (bearer token, required) — Clerk JWT token - `Authorization` header (bearer token, required) — Onepin live API key (`op_live_...`). Test and public keys are reserved in Phase 1. ## Servers - `https://api.onepin.ai` (prod, default) - `https://dev-api.onepin.ai` (dev) ## Request ### Query parameters - `offset` (integer, optional, default: 0) — Number of results to skip for pagination. - `limit` (integer, optional, default: 20) — Maximum number of results to return (1–100). - `favorites_only` (boolean, optional, default: false) — When true, return only voices in the workspace's favorites list. - `source` (list of enum, optional, nullable) — Repeat for OR across scopes: `platform` for system-provided voices, `workspace` for workspace-owned voices. - Allowed values: `platform`, `recorded`, `uploaded`, `workspace` - `gender` (list of enum, optional, nullable) — Repeat for OR - Allowed values: `male`, `female`, `neutral` - `age` (list of enum, optional, nullable) — Repeat for OR - Allowed values: `young`, `middle_aged`, `old` - `category` (list of enum, optional, nullable) — Repeat for OR - Allowed values: `news`, `narration`, `story`, `podcast`, `conversational`, `social_media`, `educational`, `business` - `accent` (list of enum, optional, nullable) — Repeat for OR - Allowed values: `american`, `british`, `australian`, `indian`, `canadian`, `irish`, `african`, `european`, `east_asian`, `southeast_asian`, `latin_american`, `middle_eastern`, `slavic`, `spanish`, `french`, `german`, `italian`, `portuguese`, `japanese`, `korean`, `chinese`, `thai`, `vietnamese`, `other` - `search` (string, optional, nullable) — Searches name, tags, and the voice's summary-derived descriptor text (closely tracks the served description; summary beyond 200 chars is not searched). - `sort` (list of enum, optional, nullable) — Repeat for multi-sort. Pairs with `order` index-wise. - Allowed values: `name`, `created_at`, `uses_count` - `order` (list of enum, optional, nullable) — Parallel to sort[]; shorter is padded with per-field defaults. - Allowed values: `asc`, `desc` - `provider` (list of string, optional, nullable) — Repeat for OR, e.g. ?provider=elevenlabs&provider=rime - `model` (list of string, optional, nullable) — Repeat for OR. Filters platform voices by TTS model, e.g. ?model=arcana&model=sonic-2 - `language` (list of enum, optional, nullable) — Repeat for OR, e.g. ?language=en-us&language=ko-kr - Allowed values: `de-de`, `en-gb`, `en-us`, `es-es`, `es-mx`, `fr-fr`, `ja-jp`, `ko-kr`, `pt-br`, `pt-pt`, `zh-cn` ### Headers - `X-Workspace-Id` (string, optional, nullable) ## Response ### 200 Successful Response - `data` (list of VoiceOut, required) - `meta` (Meta, required) - `pagination` (CountedPaginationMeta, required) — PaginationMeta variant for endpoints that compute an unpaginated total. ## Errors ### 422 Unprocessable Entity Error Validation Error - `detail` (list of ValidationError, optional) ## Types ### VoiceOut - `id` (string, required) — Unique voice identifier. - `name` (string, required) — Display name of the voice. - `provider` (string, required) — Speech synthesis provider code for this voice. - `provider_voice_id` (string, required) — Provider-assigned voice identifier used when submitting synthesis requests. - `is_active` (boolean, required) — Whether the voice is available for use. Inactive voices are not returned by list or synthesis endpoints. - `created_at` (datetime, required) — When the voice was added to the platform or workspace. - `updated_at` (datetime, required) — When the voice record was last updated. - `description` (string, optional, nullable) — Human-readable description of the voice's character and style. - `gender` (enum, optional, nullable) — Perceived gender presentation of the voice. - Allowed values: `male`, `female`, `neutral` - `accent` (enum, optional, nullable) — Accent of the voice, if classified. - Allowed values: `american`, `british`, `australian`, `indian`, `canadian`, `irish`, `african`, `european`, `east_asian`, `southeast_asian`, `latin_american`, `middle_eastern`, `slavic`, `spanish`, `french`, `german`, `italian`, `portuguese`, `japanese`, `korean`, `chinese`, `thai`, `vietnamese`, `other` - `age` (enum, optional, nullable) — Perceived age range of the voice, if classified. - Allowed values: `young`, `middle_aged`, `old` - `category` (enum, optional, nullable) — Intended use-case category (e.g. narration, conversational). - Allowed values: `news`, `narration`, `story`, `podcast`, `conversational`, `social_media`, `educational`, `business` - `color` (string, optional, nullable) — Brand color associated with the voice in the UI, as a hex string. - `tags` (list of string, optional, nullable) — Freeform keyword tags for filtering and search. - `uses_count` (integer, optional, nullable) — Number of times this voice has been used in workflow runs across the platform. - `user_id` (string, optional, nullable) — Owner user ID for workspace-cloned or user-created voices. Null for platform voices. - `source` (enum, optional) — Origin of the voice: `platform` for system-provided voices, `recorded`/`uploaded` for voices added or cloned by the workspace, `provider_imported` for voices imported from the workspace's own provider account. - Allowed values: `platform`, `recorded`, `uploaded`, `provider_imported` - `availability` (enum, optional, nullable) — Whether an imported voice can currently be selected. `unavailable` rows stay listed and readable but are rejected by workflow save, run, and synthesis. Null for platform and legacy workspace voices. - Allowed values: `available`, `unavailable` - `unavailable_reason` (enum, optional, nullable) — Why an imported voice is unavailable. Null when `availability` is `available` or absent. - Allowed values: `available`, `reconciliation_pending`, `provider_key_unavailable`, `provider_voice_missing`, `provider_import_unavailable` - `duration_seconds` (double, optional, nullable) — Duration of the audio sample in seconds, if available. - `sample_url` (string, optional, nullable) — Time-limited presigned URL for the audio preview sample. For a voice that declares no English locale this is one of its `preview_locales` clips when any exists, so the sample is never in a language the voice cannot speak; otherwise it is the voice's default clip, whose spoken language is not guaranteed. It does not vary with the `language` filter — read `language_sample_url` (same response, no extra request) or GET /voices/\{voice\_id}/preview for that. Valid for 1 hour; regenerate by fetching the voice again. - `language_sample_url` (string, optional, nullable) — Time-limited presigned URL for the preview clip in the requested `language`, and the one field on this model that DOES follow that filter. Populated only when the request carried `language=`; null without it, and null when the voice has no preview clip in a locale that filter expands to. It is the same row GET /voices/\{voice\_id}/preview?language= would serve, resolved through the same repository method, so a caller that plays this hears exactly what that endpoint would return — without a request per voice. Read `language_sample_locale` for the region actually served. Valid for 1 hour; regenerate by fetching the list again. - `language_sample_locale` (string, optional, nullable) — The actual regioned locale `language_sample_url` was served from, never a reflection of the request. A bare-family filter like `?language=en` expands to both `en-us` and `en-gb` and which one wins is deterministic but arbitrary, so label the clip from this and not from what you asked for. Null exactly when `language_sample_url` is. - `supported_languages` (list of string, optional, nullable) — BCP-47 language codes this voice supports, restricted to the officially supported locales. Null means the voice declares no locales at all; it is not matched by any `language` filter — a voice must positively declare a locale to surface under that filter. An empty array is a different state: the voice declared locales, but none of them is officially supported. Platform voices in that state are absent from the list endpoints entirely, so an empty array is only seen on a single-voice read. Voices your workspace owns are exempt from the restriction and report every locale they declare. - `preview_locales` (list of string, optional) — Locales this voice has ready-to-play preview audio for. Fetch it with GET /voices/\{voice\_id}/preview?language=\. This is what the voice can be HEARD in, not what it can SPEAK — see supported\_languages for that. Empty means no locale preview exists yet. - `supported_models` (list of string, optional, nullable) — Deprecated compatibility union of model identifiers this voice is compatible with. Null means compatible with all available models for the provider. - `model_capabilities` (list of VoiceModelCapabilityOut, optional) — Authoritative model-to-language observations after a successful full provider sync. Empty means no authoritative pair observation is available yet. - `is_favorite` (boolean, optional, default: false) — Whether this voice is in the current workspace's favorites list. ### Meta - `request_id` (string, required) - `timestamp` (datetime, required) ### CountedPaginationMeta PaginationMeta variant for endpoints that compute an unpaginated total. - `limit` (integer, required) - `total` (integer, required) - `next` (string, optional, nullable) - `prev` (string, optional, nullable) ### ValidationError - `loc` (list of ValidationErrorLocItems, required) - `msg` (string, required) - `type` (string, required) - `input` (any, optional) - `ctx` (ValidationErrorCtx, optional) ### VoiceModelCapabilityOut - `model` (string, required) — Model identifier for this exact voice capability. - `languages_known` (boolean, required) — Whether the provider authoritatively supplied locale metadata. False means unknown; true with an empty supported_languages array means known-empty. On app-facing responses an empty array can ALSO mean the provider declared locales for this model but none of them is officially supported — the voice was kept because a sibling model carries an official locale. Use the admin API to see the raw declarations. - `supported_languages` (list of string, optional) ### ValidationErrorLocItems ### ValidationErrorCtx ## Examples **Response** ```json { "data": [ { "id": "string", "name": "string", "provider": "string", "provider_voice_id": "string", "is_active": true, "created_at": "2024-01-15T09:30:00Z", "updated_at": "2024-01-15T09:30:00Z", "description": "string", "gender": "male", "accent": "american", "age": "young", "category": "news", "color": "string", "tags": [ "string" ], "uses_count": 1, "user_id": "string", "source": "platform", "availability": "available", "unavailable_reason": "available", "duration_seconds": 1.1, "sample_url": "string", "language_sample_url": "string", "language_sample_locale": "string", "supported_languages": [ "string" ], "preview_locales": [ "string" ], "supported_models": [ "string" ], "model_capabilities": [ { "model": "string", "languages_known": true, "supported_languages": [ "string" ] } ], "is_favorite": false } ], "meta": { "request_id": "string", "timestamp": "2024-01-15T09:30:00Z" }, "pagination": { "limit": 1, "total": 1, "next": "string", "prev": "string" } } ``` **SDK Code** ```python import requests url = "https://api.onepin.ai/api/v1/voices" headers = {"Authorization": "Bearer "} response = requests.get(url, headers=headers) print(response.json()) ``` ```javascript const url = 'https://api.onepin.ai/api/v1/voices'; const options = {method: 'GET', headers: {Authorization: 'Bearer '}}; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); } ``` ```go package main import ( "fmt" "net/http" "io" ) func main() { url := "https://api.onepin.ai/api/v1/voices" req, _ := http.NewRequest("GET", url, nil) req.Header.Add("Authorization", "Bearer ") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```ruby require 'uri' require 'net/http' url = URI("https://api.onepin.ai/api/v1/voices") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Get.new(url) request["Authorization"] = 'Bearer ' response = http.request(request) puts response.read_body ``` ```java import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.get("https://api.onepin.ai/api/v1/voices") .header("Authorization", "Bearer ") .asString(); ``` ```php request('GET', 'https://api.onepin.ai/api/v1/voices', [ 'headers' => [ 'Authorization' => 'Bearer ', ], ]); echo $response->getBody(); ``` ```csharp using RestSharp; var client = new RestClient("https://api.onepin.ai/api/v1/voices"); var request = new RestRequest(Method.GET); request.AddHeader("Authorization", "Bearer "); IRestResponse response = client.Execute(request); ``` ```swift import Foundation let headers = ["Authorization": "Bearer "] let request = NSMutableURLRequest(url: NSURL(string: "https://api.onepin.ai/api/v1/voices")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "GET" request.allHTTPHeaderFields = headers let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ``` > Onepin is a voice workflow platform that orchestrates, validates, and ships production-ready audio across 33 TTS models.