Which mode to use
Stream a response
Send a normalPOST /v1/audio/speech request with stream: true and response_format: "pcm".
This approach streams the audio out but still needs the whole text up front. The audio streams back as it’s generated, so playback can start long before the whole text is spoken. Streaming also raises the input limit to 50,000 characters, which makes it the best way to synthesize long text.
The response body is the raw PCM, with no framing around it. With timestamps: true, it is Server-Sent Events carrying the audio and the timestamps instead, as described in Stream timestamps. Read the raw PCM in chunks as it arrives:
out.pcm. To play it as it streams instead, write each chunk to a speaker, as the WebSocket sample below does.
If you close the connection before the stream ends, you’re billed only for the text whose audio was sent in full: long input is synthesized in pieces, and billing stops at the end of the last complete piece.
Stream text in over a WebSocket
When the text arrives a few words at a time, such as an LLM reply in a voice agent, open the speech WebSocket and send the text as you get it. Audio starts streaming back while you’re still sending text, so listeners don’t have to wait for the full reply. The session takes the same settings as a POST body, minusinput. Send them in start, then send text as it arrives, and stop to speak what’s left and end the session. The audio comes back in audio events, as base64-encoded PCM in the same format as a streamed response.
You need Python 3.10 or later with the websockets (14 or later) and sounddevice packages, and speakers or headphones, since the reply plays as audio:
Python
Multiple turns and interruptions
One connection can carry a whole conversation. Instead ofstop, send flush at the end of each reply and wait for flushed, which the server sends once that reply’s audio is out; the session stays open for the next turn. When the user interrupts, send cancel: the server drops the text not yet spoken, stops the audio in progress, and answers with cancelled. Every audio event after cancelled belongs to new text.
Errors
Errors arrive as a flaterror event, with no nested error object and no param:
error; in production, check the code:
type when it has no code), followed by a close with 1008:
invalid_api_key: a missing or invalid key. The connection opens first, then the server sends theerrorand closes.rate_limit_exceeded: the key is over its rate limit.no_billing_account,insufficient_quota,monthly_cap_reached,contract_ended: billing refusals, atstartor mid-session. The key is checked again before each piece of text is synthesized.- Problems with the
startsettings that a POST would also reject, such as a model the key can’t use or an unknown voice, get the same code as on the POST.
start, rate-limit and session-limit refusals are checked before billing, so a key over either limit gets that error even if its account also can’t pay.
The server closes with standard WebSocket close codes, each preceded by an error event except the normal close after finish:
Limits and billing
- Sessions: an API key can hold as many open sessions as its rate limit for the model allows per second, rounded down, with a minimum of 1. For example, a limit of 10 requests per second or 600 per minute allows 10 open sessions. A connection over the cap is refused with
session_limit_exceededand isn’t charged against the rate limit. - Rate limit: each session counts as one request against the key’s speech rate limit, at
start, not pertextmessage. It’s the same limitPOST /v1/audio/speechuses, so sessions and POST requests draw on one budget. - Text waiting: at most 50,000 characters received but not yet spoken, the same limit as a streamed POST’s
input. More closes the session withinput_too_long. - Idle timeout: a session with no client message and nothing being spoken for 5 minutes is closed (
session_idle_timeout). Text waiting for more input doesn’t count as activity, so sendflush. - Billing: the same as the POST: per character of text that is spoken, with control tags not counted. These aren’t billed:
- text dropped by
cancelor by closing the connection - a piece of text whose audio was cut off partway
- a piece of text with nothing to speak, such as
...on its own - a piece of text refused for an unknown control tag
- text dropped by
- Features: text normalization, voices, reference audio, control tags, and timestamps work as on the POST. Response formats other than
pcmare not available.
Related
Speech WebSocket reference
Every client and server event, with its fields.
Create a speech
The POST endpoint and every request field.
Common error types
The REST error codes, reused by the WebSocket for authentication and billing refusals.
Pricing
TTS pricing per character.