Product updates

Changelog

Fixed Scheduled
APIModels

A voice policy now picks a voice curated for the language

Voice selection read one roster per model with no language on it, so a Spanish request asking for a female voice could be answered with a voice curated for English. Voices curated for a language now come with the language, and the model-wide roster is only the fallback where nothing is curated.

  • A gender or style policy resolves against the voices curated for the requested language when there are any. Where none are curated, the model-wide roster still answers, because a voice that may be wrong is better than refusing to speak.
  • A regional tag inherits its base language, so es-MX resolves against the Spanish voices.
  • Curated voices carry no style tags, so a style preference does not reorder them and the curated order stands: voices recorded in the language come first.
  • Accent tags are unchanged. Each names one voice chosen from a rater panel and is not affected by a gender policy.

Action: No migration is required. A key with a pinned voice keeps it. A key with a gender policy may now resolve to a different voice on a non-English language, which is the point: the previous voice was curated for another language.

Models
Improved Available
APIModels

Every text-to-speech model on the board now publishes a cost

Four token- or hourly-priced TTS models gain converted per-character costs, and with a published cost they become rankable under the cost objective.

  • Google gemini-3.1-flash-tts (~USD 33.3), OpenAI gpt-4o-mini-tts (~USD 20), Soniox tts-rt-v1 (~USD 13) and Qwen qwen3-tts-flash (USD 10, an exact vendor rate) publish costs per 1M characters. Converted figures use 900 characters per spoken minute and carry a tilde.
  • With a published cost these models enter cost-objective ranking and balanced cost-weighting. qwen3-tts-flash is now the cheapest text-to-speech model on the board while ranking last on naturalness - a key optimized for cost may route to it.
Added Scheduled
APIModels

Russian streaming transcription routes through Soniox

Russian streaming transcription is now served by Soniox stt-rt-v5, the only realtime provider currently verified for Russian. Buffered Russian transcription remains on OpenAI's gpt-4o transcription models.

  • Russian sessions on GET /v1/transcribe/stream fail closed to the verified soniox:stt-rt-v5 candidate. If Soniox is unavailable after key and request constraints, the router refuses the request instead of falling through to an unverified streaming provider.
  • Buffered Russian transcription is unchanged. POST /v1/audio/transcriptions continues to route only to openai:gpt-4o-transcribe and openai:gpt-4o-mini-transcribe, including when Soniox is available.
  • Russian remains unmeasured on the transcription boards, so it inherits English-board ordering before transport-specific verification narrows the candidates.
  • GET /v1/routing/preview does not model buffered versus streaming transport. A Russian preview therefore shows the buffered OpenAI chain, while the streaming socket dials Soniox.

Action: No migration is required. Existing buffered Russian transcription keeps its current OpenAI routing; streaming Russian is available when soniox:stt-rt-v5 is routable.

Improved Available
APIConsole

Usage, activity, and API key limits follow the active organization

Usage and activity are scoped to the active organization, API key limits apply per organization, and existing keys can be assigned to the current organization.

  • Usage and activity views now include events for keys in the active organization and organization-unscoped keys. Organization-less API sessions continue to see usage and activity across all of the owner's keys.
  • The 10-key limit now applies per organization, including organization-unscoped keys visible there. Organization-less API sessions keep the user-wide 10-key limit.
  • Keys can be explicitly assigned to the current organization. Keys created before organization scoping, or created by organization-less API sessions, remain visible in all of the owner's organizations until they are assigned.
Improved Available
APIConsole

API keys follow the active organization, and Russian gains LLM routing

The console's API key list and new keys are scoped to the organization you are working in, and Russian requests can now route the LLM stage.

  • Keys created from the console are stamped with the active organization when one is selected, and the key list shows that organization's keys. Keys created without an active organization stay organization-unscoped and, like keys created before this change, are visible in all of their owner's organizations. API callers authenticating with OAuth tokens see all of their keys, unchanged.
  • Russian, previously transcription-only, is accepted on the LLM stage: selection rides the English board's ranking (the models themselves are multilingual, and no language parameter is forwarded upstream). Russian text-to-speech stays refused with unsupported_language - no vetted Russian voices exist.
  • Usage and activity views remain user-scoped for now.
Fixed Available
APIConsole

Language lists are alphabetical and Norwegian is labeled plainly

Catalog language labels say Norwegian instead of Norwegian Bokmal, language lists render alphabetically after English, and Norwegian synthesis now works on ElevenLabs and Cartesia.

  • The control-plane catalog labels the nb language code as Norwegian. The code itself is unchanged; only the human-readable label moves.
  • Per-language boards on speko.ai render in alphabetical order by language name. Row order within each board is still the study's own metric ranking.
  • Norwegian (nb) text-to-speech requests to ElevenLabs and Cartesia previously returned a provider error: both vendors key Norwegian by the macrolanguage code. The router now sends no to those two vendors; the nb code callers send is unchanged, and vendors that accept nb keep receiving it.
Added Scheduled
APIModels

Hindi, Tamil and Telugu are routable

