Skip to main content

Overview

OpenRouter is a model gateway: one API key and one bill across many providers, behind an OpenAI-compatible interface. Selecting the model assemblyai/assemblyai-tts-1 on its speech endpoint routes the request to AssemblyAI’s OpenAI-compatible speech route, and the audio streams back as it is synthesized. You send OpenAI-shaped fields. The one AssemblyAI option OpenRouter forwards, language, travels in a provider.options.assemblyai block.

Routing through OpenRouter, or calling us directly

Route through OpenRouter when you already buy inference through it and want speech on the same key, bill, and client, or when you’re comparing speech models and want to swap providers by changing one string. Call Streaming TTS directly when you need what the gateway doesn’t pass through:
  • The WebSocket API, with one session per call and barge-in without reconnecting, and the native LiveKit and Pipecat plugins built on it.
  • Word timings, for captions and for knowing how much of a reply the user heard.
  • Audio other than 24 kHz PCM and MP3: WAV, G.711 mu-law and A-law, and sample rates from 8 kHz to 48 kHz.
  • Cloned voices named by slug or as {"id": ...}, and consent renewal.
  • Temporary tokens for browsers, and the request id and X-TTS-* headers on every response.

OpenRouter TTS docs

OpenRouter’s reference for the speech endpoint.

Streaming TTS quickstart

Use Streaming TTS directly, without the gateway.

Quickstart

Set your OpenRouter key:
Then POST the text. The response body is the audio itself, not JSON:
hello.pcm holds raw 16-bit little-endian mono PCM at 24 kHz. To play it, run ffplay -f s16le -ar 24000 -ch_layout mono hello.pcm, or ask for "response_format": "mp3".

Request fields

The default response_format on OpenRouter is pcm, where the direct route’s is mp3. Code that moves between them and leaves the format out gets a different format back. Set response_format explicitly.
OpenRouter accepts other fields of the OpenAI speech request, such as instructions, and AssemblyAI ignores them. A successful request is not evidence that a field took effect.

Voices

voice takes the same strings as on the direct route:
  • The library slug of a voice in the catalog, such as aai-kyla, or aai-constanza for Mexican Spanish.
  • A voice’s ID, a UUID, as the voice catalog copies it.
  • An OpenAI voice name such as alloy, mapped to an AssemblyAI voice as the direct route’s table shows.
A bare name without the aai- prefix, such as kyla, is refused. OpenRouter accepts voice only as a string, so the {"id": ...} form for cloned voices is refused before it reaches AssemblyAI. When voice is left out, the request still succeeds, with a default voice. Name the voice to get the same one every time.

AssemblyAI options

Options specific to AssemblyAI go under provider.options.assemblyai, keyed by our provider slug.
string
The language to speak: a language name such as spanish or an ISO 639-1 code such as es, in any letter case. One of English, Spanish, German, Italian, Portuguese or French. When you leave it out, the voice speaks the language it was recorded in. Set it to the language of your text; see Voice and language.
Put language under provider.options.assemblyai. A top-level language field is accepted with 200 and does nothing, whatever its value.
language is the only option OpenRouter forwards. The direct route’s other extensions, sample_rate, voice_id and extend_biometric_consent, and any other key, are dropped without an error.

Response

A 200 carries the raw audio as its body, streamed as it is synthesized: the first bytes arrive long before the last, so a player can start on them. Write the body to a file or a player, and don’t parse it as JSON. The X-TTS-* and Server-Timing headers of the direct route don’t come through. These requests bill through your OpenRouter account, per character of input; the model page has the price, and OpenRouter’s generation API reports each request’s cost by its X-Generation-Id.

Errors

Errors use OpenRouter’s body, with the status as code:
When AssemblyAI refuses a request, OpenRouter prefixes our message with Provider rejected the request:. Some of our refusals arrive without their message, as Provider returned 400. For the cases below, the message you get depends on who refused the request: OpenRouter doesn’t bill failed generations, and documents its other statuses, such as 402 for insufficient credits and 429 for rate limits, in its API reference.

Limits

  • Text length. 5,000 characters per request, the same as the direct route. OpenRouter enforces it before the request reaches us.
  • Data zone. OpenRouter routes requests to https://openrouter.ai/api/v1 to either of our data zones, US or EU. To keep a request in Europe, send it to OpenRouter’s EU in-region endpoint, https://eu.openrouter.ai/api/v1, which needs an OpenRouter Business or Enterprise plan; without one, it answers 403.