> 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/favorite-voice-api-v-1-voices-voice-id-favorite-post/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://onepin.ai/_mcp/server. # Favorite Voice POST https://api.onepin.ai/api/v1/voices/{voice_id}/favorite Add a voice to the current workspace's favorites. The response is a full `VoiceOut` and is narrowed exactly as `GET /voices/{voice_id}` is, so favoriting a voice never reveals locales the read endpoints hide. Note that favoriting a platform voice with no official locale succeeds but that voice will not appear under `?favorites_only=true`, which applies the same list restriction. Favorites are workspace-scoped, not per-user: all members of the workspace see the same favorited set. Idempotent — favoriting a voice that is already favorited succeeds without error. Returns the voice with `is_favorite=true`. Requires the caller to have at least editor role in the workspace. Returns 404 for a platform voice from a provider that is not available to your account; voices your workspace owns can always be favorited. Reference: https://onepin.ai/docs/reference/api-reference/voices/favorite-voice-api-v-1-voices-voice-id-favorite-post ## 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 ### Path parameters - `voice_id` (string, required) ### Headers - `X-Workspace-Id` (string, optional, nullable) ## Response ### 200 Successful Response - `data` (VoiceOut, required) - `meta` (Meta, required) ## 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) ### 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" } } ``` **SDK Code** ```python import requests url = "https://api.onepin.ai/api/v1/voices/voice_id/favorite" headers = {"Authorization": "Bearer "} response = requests.post(url, headers=headers) print(response.json()) ``` ```javascript const url = 'https://api.onepin.ai/api/v1/voices/voice_id/favorite'; const options = {method: 'POST', 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/voice_id/favorite" req, _ := http.NewRequest("POST", 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/voice_id/favorite") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Post.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.post("https://api.onepin.ai/api/v1/voices/voice_id/favorite") .header("Authorization", "Bearer ") .asString(); ``` ```php request('POST', 'https://api.onepin.ai/api/v1/voices/voice_id/favorite', [ 'headers' => [ 'Authorization' => 'Bearer ', ], ]); echo $response->getBody(); ``` ```csharp using RestSharp; var client = new RestClient("https://api.onepin.ai/api/v1/voices/voice_id/favorite"); var request = new RestRequest(Method.POST); 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/voice_id/favorite")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "POST" 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.