All three had transcription numbers and no synthesis study, so they were refused as unsupported. Synthesis boards for them are now published, so each has evidence on both legs and each publishes an evidence card. Routing follows the measurements.

  • Hindi, Tamil and Telugu are selectable and routable where they previously answered unsupported_language. Each is measured on both legs, transcription and synthesis, and each publishes a landing-page evidence card.
  • Thai, Vietnamese, Mandarin, Indonesian and Malay stay out. They have transcription numbers and no synthesis study, so enabling them would advertise routing a language whose naturalness was never measured.
  • The published boards, catalog and language studies were regenerated from the publisher in the same change, so the numbers behind these languages are the ones the benchmark site shows.

Action: No migration is required. Keys created before this change are unaffected. A new key can now select Hindi, Tamil or Telugu, and GET /v1/routing/preview shows what each will choose.

Added Scheduled
APIModels

Groq, Baseten, Fireworks and Amazon Polly are wired

Five upstreams the router previously had no way to call. Each was probed against its own live API before landing, not taken from vendor documentation. They become callable now and enter routing once they are measured, because the catalog is derived from benchmark evidence rather than written by hand.

  • Groq serves both language models and transcription, Baseten and Fireworks serve language models, and all four speak the OpenAI wire exactly, so they needed no adapter code at all.
  • Amazon Polly speech synthesis arrives through a native adapter that signs each request with SigV4. Polly caps PCM at 16 kHz while every other synthesis upstream is pinned to 24 kHz, so the router resamples rather than serving audio that would play half again too fast under a header declaring the wrong rate. That conversion needs the whole utterance, so Polly is buffered rather than progressive.
  • These five are callable, not yet routable. GET /v1/models lists what the routing snapshot measures, so an upstream with no benchmark evidence stays out of the catalog rather than appearing unranked. Wiring precedes measurement because the adapter is what lets a benchmark run through the gateway at all.
  • Groq transcription is batch only, and Groq accepts ISO 639-1 codes only, so it spells Norwegian differently from the tag the router routes on. Both constraints are recorded beside the upstreams they bind.
  • Speech-to-speech now proves an upstream is alive before routing to it. A successful socket handshake was being scored as candidate health, but a rejected session returns the same handshake and reports the failure as an in-band error frame, so one rotated credential could consume every session while the healthy provider behind it was never dialled and the breaker never opened.

Action: No migration is required. Existing keys are unaffected. Four credentials must be bound in every region before these upstreams report routable: GROQ_API_KEY, BASETEN_API_KEY, FIREWORKS_API_KEY and the Amazon credential. An unbound credential disables only its own upstream.

Fixed Scheduled
API

voice: auto no longer refuses to synthesize

Sending voice auto to the OpenAI-compatible speech route answered 503 no_candidate in every language, while an empty voice, an omitted voice and a preset voice all returned audio. Auto now means what it means for model: the router chooses.

  • POST /v1/audio/speech with voice auto returned 503 no_candidate for every language, including languages with more than a dozen routable synthesis models. The native synthesis route was never affected.
  • Auto was read as the name of a specific voice. That marks the request as having pinned a voice, which narrows the candidate list to vendors offering that exact voice, and no vendor offers one called auto, so the list emptied and routing reported that nothing matched.
  • Auto and speko/auto are now read the same way an absent voice is read, matching how model already treats them. A real voice still pins, so a caller who names one is still honoured.

Action: No migration is required. Callers who worked around this by sending an empty voice can keep doing so; both spellings behave identically.

Improved Available
APIModels

Per-language routing evidence for thirteen more languages

The router ranked languages it had never measured. It held per-language evidence for four transcription languages, none for the LLM stage and one for speech synthesis; every other language silently fell back to the English ranking. Per-language evidence now comes from the same benchmark dataset the public API answers from, covering fourteen transcription languages, seven for the LLM stage and six for speech synthesis.

  • Arabic, Filipino, German, Hindi, Indonesian, Malay, Mandarin, Norwegian, Spanish, Tamil, Telugu, Thai and Vietnamese now rank on their own measurements instead of English. A language still without one continues to use the English ranking and reports it as a proxy.
  • Rows carry the date they were measured, and the stage reports the range its measurements span rather than only the newest.
  • Thai transcription is measured on a batch corpus rather than the live socket. The ranking now records which path the number came from instead of presenting it as a live-path result.
  • The model roster, prices, voice rosters and English numbers are unchanged. Only per-language evidence moves, so a request that was already routing on a measured language keeps the same answer.

Action: No migration is required. A key pinned to a specific model keeps that pin. A key that routes by objective may select a different model on a language that previously had no measurement of its own, and GET /v1/routing/preview shows what it will choose.

Benchmarks
Fixed Available
APIModels

Complete /v1/models schema in the OpenAPI contract

