A voice agent captures a turn the moment endpointing fires, and nothing downstream can start until the words come back. A dictation feature has to put text on screen while the user is still holding the key down. A phone tree captures one utterance and branches the call on what it heard. A push-to-talk message leaves the moment the user lifts their thumb. In all of them, transcription runs first, and every model after it reasons about the words transcription handed over. A wrong word there is a wrong answer three calls later, and the LLM has no way to know it.
The model layer behind that first call has consolidated everywhere except the first call itself. If you build on OpenRouter, you already reach dozens of models through one key, one billing relationship, and one request shape. Speech-to-text sat outside it — a separate vendor, a separate contract, a separate SDK — wrapped around the one component that determines what the rest of your stack sees.
Today, Universal-3.5 Pro is available on OpenRouter, served through our Sync Speech-to-Text API. It is listed on OpenRouter’s transcription endpoint as assemblyai/universal-3-5-pro. Pricing matches our published rates, and OpenRouter passes it through without a markup.
One POST to the endpoint you already call
One request, one transcript, no job to poll and no WebSocket to hold open:
That is the whole integration. If you are already calling OpenRouter for chat completions, the path and the model string are the only things that change. The endpoint also accepts OpenAI-style multipart requests, so an existing OpenAI SDK client pointed at OpenRouter’s base URL works against Universal-3.5 Pro without a code change. Word timestamps come back through OpenRouter’s own response_format and timestamp_granularities fields rather than ours, which keeps the response shape identical to every other transcription model on the endpoint.
Three ways to tell the model what it is about to hear
Three AssemblyAI options travel in provider.options.assemblyai: prompt, keyterms_prompt, and conversation_context. They are what change what you get back on hard audio, and they are the difference between a transcript that is broadly right and one that gets your customer’s vocabulary right.
prompt describes the situation before the first word arrives. Up to 6,000 characters telling the model what kind of speech is coming: your domain, your product, the shape of the conversation. “Transcribe this medical intake conversation” primes the decoder for clinical vocabulary, which is exactly the ground where a general-purpose model guesses and gets it plausibly wrong. Plausibly wrong is the expensive failure, because it survives every check downstream of it.
conversation_context passes the preceding turns with each request, up to 500 turns or 16,000 characters, and the model transcribes the current clip against that history. When a caller answers “yeah, the second one” or reads back a number the agent just asked for, the model knows what question it is answering. You are only billed for the current turn’s audio, so carrying the full dialogue costs nothing but request size.
keyterms_prompt takes up to 100 terms per request and biases the model toward strings you already know: product names, drug names, the SKUs that belong to this one account. Because they are sent per request, they can be scoped to the individual conversation instead of baked into a global config, which matters when your vocabulary differs per customer.
Built for one turn of speech and an answer right now
The Sync API is shaped like the request a voice product actually makes: one turn in, one transcript back, in the same response. Called directly, it returns a finished transcript in ~134 ms at p50, against the 5 to 6 seconds of job overhead a submit-and-poll flow adds to audio that takes two seconds to play. A request routed through OpenRouter carries one additional network hop on top of that, so budget accordingly and measure your own p50 from your own region before you wire it into a turn loop.
Clips run from 80 ms to 2 minutes, up to 40 MB, only as WAV. That bound is the design rather than a limit we are working around: audio longer than a turn belongs on the Async API, and a live session that stays open belongs on Realtime. Sync covers the case where the audio is already short and the answer is needed before the user notices waiting.
The accuracy every model after it inherits
Marketplaces make models look interchangeable, and transcription is the one call in the stack where they are not. Universal-3.5 Pro ranks first for accuracy across independent speech-to-text benchmarks, and it posts a 1.59% normalized word error rate on short-form audio, which is the exact audio profile a turn-shaped request produces.
Proper nouns, alphanumerics, email addresses, and postal addresses are the strings a caller spells out and an agent has to act on, and they are where the gap between transcription models shows up as a failed task rather than a lower score. The model is the same one running behind our Async, Realtime, and Sync surfaces, at the same checkpoint, in all 19 native languages. Routing it through OpenRouter changes the billing relationship and the request shape. It does not change the model.
Key details
Pricing
$0.45 per hour of audio, billed by duration, matching the Sync API rate on our pricing page. For the first week, pricing will be 50% off of list via OpenRouter.
Get started
If you have an OpenRouter key, the Python snippet above runs as written. If you want the surfaces OpenRouter doesn’t front, start with a free AssemblyAI account and read the Sync API guide. Both reach the same model.
FAQ
Is this the same model as AssemblyAI’s own API?
Yes. Same model, same checkpoint, same 19 languages. OpenRouter fronts the Sync API; it does not host a separate copy.
Do I need an AssemblyAI account?
No. Your OpenRouter key is the only credential involved, and billing runs through OpenRouter. You need an AssemblyAI account only for the surfaces OpenRouter doesn’t front.
Does OpenRouter mark up the price?
No. $0.45/hr of audio is our published Sync rate, passed through.
Can I use my existing OpenAI SDK client?
Yes. Point it at OpenRouter’s base URL and set the model string. The endpoint accepts OpenAI-style multipart requests.
How do I get word timestamps?
Set response_format: "verbose_json" and timestamp_granularities: ["word"]. Those are OpenRouter’s fields, not ours.
