Update a voice
curl --request PATCH \
--url https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id} \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"name": "<string>",
"description": "<string>",
"language": "<string>",
"labels": {}
}
'import requests
url = "https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id}"
payload = {
"name": "<string>",
"description": "<string>",
"language": "<string>",
"labels": {}
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({name: '<string>', description: '<string>', language: '<string>', labels: {}})
};
fetch('https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'name' => '<string>',
'description' => '<string>',
'language' => '<string>',
'labels' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id}"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"language\": \"<string>\",\n \"labels\": {}\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id}")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"language\": \"<string>\",\n \"labels\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"language\": \"<string>\",\n \"labels\": {}\n}"
response = http.request(request)
puts response.read_body{
"data": {}
}Endpoints
Update a voice
Patch a voice’s mutable metadata.
PATCH
/
flam-voice-svc
/
api
/
v1
/
public
/
voices
/
{id}
Update a voice
curl --request PATCH \
--url https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id} \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"name": "<string>",
"description": "<string>",
"language": "<string>",
"labels": {}
}
'import requests
url = "https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id}"
payload = {
"name": "<string>",
"description": "<string>",
"language": "<string>",
"labels": {}
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({name: '<string>', description: '<string>', language: '<string>', labels: {}})
};
fetch('https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'name' => '<string>',
'description' => '<string>',
'language' => '<string>',
'labels' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id}"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"language\": \"<string>\",\n \"labels\": {}\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id}")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"language\": \"<string>\",\n \"labels\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"language\": \"<string>\",\n \"labels\": {}\n}"
response = http.request(request)
puts response.read_body{
"data": {}
}Updates a voice’s metadata. All fields are optional - only the fields present in the body are changed.
The voice must belong to the API key’s workspace, otherwise the request returns
404.
A non-
null labels object replaces the entire label set. Omit labels to leave the existing
tags unchanged.Authorization
string
required
Your workspace API key (
flv_…).Path parameters
string
required
The voice id.
Body
string
New display name.
string
New long-form description.
string
New primary language (BCP-47, e.g.
en).visibility is not accepted on the public API. Publishing a voice is admin-only, so any
visibility value sent here is silently ignored and the voice’s current visibility is left
unchanged.object
Replacement key/value tags, e.g.
{ "accent": "british" }. Replaces the whole label set.Response
Voice
The updated Voice object.
curl -X PATCH \
"https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/665f1a2b3c4d5e6f7a8b9c0d" \
-H "X-API-Key: $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Documentary Narrator","description":"Warm documentary voice"}'
const id = "665f1a2b3c4d5e6f7a8b9c0d";
const res = await fetch(
`https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/${id}`,
{
method: "PATCH",
headers: {
"X-API-Key": process.env.FLAM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "Documentary Narrator", description: "Warm documentary voice" }),
},
);
const { data } = await res.json();
console.log(data);
import os, requests
voice_id = "665f1a2b3c4d5e6f7a8b9c0d"
res = requests.patch(
f"https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{voice_id}",
headers={"X-API-Key": os.environ["FLAM_API_KEY"], "Content-Type": "application/json"},
json={"name": "Documentary Narrator", "description": "Warm documentary voice"},
)
print(res.json()["data"])
Response
{
"status": 200,
"data": {
"id": "665f1a2b3c4d5e6f7a8b9c0d",
"name": "Documentary Narrator",
"status": "ready",
"primary_language": "en",
"visibility": "private",
"clone_method": "instant"
},
"error": false
}
Prompt
Copy this into your coding agent (Claude Code, Cursor, Codex…) to implement this endpoint.You are implementing a typed client for one Finesse Voice API endpoint: "Update a voice".
Everything you need is in this prompt. Do not invent anything that is not specified here.
WHAT TO BUILD
- A function updateVoice(id, patch) (name it per the project's conventions) that patches a voice's
mutable metadata and returns the updated Voice.
- 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: PATCH
- URL: https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices/{id}
- Content-Type: application/json
PARAMETERS
- id (path, string, required): the voice id, a 24-character hex string (for example
"665f1a2b3c4d5e6f7a8b9c0d"). Validate it locally against /^[0-9a-f]{24}$/i and URL-encode it.
- The voice must belong to the key's workspace, otherwise 404.
REQUEST BODY (all fields optional; only the fields PRESENT in the body are changed)
- name (string): new display name.
- description (string): new long-form description.
- language (string): new primary language, BCP-47 (e.g. "en").
- labels (object): replacement flat string-to-string map, e.g. { "accent": "british" }. A
non-null labels object REPLACES THE ENTIRE label set. Omit labels to leave existing tags alone.
To add one tag, the caller must send the full desired set (existing tags from the Voice's labels
array converted to an object, plus the new one); document this on the function.
- Do NOT send visibility: it is not accepted on the public API and is silently ignored
(publishing is admin-only). Leave it out of the patch type.
- Build the JSON body only from keys the caller actually set. Never send undefined or null
placeholders for unchanged fields. Reject an empty patch locally.
{
"name": "Documentary Narrator",
"description": "Warm documentary voice"
}
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)
"data" is the updated Voice:
{
"status": 200,
"data": {
"id": "665f1a2b3c4d5e6f7a8b9c0d",
"name": "Documentary Narrator",
"status": "ready",
"primary_language": "en",
"visibility": "private",
"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
- 400: invalid request body. Do not retry.
- 401: X-API-Key missing, malformed, invalid or revoked. Do not retry.
- 404: the voice does not exist or belongs to another workspace. Do not retry.
- 429: rate limited. Retry with backoff.
- 502: upstream temporarily unavailable. Retry with backoff.
- 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.
(Repeating the same PATCH body is harmless, which is why retries are allowed here.)
ACCEPTANCE CRITERIA
- The request body contains exactly the keys the caller set, never visibility.
- The typed updated Voice from "data" is returned.
- The labels-replace semantics are documented on the function.
- 404 is a distinct not-found error. The key is never logged.
TEST PLAN
- Unit tests with mocked HTTP: body contains only the provided keys; labels object serialized as
an object; visibility is not sendable; empty patch rejected; 200 returns Voice; 404; 429 then 200.
- Smoke script: given FLAM_VOICE_ID (a voice you own), update its description to a timestamped
string, print the returned description, then restore the original value.
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.

