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:voicescope. - Requires: a positive credit balance (otherwise
402).
generate:voice is on by defaultLike 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.
| Field | Type | Required | Notes |
|---|---|---|---|
inputs | object | ✓ | The endpoint's own named inputs. Unknown keys are rejected. |
webhook_url | string | - | 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
| Status | Code | When |
|---|---|---|
| 400 | INVALID_REQUEST | An input failed validation, an unknown key was sent, or the voice is unknown. |
| 401 | UNAUTHORIZED | Missing or invalid key. |
| 402 | PAYMENT_REQUIRED | Insufficient credit balance. |
| 403 | FORBIDDEN | Key lacks generate:voice. |
| 404 | NOT_FOUND | No such endpoint — or one your key cannot reach. See below. |
404 is not always a typoEndpoints 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.
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.