The URLs, the
X-API-Key header and your existing keys all keep working: no endpoint was removed,
and keys created before this update keep full access and don’t expire. A few behaviours changed,
and some of them need a small update on your side.Check your integration
These changes can make requests that worked before fail, or return something different.Cloning and enrolling require consent
Cloning and enrolling require consent
POST /voices/clone and POST /voices/{id}/enroll now return 400 unless the form includes
consent=true. Sending it attests that you are the speaker or hold their explicit permission to
clone their voice, and the attestation is recorded on the voice.Update: add the form field consent=true to clone and enroll requests. See
Clone a voice.The voice list includes the public catalog
The voice list includes the public catalog
GET /voices returns your workspace’s voices first, then the ready public Finesse voices, in
pages of up to 100. Public voices have visibility: "public" and belong to another workspace,
so updating or deleting one returns 404.Update: if you treat the list as “my voices” (for counts, syncing or bulk deletes), keep
only the voices whose org_id is your workspace, and use page and has_more to page through.
See List voices.Plan limits return 429
Plan limits return 429
Each plan now limits how many syntheses a workspace runs at once (Free 2, Starter 5, Pro 10,
Enterprise 50). This counts speech, streams and realtime sessions together. The Free plan also
allows 2 requests per second, including polling. Over a limit, the API returns
429 with
code concurrency_limited or rate_limited and a Retry-After header.Update: retry 429 responses after Retry-After seconds, and reuse a realtime session
instead of opening several. See Rate limits.Fewer fields on voices and jobs
Fewer fields on voices and jobs
Voices no longer include
created_by, metadata or deleted_at. Clone jobs no longer include
engine_id, external_job_id, idempotency_key, params or result.Update: if you read why a voice failed from metadata.error, read the clone job’s error
instead (GET /jobs/{id}). See the voice object and
Get a job.Engine errors have their own codes
Engine errors have their own codes
When the voice engine is busy, the API returns
503 with code upstream_busy and a
Retry-After header (it used to pass the engine’s 429 through). Text the engine can’t use
returns 422 invalid_input, and other engine failures return 502 upstream_error. Stream
error events carry the same codes.Update: branch on the code field rather than the status or message, and retry 503. See
Errors.Stricter input validation
Stricter input validation
These now return
400 instead of being passed to the engine:output_formatother thanwav,s16le,s24le,s32le,f32leorf64le(lower case)sample_rateoutside 8,000-48,000,expressivenessoutside 0-1, ornum_stepabove 32ref_textlonger than 4,000 characters
413 payload_too_large.Update: send values within these ranges. See
Synthesize parameters.Realtime WebSocket follows the documented protocol strictly
Realtime WebSocket follows the documented protocol strictly
- Only the documented message types are accepted:
task_start,task_continue,task_cancelandsession_end. Anything else gets a non-fatalinvalid_messageerror. - Numeric parameters such as
sample_ratemust be JSON numbers, not strings. - A single message can be at most 1 MiB; a larger one closes the socket with
1009. - Keep to 4 open sessions per workspace; more can be refused with
429too_many_sessions. - The connection now opens before the engine is reached, and failures close it with a code:
1013when the engine is unavailable,1008for insufficient credits,1011for other errors.
Added, with nothing to change
- Errors keep
status,errorandmessage, and add a machine-readablecodeand arequest_id. Malformed ids return404instead of500. - Every response has an
X-Request-Idheader; quote it when you contact support. - Short-lived tokens (
POST /tokens) for browsers and devices. - Key scopes, expiry and rotation for new keys, in Settings → API keys.
- An
Idempotency-Keyheader on create, clone, enroll and speak, so a retry runs at most once. - An OpenAI-compatible speech endpoint, and voice aliases.
- The realtime WebSocket also accepts the key in the
X-API-Keyheader. The?api_key=query parameter still works but is deprecated.

