Skip to main content
Action may be needed
API update
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.
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.
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.
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.
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.
These now return 400 instead of being passed to the engine:
  • output_format other than wav, s16le, s24le, s32le, f32le or f64le (lower case)
  • sample_rate outside 8,000-48,000, expressiveness outside 0-1, or num_step above 32
  • ref_text longer than 4,000 characters
An upload whose whole request body is over 21 MiB returns 413 payload_too_large.Update: send values within these ranges. See Synthesize parameters.
  • Only the documented message types are accepted: task_start, task_continue, task_cancel and session_end. Anything else gets a non-fatal invalid_message error.
  • Numeric parameters such as sample_rate must 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 429 too_many_sessions.
  • The connection now opens before the engine is reached, and failures close it with a code: 1013 when the engine is unavailable, 1008 for insufficient credits, 1011 for other errors.
Update: send only the documented messages, and handle the close codes. See Realtime WebSocket.

Added, with nothing to change

  • Errors keep status, error and message, and add a machine-readable code and a request_id. Malformed ids return 404 instead of 500.
  • Every response has an X-Request-Id header; 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-Key header 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-Key header. The ?api_key= query parameter still works but is deprecated.