The published Model schema now declares every field GET /v1/models actually serves, not just the OpenAI-compatible core.

  • The Model schema previously declared only id, aliases, object, provider, model, api, and routable; the endpoint has served more for some time.
  • Newly declared: created and owned_by (OpenAI-compatibility fields), quality with its stage-fixed qualityUnit (% WER for stt, score for llm, MOS for tts), latencyMs, costPerMinUsd, and the per-model measured languages array.
  • Also declared: the TTS-only voices roster ({ id, name, gender, styles }) and its voicesVerified curation date, marked as present only on api: tts entries.
  • costPerMinUsd is documented with its real stage-dependent units: USD per minute for stt, per 1M tokens for llm, per 1M characters for tts.
  • All newly declared fields are optional in the schema, so responses from routers deployed before the voice-roster release continue to validate.
  • Contract documentation only; no response bytes or routing behavior change.
Improved Available
APIModels

Arabic, German, French and Norwegian are routable, and Spanish transcription follows its own numbers

Two hardcoded lists sat in front of the measurements. The allowed-language list named English and Spanish, so four languages measured and published on BOTH speech legs answered unsupported_language. Transcription also carried a fixed Spanish provider order that outranked any score. Both are gone; routing follows the measurements.

  • Arabic, German, French and Norwegian are selectable and routable where they were previously rejected as unsupported. Each is measured on both legs -- a transcription number and a synthesis number -- and each publishes a landing-page evidence card.
  • A language measured on only one leg stays out on purpose. Thai, Hindi, Indonesian, Malay, Mandarin, Tamil, Telugu and Vietnamese have transcription numbers and no synthesis study, so enabling them would advertise routing a language whose naturalness was never measured.
  • Spanish transcription no longer carries a fixed ElevenLabs-then-Soniox order. That order existed because the global ranking was measured on English; Spanish now has its own measurements, so the ranked board is the evidence and picks the model the numbers pick.
  • Text-to-speech accents are unchanged. They name a specific voice chosen from a rater panel, because no board measures per-country Spanish, so they remain an ordered chain rather than a ranking.
  • GET /v1/models advertises exactly what the router will serve. A language in that list can no longer answer 400 on the first call.

Action: No migration is required. An existing key keeps its pins and its routing. A key created with Spanish selected before this change carries the pinned ElevenLabs-then-Soniox order it was created with; recreate it, or unpin its transcription stage, to follow the measurements.

Benchmarks
Added Available
APIModels

Deepgram Flux is routable, Hindi, Tamil and Telugu transcription are published, and vendor pricing returns to two boards

Deepgram Flux was measured on the transcription board but named no model the gateway would accept, so nothing could pin it. The pin deepgram:flux-general-en now routes it. The multilingual transcription study adds Hindi, Tamil and Telugu, and the transcription and LLM boards publish vendor pricing again now that every row on each carries a rate.

  • Deepgram Flux is published and routable: the pin deepgram:flux-general-en names the English model, which is what the board row measures, since its conditions read English read speech on FLEURS. The catalog splits Flux by language and the row names no variant, so a Flux request for another language is not covered by that measurement.
  • Hindi, Tamil and Telugu join the multilingual transcription study, taking it from five languages to eight. Routing already held measured evidence for all three, so selection on those languages does not change; what is new is the published study behind it.
  • The transcription and LLM boards show vendor pricing again. A price column is dropped unless every row on the board carries a rate, and both now do. Speech synthesis still has rows without one, so its price column stays off.
  • Five transcription candidates and one LLM candidate gain a published price as a result. Cost-ranked routing and the X-Speko-Max-Price constraint skip candidates with no published price, so those six now take part in both where they were previously passed over.
  • Fish Audio s2.1-pro and Palabra palabra-tts-v1 are published on the speech synthesis board. Both carry an automated naturalness estimate marked with a leading tilde rather than a blind-panel score, so neither is ranked among the most natural voices and neither is routable: they are published as measured evidence and are rejected if pinned. GET /v1/models reports routable false for both.
  • A fresh benchmark run moves several speech synthesis numbers. Inworld TTS-2 is measured at 116 ms rather than 120 ms and priced at USD 25 per 1M characters rather than USD 20.80, and ElevenLabs v3 is measured at 481 ms rather than 475 ms.

Action: No migration is required. Existing pins keep resolving to the same models. To use Flux, pin deepgram:flux-general-en on transcription. If you set X-Speko-Max-Price or route transcription and LLM traffic by cost, six candidates that were previously skipped for having no published price now compete, and GET /v1/routing/preview shows what will be chosen.

Benchmarks
Added Available
API

Voice constraints on router keys: gender, pinned voice, per-model overrides

