Skip to main content

Voice

Voice endpoints synthesize speech from text and return a job you poll or receive by webhook. Every endpoint has its own fields, defaults and voices; the catalog below is generated live from the API, so it always matches what is deployed — an endpoint configured in the console appears here without a docs deploy.

POST /api/v1/voice/{slug}
  • Auth: Bearer key with the generate:voice scope.
  • Requires: a positive credit balance (otherwise 402).
generate:voice is on by default

Like generate:image, the voice scope is granted to every API key at creation. If you have a key, you can call these endpoints — no extra provisioning step.

Request body

Every voice endpoint takes the same envelope. Only the contents of inputs differ, and those are what the catalog below documents per endpoint.

FieldTypeRequiredNotes
inputsobjectThe endpoint's own named inputs. Unknown keys are rejected.
webhook_urlstring-HTTPS URL notified when the job finishes. Falls back to your account-level webhook. The callback is signed — see Webhooks.

The voice input

Every voice endpoint has one input naming which voice reads the text. It is called voice unless the endpoint renames it, so read the name off the endpoint's own card in the catalog below — the card names the field and lists the values it accepts. The same name is published as voice_input in the JSON catalog, and the values are served live at:

GET /api/v1/voice/{slug}/voices

An unknown voice is rejected with 400 before anything is charged.

Response

A successful call returns 202 Accepted immediately — synthesis happens asynchronously:

{ "job_id": "64b7f5a6-f341-47c7-9c1e-7d13f6391cc6", "status": "queued" }

Then either poll or wait for the webhook:

GET /api/v1/voice/jobs/{job_id}

:::danger Verify the webhook signature Every callback carries a Standard Webhooks signature. Verify it before you act on the payload — otherwise anyone who learns your webhook URL can forge a completed job whose outputs point at a host they control. See Webhooks. :::

A completed job carries status, an outputs object with the audio artifact, and whatever envelope fields the endpoint declares. A failed job carries error and error_code — see Jobs for the code table and which failures are worth retrying.

Output URLs are presigned and expire — download the clip rather than storing the link.

Example

curl -X POST https://api.phantomrouter.ai/api/v1/voice/{slug} \
-H "Authorization: Bearer $PHANTOM_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": { "text": "A short line of speech.", "voice": "{voice-slug}" }
}'

Each card in the catalog below carries a copyable command with that endpoint's real fields — and a real voice — already filled in.

Errors

StatusCodeWhen
400INVALID_REQUESTAn input failed validation, an unknown key was sent, or the voice is unknown.
401UNAUTHORIZEDMissing or invalid key.
402PAYMENT_REQUIREDInsufficient credit balance.
403FORBIDDENKey lacks generate:voice.
404NOT_FOUNDNo such endpoint — or one your key cannot reach. See below.
A 404 is not always a typo

Endpoints can be restricted to keys holding an extra scope. A restricted endpoint returns exactly the same 404 as one that does not exist, so that a private endpoint cannot be discovered by probing. If an endpoint you were told about returns 404, ask for the scope rather than assuming the slug is wrong — the catalog below lists only the endpoints open to every key.

Catalog

Every published voice endpoint, with its exact request contract and its voices. Generated live from the API, so a change in the console appears here without a docs deploy.

Generating a client?

The same data is served as JSON at GET /api/v1/public/voice — unauthenticated, CORS-open, and ETag-cached. Each endpoint is also available on its own at GET /api/v1/public/voice/{slug}. The per-endpoint schema_version changes only when the contract itself changes, and matches the authenticated GET /api/v1/voice/{slug}/schema.

Loading the live endpoint catalogue from /api/v1/public/voice. It is also readable directly as raw catalogue JSON — no key required.