---
title: "Synthesize speech"
method: POST
path: "/v1/audio/speech"
tags: ["Text To Speech (Speak)"]
---

# Synthesize speech

`POST /v1/audio/speech`

## Request

- Base URL: `https://api.pyai.com`
- URL: `https://api.pyai.com/v1/audio/speech`
- Auth: one of:
  - HTTP bearer
  - API key in header `x-api-key`

OpenAI-compatible text-to-speech. Returns audio bytes. The serving adapter defaults to incremental delivery from the streaming lane. Set `stream: false` when you require a complete buffered body with `Content-Length`. Requires the `speak:synthesize` scope.

## Request body

- object
  - `model` string: Use `pyai-speak`. The OpenAI `tts-1` and `tts-1-hd` aliases remain accepted for drop-in compatibility.
  - `input` string, required: Text to synthesize. Maximum 2000 characters; longer text answers `400 input_too_long` naming the limit and the length you sent. Split longer scripts into several requests.
  - `voice` string: A stock voice id from `GET /v1/voices` (e.g. `stock_dorit_en_us`) or a cloned voice id (e.g. `voice_abc`) created via `/v1/voice/clones`. Omit to use the platform default voice (`stock_dorit_en_us`). For drop-in OpenAI compatibility, the preset names `alloy`, `echo`, `fable`, `onyx`, `nova`, and `shimmer` are also accepted and map to PyAI stock voices.
  - `response_format` 'wav' | 'mp3' | 'opus' | 'aac' | 'flac' | 'pcm' | 'g711_ulaw' | 'g711_alaw': Output audio format. The response `Content-Type` varies by format (`audio/wav`, `audio/mpeg`, `audio/ogg`, `audio/aac`, `audio/flac`, `audio/pcm`, `audio/basic`). `pcm` returns raw, headerless 16-bit little-endian mono samples (no container) at `sample_rate`, the format voice-agent orchestrators (e.g. Vapi custom-voice, LiveKit/Pipecat) feed directly into their pipelines. `g711_ulaw`/`g711_alaw` return raw, headerless G.711 telephony audio at a fixed 8 kHz mono (for Twilio/Plivo/FreeSWITCH); `sample_rate` does not apply and is rejected unless set to `8000`.
  - `sample_rate` integer: Optional output sample rate in Hz (8000-48000), e.g. `8000`/`16000` for telephony or `24000` for wideband. Omit to use the native 24 kHz. Most relevant with `response_format: pcm`. Does not apply to `g711_ulaw`/`g711_alaw`, which are always 8 kHz mono (a conflicting value is rejected).
  - `speed` number: Reserved but not active on Speak. Sending this field currently returns `400 unsupported_parameter`.
  - `emotion` 'neutral' | 'happy' | 'sad' | 'angry' | 'fearful' | 'surprised': Optional named delivery direction, rendered in the same voice in streaming or buffered mode. Inspect the voice's `speak_emotions` in `GET /v1/voices`; an empty or absent list means directed emotion is unavailable. Unsupported voice/emotion combinations return `400 unsupported_parameter`. `neutral` preserves ordinary delivery on every voice. An unavailable emotional rendering returns an error instead of a neutral replacement. Intensity and free-form `instructions` are not supported on Speak; supplying either returns `400 unsupported_parameter`.
  - `stream` boolean: Use incremental audio delivery. The default is `true`: the adapter forwards validated chunks as they arrive. Set `false` to use the blocking lane and receive a complete buffered body with a `Content-Length`. Every catalog voice accepts both values. A voice whose serving fleet has no streaming lane is rendered on the blocking lane and the response carries `x-pyai-stream: buffered` — same body, same format, higher time-to-first-byte.
  - `seed` integer: Reserved but not active on Speak. Sending this field currently returns `400 unsupported_parameter`.
  - `temperature` number: Reserved but not active on Speak. Sending this field currently returns `400 unsupported_parameter`.

## Response `200`

Audio bytes. Bytes are delivered incrementally by default; `stream: false` returns a complete buffered body. The `Content-Type` varies by `response_format`: `audio/wav` (wav), `audio/mpeg` (mp3), `audio/ogg` (opus), `audio/aac` (aac), `audio/flac` (flac), `audio/pcm` (pcm, raw/headerless), and `audio/basic` (g711_ulaw/g711_alaw, raw/headerless G.711 at 8 kHz mono).

## Other responses

- `401`: Missing or invalid API key (`code: unauthorized`)
- `402`: Billing gate: org out of prepaid credit, per-key budget hit, or plan quota exhausted (`code: credit_exhausted | key_budget_exceeded | insufficient_quota`). Do not retry; add credit or raise the limit. A brand-new key may see this on its first call until the account is funded.
- `403`: Key lacks the required scope or the origin is not allow-listed (`code: forbidden | origin_not_allowed`)
- `409`: A cloned voice that is `ready` but has not yet reached the render tier (`code: voice_propagating`). Honour `Retry-After` (60 s) and retry the same request; do not re-enroll.
- `429`: Too many requests or too many concurrent realtime sessions; see Retry-After header (`code: rate_limit_exceeded | concurrency_limit_exceeded | daily_cap_exceeded`)

---

[API](https://skmtc.dev/pyai/apis/pyai-api.md) · [All operations](https://skmtc.dev/pyai/apis/pyai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/pyai/apis/pyai-api/revisions/271a1539185b?raw)