Router keys can constrain TTS voices by gender, pin a voice to one model, and override the voice for individual models while preserving caller-selected voices and legacy behavior.

  • Router keys have new optional TTS policy fields: gender, pinned_voice and overrides. They will be writable through the console and control plane in an upcoming release. When one of these fields activates matching, the legacy key voice is ignored.
  • Voice resolution now runs separately for every candidate in a failover chain. A female or male constraint is a hard filter, and a candidate with no qualifying roster voice is skipped before it is dialed. Requests using the regional accent candidate chain keep their existing accent-locked voice behavior.
  • On HTTP routes, when no candidate satisfies an active voice constraint, the request returns 503 with error code no_matching_voice and names the requested gender when one was set.
  • GET /v1/synthesize/stream now honors the legacy key voice when its config frame has no voice. The WebSocket fallback behind POST /v1/synthesize/stream and POST /v1/audio/speech/stream now honors it when the request body has no voice; all three paths previously ignored it. A key voice belonging to a different vendor than the serving model now fails over instead of silently serving that vendor's default.
  • A voice sent by the caller in the request body or WebSocket config frame always wins, unchanged.
  • Keys without the new matching fields still use their legacy key voice. Streaming requests now apply it when the caller sends no voice, and normal candidate failover remains active when a vendor rejects it.
  • On GET /v1/synthesize/stream, if an active voice constraint filters out every otherwise eligible WebSocket TTS candidate, the router sends an in-band error frame with code NO_MATCHING_VOICE and closes the socket.
Added Available
APIConsole

Voice policy controls for router keys

Router key policies can now select a TTS gender, pin a model voice, and set per-model voice overrides through the API and console.

  • POST and PATCH /api/keys accept TTS policy fields gender, pinnedVoice with model and voice members, and overrides keyed by TTS catalog model id. GET /api/keys returns the same fields in camelCase.
  • A pinned voice or override is checked against a model's curated voice roster. A missing roster entry returns unknown_voice; an empty roster remains permissive until that model has been curated.
  • A female or male constraint that disagrees with a curated pinned or override voice returns voice_gender_conflict.
  • Keys with an accent-tagged language, such as es-MX, keep accent-managed voice selection and reject active voice constraints with accent_managed_voice.
  • The legacy TTS voice field cannot be combined with an active gender, pinnedVoice, or overrides setting. Such a policy returns invalid_policy because pinnedVoice supersedes voice and the caller must choose one mechanism.
Added Available
APIModels

Named voice rosters in the TTS catalog

/v1/models TTS entries now include a curated voices array and a voicesVerified date.

  • Each TTS entry in GET /v1/models now carries voices[] with { id, name, gender, styles }; roster order is meaningful, and the first entry is the provider default.
  • An empty voices array means the roster is not yet curated, not that the model has no voices.
  • TTS entries also carry voicesVerified, the curation date; it is null until a roster ships.
  • STT and LLM entries are unchanged.
  • No routing behavior changes; this is catalog metadata only.
Fixed Available
APIModels

Streaming transcription now honours the provider and language a request asks for

GET /v1/transcribe/stream ignored X-Speko-Allow, X-Speko-Deny, X-Speko-Language, the objective, the price cap and the key's own provider order. Every request was served by whichever stream-capable provider ranked first, so four of the five provider families that implement streaming transcription were unreachable.

  • The route asked for every routable transcription candidate instead of the caller's allowed set. Measured against the live API: all 13 routable transcription models resolved to the same provider regardless of which one was pinned.
  • The route now selects from the caller's allowed set, applies the deny list, and preserves the ranked order and failover across candidates.
  • A request whose constraints leave no streaming-capable provider is refused with 503 no_streaming_provider naming the language, the allow list and the deny list, instead of being served by a provider it did not ask for.
  • X-Speko-Language and the key's policy language now apply. The language field in the config frame still wins when present, but it no longer defaults to English: a frame that omits it previously transcribed as English no matter what the key or the header requested, which is the common case because both framework plugins omit the field unless the application sets one.
  • Transcription is accent-independent, so a regional tag is reduced to its base language before it reaches a provider. This is required now that the header and the key language apply: an accent-tagged key carries a tag such as es-PR, and while three providers already reduced it themselves, one forwarded it verbatim to an API that does not list it as a language code.

Action: No migration is required. A client that pinned a transcription provider and silently received a different one now either receives the pinned provider or a 503 naming the constraint.

Streaming transcription
Improved Available
APIModels

Streaming TTS reaches every provider that can stream, including the regional-accent voices

POST /v1/audio/speech/stream and POST /v1/synthesize/stream could only be served by the four providers with a WebSocket TTS surface. Seven more already answer with progressive raw PCM over HTTP, and two of them carry the validated regional Spanish voices, so an accent tag could never reach its own voice on a streaming route.

  • The streaming routes now select across every provider whose response is already progressive PCM, ranked by the board, and serve it through the same engine the buffered routes use. That engine was forwarding those providers chunk by chunk all along; only the streaming routes' candidate filter hid it.
  • A regional Spanish tag now resolves to its own voice on a streaming route. Verified locally against the live providers: es-CO resolved to ElevenLabs and es-PR, es-CU and es-MX to Cartesia, each arriving chunked with the first bytes between 283 and 1186 ms.
  • A key whose routing policy pins a TTS chain still has that chain outrank the accent order, which is unchanged and deliberate. Accent order applies when the key carries no TTS chain.
  • Providers reached only over WebSocket TTS are unaffected: a request pinned to one of them is still served by the WebSocket engine, so existing pins keep working.
  • A request that can only reach a provider which returns one finished blob is refused with 503 no_streaming_tts_provider naming the constraint, rather than served as a response whose body arrives all at once. response_format other than pcm is still refused with 400.
  • The WebSocket ready frame now carries routeReason, the selection explanation the upgrade could not send as a header.

