POST /audio/speech, under the base URL https://tts.us.assemblyai.com/v1/openai. Point an OpenAI SDK, or a framework that is built on one, at that base URL with your AssemblyAI API key as its API key, and it speaks with AssemblyAI voices without other changes. This page is for code that already speaks the OpenAI speech API. For a new integration, the Python and JavaScript SDKs and the native LiveKit and Pipecat plugins reach the whole API, word timings and barge-in on one session included.
The route is part of the HTTP API and behaves like Create speech: the audio streams as it is synthesized, the status waits for the first audio so every earlier failure is a real HTTP status, a failure after that point cuts the connection, and the same limits and billing apply. Only the request fields and the error body follow OpenAI; Create speech (OpenAI compatible) is the route reference.
OpenAI SDKs
- Python
- JavaScript
openai 3, which is built on httpx2; with openai 2 or earlier, write httpx for httpx2 and DefaultHttpxClient for DefaultHttpx2Client. The JavaScript example needs npm install undici, because the SDK takes a dispatcher only together with undici’s own fetch; leave both out in a browser.
The SDKs send the key as Authorization: Bearer <key>, which the API accepts. hello.pcm holds raw 16-bit mono PCM at 24 kHz. In a browser, give the JavaScript SDK a temporary token minted with product=tts as its apiKey, never your API key, and set its dangerouslyAllowBrowser option to true: without it the SDK refuses to run in a browser.
Keep connections between replies
Build the client once per process and reuse it. Our load balancer keeps an idle connection open for an hour, but the OpenAI SDKs close it much sooner: the Python SDK after 5 seconds, and the JavaScript SDK on Node after 4, the default of Node’sfetch. A voice agent whose replies are further apart than that opens a new connection, a TCP and a TLS handshake, before nearly every reply. The limits and dispatcher settings in the examples above keep idle connections for 2 minutes.
In the API’s region, a reply on a kept connection reached its first byte about 6 ms sooner with the Python SDK, after pauses of 6 and 65 seconds (40 replies per way), and too little sooner to measure with Node. From farther away, every reconnect costs two network round trips: about 120 ms between the US west coast and tts.us. To open the connection before the first reply as well, call client.models.list() when a call starts; it reads GET /v1/openai/models, which synthesizes nothing and needs no credential. See Optimizing accuracy and latency for the other habits that shorten the wait.
Voice agent frameworks
LiveKit and Pipecat both have native AssemblyAI plugins, documented on LiveKit and Pipecat, which run on the WebSocket API with one session per call, barge-in without reconnecting and word-timed transcripts. Use those for a new agent. The OpenAI plugins below are for a pipeline already built on them, and give up the word timings. Voice agents should ask forpcm: raw PCM starts playing on the first bytes and needs no decoding, while MP3 adds about 46 ms of encoder silence (at 24 kHz) to the start of every sentence.
- LiveKit
- Pipecat
Request fields
model and input are required. voice names the voice, response_format the audio (mp3 by default, pcm for a voice agent, wav, mulaw or alaw), and stream_format: "sse" asks for speech events instead of bytes. Create speech (OpenAI compatible) lists every field, its values and its refusals; the sections below cover the ones that are not plain OpenAI.
Models
Names are case-insensitive. OpenAI’s model names, such as
tts-1, are refused with 400 and the code model_not_found; List models (OpenAI compatible) answers the language model names, and the X-TTS-Model response header reports the model that spoke.
Voices
voice keeps its OpenAI name and takes one of these:
- An OpenAI voice name, in any letter case. Each one speaks with an AssemblyAI voice in its place, as the table below shows.
- The library slug of an AssemblyAI voice, such as
aai-jane, in lowercase: thevoice_idthat names the voice on every other route. - A voice’s ID, a UUID. A cloned voice can also go in as an object.
jane is not one of them. It is refused with 422 (invalid_request_error, code invalid_parameter, param voice), and the message names the slug to send, aai-jane.
An OpenAI voice name speaks with the AssemblyAI voice in this table, which speaks English. Jane has no OpenAI name; ask for her, like any AssemblyAI voice, by her slug,
aai-jane. The response header X-TTS-Voice-Id gives the voice_id the request spoke with, and when you sent an OpenAI voice name, X-TTS-Voice-Alias holds it. Other values are not supported: a misspelt name is refused with 422 like a bare name, and a slug or UUID that names no voice with 400, code invalid_request, both on voice.
Cloned voices
A cloned voice you created in the AssemblyAI dashboard goes invoice as an object holding its ID or slug, {"id": "<voice ID>"}, which the OpenAI Python SDK passes through as it is, or as a string holding its UUID. The slug of one of your voices in a plain string is refused: send it as {"id": "<slug>"}. Name the model of the voice’s own language, such as assemblyai-tts-en for an English voice, or a model’s name, such as assemblyai-tts-1; another language’s model is refused with 400 on language.
400 invalid_request_error with the code voice_not_found, voice_expired, voice_blocked, voice_not_paired, model_retired, model_not_found or not_owner, and biometric_terms_not_accepted answers 403; see Errors.
Extensions
These fields are not part of the OpenAI request; most OpenAI clients pass them through an extra-body option (extra_body in the Python SDK):
The language is the one
language sets, else the one the model name implies, else a cloned voice’s own language, else English. Clients that cannot add fields pick a language with the model name.
word_boundaries must be left out or false. Use Stream speech with word timings for them.
Speech events
With"stream_format": "sse" the response is text/event-stream in OpenAI’s form: one data: line per event and no event: lines. While nothing is sent for 15 seconds, a : keepalive comment line arrives; skip lines that start with :.
- Each
speech.audio.deltacarries base64 of the next bytes of theresponse_format: withwav, the WAV header comes first; withmp3, whole MP3 frames. speech.audio.doneends the stream. Itsusagecounts characters, not tokens: the token counts are always0,input_charactersis the length ofinput, andbilled_characterswhat the request is billed. There is no[DONE]line.- A failure after the first audio sends
{"type": "error", "error": {...}}, with the fields of the error body below, and then cuts the connection. Withhttpx, read withiter_bytes()oriter_lines()and nochunk_size, or theerrorevent can be lost with the cut; see Streamed and buffered responses.
Errors
Errors use OpenAI’s body, so OpenAI SDKs raise their usual exceptions (openai.BadRequestError, openai.RateLimitError, …). The code is the one the HTTP API uses:
400 for a bad field, model or voice, 401 for the credential, 402 for funds, 422 for a voice string that is not an OpenAI name, a slug or an ID, 429 for both the requests-per-minute limit (rate_limited) and no room just now (at_capacity), and 503 when the service is unavailable.
OpenAI SDKs retry a 429 on their own, twice by default. A request can be refused at_capacity after it was counted against your requests per minute, so each of those retries counts as a request too. To keep retries within your limit, set max_retries on the client and back off for Retry-After yourself.
Every response carries the request’s id in X-Request-Id, and a 200 also in X-Generation-Id; quote it in support requests. Send X-Client-Request-Id (1 to 512 printable ASCII characters) to have your own id echoed back.
Related
- Create speech (OpenAI compatible): the route reference, with every field, status and code.
- List models (OpenAI compatible) and List models (OpenRouter): the public models documents.
- HTTP API basics: hosts, headers, limits and billing shared with the native routes; Word boundaries for word timings over HTTP.
- LiveKit and Pipecat: the native plugins.
- Optimizing accuracy and latency: client habits that shorten the time to first audio.