Action: No migration is required. A caller that pinned a WebSocket provider to obtain streaming can drop the pin and let the board choose.

Streaming TTSLanguages and accents
Added Available
APIModels

Streaming transcription adds ElevenLabs and Soniox, and prefers them for Spanish

ElevenLabs Scribe realtime and Soniox are now served on the streaming transcription socket, and a Spanish request prefers ElevenLabs first and Soniox second ahead of the board's global winner. A Deepgram streaming defect that returned empty transcripts is fixed in the same change.

  • Providers whose whole session configuration arrives in a first message can now be served: the streaming transcription path gained a handshake step, which text-to-speech already had. Soniox sends its API key in that frame and had been unavailable for that reason alone.
  • ElevenLabs streams its realtime model. The catalog row is the batch model, and the socket accepts only the realtime identifier, so the router translates between them rather than publishing a second row for one wire value.
  • A Spanish streaming request now prefers ElevenLabs, then Soniox. This is a preference and not a restriction: a provider that cannot serve the request is skipped, the rest of the ranking keeps its order behind it, and a language with no preference is unaffected.
  • Deepgram streaming sent no encoding or sample rate, so raw audio arrived undecodable: the socket connected, the audio was accepted, and every transcript came back empty with no error reported. It now declares the 16 kHz mono format the router guarantees.
  • Verified against the live providers: a Spanish request resolved to ElevenLabs and returned a transcript, the same request pinned to Soniox returned a transcript, and an English request resolved to Deepgram and returned a transcript.

Action: No migration is required. A Spanish streaming transcription request that previously resolved to whichever provider ranked first globally now resolves to ElevenLabs, or to Soniox when ElevenLabs cannot serve it.

Streaming transcription
Fixed Available
APIModels

Routing preview reflects the per-language transcription preference

The Spanish transcription preference applied to requests but not to GET /v1/routing/preview, so the console key wizard listed and pinned the global ranking instead. A key created that way was pinned to an order that then outranked the preference on every request it made.

  • The preference is now applied wherever a candidate order is produced: buffered transcription, streaming transcription, and the routing preview. Previously it applied only to the streaming socket.
  • For Spanish, the preview now returns ElevenLabs first and Soniox second. The console builds its override list from this response, so a key created with Spanish selected pins that order rather than the global one.
  • It remains a preference and not a restriction: a provider that cannot serve the request is skipped, the rest of the ranking keeps its order behind it, and a language with no entry is unaffected.
  • The preference is keyed on the base language, because transcription is accent-independent: es, es-CO and es-419 all resolve to the same order.

Action: No migration is required. Keys created before this change carry the pinned order they were created with; recreate a key, or unpin its transcription stage, to follow the preference.

Streaming transcription
Fixed Available
APIModels

A regional language tag no longer silences WebSocket TTS providers

Requesting a regional tag such as es-CO or es-MX forwarded the full tag to providers that accept only a base language. They rejected the request as invalid input, and because the streaming transports commit a 200 before the provider answers, the caller received a successful response carrying no audio.

  • Measured against the live API with identical text pinned to one provider: en and es each returned real audio, while es-CO and es-MX returned an empty 200 and the provider reported invalid input data. The tag now reaches these providers as its base subtag.
  • The region is not lost. Regional delivery is handled by the accent routing table, which pins a validated voice or supplies a steering instruction, and no WebSocket TTS provider is an accent primary.
  • This was reachable from the default route: a key whose policy language is a regional Spanish tag sent no language header and still resolved to an affected provider, so the documented quickstart body returned a successful response with no audio.
  • Both the buffered and streaming paths carried their own copy of these handshakes and both are fixed.

Action: No migration is required.

Streaming TTSOpenAPI
Fixed Available
API

A synthesis request with no text returns 400 instead of a provider error

A TTS request whose text field was absent or empty was sent to a provider, rejected, and retried across every candidate before returning 502 all_upstreams_failed. A malformed request now returns 400 and names the field it is missing.

  • The check runs before any provider is selected, so an incomplete request costs no synthesis and no failover attempts.
  • The named field matches the route: input on POST /v1/audio/speech and its streaming sibling, text on POST /v1/synthesize and its streaming sibling.
  • This was reachable by sending the native text field to the OpenAI-shaped route, whose provider body is built from input. The request looked valid, reached a provider with nothing to speak, and the resulting 502 named the provider rather than the request.

Action: No migration is required. A client that was receiving 502 for an incomplete body now receives 400 with the field name.

OpenAPI
Improved Available
APIModels

Gemini TTS audio now leaves the router as it arrives

Gemini answers in server-sent events with base64 audio per frame, and the router was decoding the whole body before sending any of it. On a streaming route each frame is now decoded and forwarded as it arrives, which moved first-byte latency from 3695 ms to 786 ms on the same request, and gives the Dominican Spanish accent a streaming voice for the first time.

  • The router already asked Gemini for its streaming endpoint with server-sent events. The response was then collected in full so the base64 audio inside each frame could be decoded, which meant the caller waited for the entire synthesis. Frames are now decoded one at a time.
  • Measured on the same request with the provider pinned: the buffered route returned one chunk after 3695 ms, and the streaming route returned 159 chunks with the first at 786 ms.
  • es-DO is served only by Gemini, so it previously had no voice at all on a streaming route. Measured unpinned on es-DO: 153 chunks, first at 1321 ms.
  • Above 700 characters Gemini is asked for a single response rather than a stream, because its streaming response truncates on long audio. A streaming request that can only reach Gemini with more text than that is now refused with 503 no_streaming_tts_provider instead of being answered with a body that arrives all at once. The buffered route serves it.
  • A frame split across two network reads is held until it is complete, so audio is never truncated by a partial base64 value. The whole-body decoder is now the incremental decoder driven to completion, so the two cannot disagree about what a response means.

Action: No migration is required.

Streaming TTSLanguages and accents
Fixed Available
APIModels

An omitted speed no longer breaks synthesis on Hume and Smallest AI

A synthesis request that did not set speed serialized it as JSON null, which Hume and Smallest AI reject. On the streaming transports the 200 status line is sent before the provider replies, so the caller received a successful response carrying zero bytes.

  • An absent optional field was serialized as JSON null rather than omitted. Hume rejected the whole payload with a float_type parse error on speed, and Smallest AI rejected it as invalid input data. Absent optional fields are now left out, which lets the provider apply its own default. A request that sets speed explicitly is unchanged.
  • On POST /v1/audio/speech/stream and POST /v1/synthesize/stream the 200 status line and routing headers are committed before the provider answers, so this could not be reported as an error status: the response was a successful one carrying no audio.
  • The buffered and streaming WebSocket TTS paths each carried their own copy of these provider handshakes. Both now share one helper, so the two cannot drift apart.

Action: No migration is required.

Batch TTSOpenAPI
Added Available
APIModelsSDKDocs

Two new OpenAI STT models, Spanish routing moves to AssemblyAI, and the API contract is corrected to match the router

The catalog publishes openai:gpt-transcribe and openai:gpt-live-transcribe as separate candidates, Spanish speech-to-text now routes to AssemblyAI Universal-3.5 Pro on its own measured numbers, and several OpenAPI descriptions are corrected where they described behavior the router never had.

  • New published router ids: openai:gpt-transcribe (buffered) and openai:gpt-live-transcribe (streaming) are now separate speech-to-text candidates rather than one combined board row, and xai:grok-voice-think-fast-2.0 is published. The catalog moves from 43 to 45 candidates, speech-to-text from 12 to 14.
  • openai:gpt-live-transcribe runs only on OpenAI's realtime transcription endpoint. Pinning it on POST /v1/transcribe or POST /v1/audio/transcriptions returns HTTP 400 naming the buffered models that are available, and automatic selection on those paths never picks it.
  • Spanish speech-to-text selection changes: quality routing that chose elevenlabs:scribe_v2 and measured-language latency routing that chose soniox:stt-rt-v5 both now choose assemblyai:universal-3-5-pro, which posts 3.5 percent streaming WER against ElevenLabs at 3.8 and the fastest endpoint on the board at 66 ms. Its Spanish figures were measured on universal-3-5-pro directly.
  • The published English WER and endpoint for AssemblyAI Universal-3.5 Pro were measured against the u3-rt-pro id, which AssemblyAI auto-routes to this model from 2026-08-07 and retires around 2026-09-25. That disclosure now travels with the board row, so it survives every regeneration of the catalog.
  • Claude Haiku 4.5 is sent upstream as the dated provider id claude-haiku-4-5-20251001. The public Speko model id anthropic:claude-haiku-4-5 is unchanged.
  • CORRECTION, not a change in behavior: the OpenAPI document previously listed mp3, opus, aac and flac as accepted values of response_format on POST /v1/audio/speech. The router has never served those and rejects them with HTTP 400. The document now lists what is actually accepted, pcm, pcm16 and wav, with pcm as the default. No request that previously succeeded stops succeeding.
  • POST /v1/audio/speech requires only input. model and voice are optional and model defaults to auto, so a language-tagged accent route can supply both.
  • Documented, previously undocumented, response behavior: a 502 when every upstream fails, carrying x-route, x-route-reason and x-speko-failover-count but not x-speko-first-byte-ms; and the split between router-generated JSON errors and a non-retryable upstream 4xx passed through with the upstream status, media type and body, which can be non-JSON.
  • Header semantics are now stated precisely. x-route is the display-cased Provider/model form rather than the provider:model form used for constraints, is the last attempted route on terminal failure, and reads none/none when nothing was attempted. x-speko-failover-count also counts candidates tried before a terminal failure. x-speko-first-byte-ms can be absent, and absence does not mean zero.
  • X-Speko-Max-Price and the routing-preview max_price parameter are stage-specific: USD per minute for speech-to-text, per 1M tokens for LLM, and per 1M characters for text-to-speech. Candidates with no published price are excluded when the constraint is set.
  • /healthz is documented as unauthenticated, returning 200 only when the loaded snapshot has at least one routable provider in each of speech-to-text, LLM and text-to-speech, and 503 with the per-stage readiness map otherwise.
  • /v1/models documents routable explicitly: only rows with routable true can be selected or pinned. Rows with routable false remain published as measured evidence and are rejected if pinned.
  • The OpenAPI response carries x-speko-cloud-run-revision and x-speko-release-sha when the runtime supplies them, and omits them when it does not.
  • LiveKit plugin: route headers are read before an error is raised, and a terminal response with no route headers now clears attribution instead of retaining the previous successful route. last_selection becomes None and provider/model fall back to speko / speko-router.
  • Pipecat plugin: a non-200 chat-completion or synthesize response now sets service.route to unknown/unknown rather than leaving the last successful route attached to a failed turn.
  • pipecat-speko requires Python 3.11 or newer; 3.10 support is removed. Dependencies tighten to aiohttp>=3.11.12,<4, loguru>=0.7.3,<0.8 and websockets>=13.1,<17, and a new examples extra installs the Daily/WebRTC runner dependencies.
  • livekit-plugins-speko declares requires-python >=3.10,<3.15, adds aiohttp>=3.10,<4, caps livekit-agents below 2, and adds an examples extra for the Silero VAD plugin.
  • LiveKit text-to-speech model pinning is documented: auto or unset leaves ranking unrestricted, while a catalog candidate id, upstream model name, or candidate-id suffix restricts routing to every matching candidate. Use the full provider:model id for an unambiguous pin.

Action: No migration is required. To use OpenAI streaming transcription, pin openai:gpt-live-transcribe on the transcription socket and send at least 24 kHz audio or let the router upsample.

QuickstartOpenAPI
Fixed Available
APIModels

Streaming TTS works without a provider pin, and the WebSocket text lifecycle has one owner

Unpinned streaming TTS requests no longer return 503, the documented WebSocket frame sequence now produces audio on every streaming provider, bare provider names work in X-Speko-Allow, and interimResults finally controls whether partial transcripts are sent.

  • POST /v1/audio/speech/stream and POST /v1/synthesize/stream previously returned 503 no_streaming_tts_provider for any request that set no X-Speko-Allow header, whenever the API key's routing chain pinned a provider that cannot stream. The chain now orders selection only through candidates that can stream, and falls back to the ranked board when none of them can.
  • On the /v1/synthesize/stream WebSocket, the documented sequence of a config frame carrying text followed by {"type":"end"} produced no audio. The vendor handshake and the caller's end frame each closed the text stream, so the second close was rejected. A single owner now tracks the text lifecycle, {"type":"end"} is idempotent, and text pushed before a provider reports readiness is buffered instead of dropped or repeated.
  • Incremental text on a provider that defers until readiness previously spoke the utterance twice. It is now sent exactly once.
  • {"type":"end"} is required to finish an utterance on the WebSocket transport. A caller that opened with all of its text in the config frame and never sent an end frame was previously completed by the handshake on one provider; that accident is gone, and sending the documented end frame is safe to do unconditionally.
  • X-Speko-Allow accepts a bare provider name as documented. Previously only the display name matched, so a value such as smallest returned 503 no_candidate while smallest:lightning_v3.1 routed.
  • The streaming transcription config frame accepts interim_results as an alias for interimResults, and the flag is now enforced by the router: interimResults false returns final transcripts only, regardless of what the upstream provider chooses to send. Ready and error frames are never withheld.
  • GET /v1/routing/preview returns the standard JSON error envelope with code invalid_query when the query string is missing stage or carries an invalid value, instead of a text/plain body.
  • The OpenAPI description for GET /v1/transcribe/stream documented a first frame the handler rejects. It now records the required type field, the 16 kHz mono 16-bit signed little-endian PCM input format, the end frame, and the transcript frame shape.

Action: No migration is required for the chunked-HTTP transports. Callers of the /v1/synthesize/stream WebSocket must send {"type":"end"} to finish an utterance; it is idempotent, so sending it unconditionally is safe. Callers that filtered partial transcripts client-side can now set interimResults false instead.

Streaming TTSStreaming transcriptionOpenAPI
Added Available
APIModels

OpenAI realtime speech-to-text

Streaming transcription can now route to OpenAI, and realtime-only transcription models are refused on the buffered path instead of failing at the vendor.

  • The streaming transcription socket can select OpenAI. Previously the router had no OpenAI streaming transport, so openai:gpt-live-transcribe could not be reached on any path.
  • openai:gpt-live-transcribe runs only on OpenAI's realtime transcription endpoint. Pinning it on POST /v1/transcribe or POST /v1/audio/transcriptions now returns HTTP 400 naming the buffered models that are available, rather than forwarding a request the vendor rejects.
  • Automatic selection on the buffered path never picks a realtime-only model, and the refusal does not advertise one.
  • The streaming config frame accepts an optional sampleRate between 8000 and 48000, defaulting to 16000. OpenAI requires at least 24 kHz, so lower-rate audio is upsampled and the rate actually sent is declared to the vendor.
  • Within one streaming session, gpt-live-transcribe emits cumulative partial transcripts and a single final transcript at end of stream. It carries no vendor turn detection, so there are no mid-call finals. The gpt-4o transcription models are unchanged and continue to finalize on vendor voice activity detection.

Action: No migration is required. Existing streaming and buffered integrations are unchanged. To use OpenAI streaming transcription, pin openai:gpt-live-transcribe on the transcription socket and send at least 24 kHz audio or let the router upsample.

QuickstartOpenAPI
Fixed Available
APIModels

AssemblyAI streaming transcription names the model with the parameter AssemblyAI still accepts

Streaming transcription opened its socket with the retired singular speech_model parameter, which AssemblyAI refuses. It served no streaming transcription at all in the week to 2026-08-02: 37 attempts, 37 failures, 35 of them that one rejection. The parameter is now the plural speech_models, matching the batch path, which already sent the plural form and was unaffected.

  • The streaming socket query named the model with speech_model. AssemblyAI retired that spelling and answers that it is deprecated in favour of speech_models, refusing the connection before any audio is sent. Measured over the week to 2026-08-02, AssemblyAI streaming recorded 37 attempts and zero successful selections, and 35 of the 37 failures were that single response.
  • AssemblyAI was not unavailable during this period. The rejection was produced by the shape of the request the router sent, so it was invisible as provider health and presented instead as a quietly shorter failover chain: a caller whose key ranked AssemblyAI first was served by whichever candidate ranked behind it, with no error.
  • Batch transcription was never affected. It already sent the plural speech_models as a single-element array, so the streaming and batch paths now spell the same request the same way.

Action: No migration is required. Keys that rank AssemblyAI first for streaming transcription will begin reaching it instead of falling through to the next candidate, so the provider serving a given stream may change. Pin explicitly with X-Speko-Allow to keep a request on one vendor.

Speech to text
Fixed Available
APIModels

An accent tag no longer reduces the provider board to two candidates

Requesting a regional accent such as es-CO or es-MX left only the two providers named in that accent's table, so an explicit X-Speko-Allow for any other healthy provider returned 503 no_candidate and an accent request had almost no failover depth. An accent now orders the board instead of replacing it.

  • A TTS request carrying an accent tag removed every candidate whose wire protocol the accent table did not list. Measured against the live API, an accent tag cut the reachable TTS providers from 13 to 2, while the same key on es, es-419 or en reached all 13. Providers filtered out this way were healthy; the request never reached them.
  • The 503 reported only the caller's allow-list, deny-list, price cap and stage, so a rejection made by the accent read as a bad pin. The error now also reports the language actually routed on and, when the tag is an accent, the candidate chain that accent pins.
  • An accent's own candidate order reached selection as an exclusive filter rather than a preference. A chain therefore capped failover at its own length and, if every chain entry was unavailable, returned 503 instead of falling back to the ranked board. Chains from a key's routing policy had the same ceiling.
  • Ordered candidates are now a preference prefix: the accent's validated voice, or the key's policy chain, is still tried first, and selection continues into the ranked board behind it. An unpinned accent request resolves to the same provider it did before, now with the rest of the board behind it as failover.
  • GET /v1/routing/preview applied the same narrowing and continues to agree with the proxy, because both rank through the accent chain rather than filtering on it.

Action: No migration is required. Keys pinned to an accent gain failover depth they did not previously have, so a request that used to fail with 503 no_candidate may now be served by a provider further down the board. Pin explicitly with X-Speko-Allow to keep a request on one vendor.

Routing and languagesRouting keys
Fixed Available
APIModels

API compatibility restoration for Gemini, AssemblyAI, and Russian STT

This release restores legacy Gemini TTS and AssemblyAI STT model IDs and the Russian language hint on OpenAI transcription models.

  • Compatibility support maps gemini:gemini-3.1-flash-tts-preview to google-tts:gemini-3.1-flash-tts-preview across model bodies, allow and deny headers, and API-key routing chains.
  • The catalog publishes assemblyai:universal-3-5-pro while the legacy assemblyai:u3-rt-pro and assemblyai:universal-3.5-pro pins remain accepted.
  • The restored multipart path forwards language=ru to OpenAI gpt-4o-transcribe and gpt-4o-mini-transcribe.
  • For buffered STT requests, a multipart body language field now overrides X-Speko-Language for every supported language. An empty or otherwise invalid body language value returns HTTP 400 even when the header contains a valid value.
  • The Russian STT constraint stays inside compatible OpenAI transcription models. Unsupported TTS, LLM, and realtime STT routes do not fall back to English.

Action: No migration is required. New integrations should use google-tts:gemini-3.1-flash-tts-preview and assemblyai:universal-3-5-pro; existing integrations can keep the legacy IDs.

QuickstartOpenAPI
One stable API for the voice stack.

Speko