Product updates

Changelog

Dated API, model, SDK, console and documentation changes, with the migration action each one needs.

45 entries
AddedScheduled
APIModels

Modulate speech-to-text is measured but not routable here

Modulate Velma 2 now appears on the published speech-to-text board. The router does not serve it: a Modulate pin fails closed rather than dialing a wire that does not exist.

  • Modulate Velma 2 is measured on the speech-to-text board and routes through the Speko platform gateway, but this router has no Modulate upstream. A request pinned to modulate fails closed with a no-provider error instead of being silently served by a different vendor.
  • The capability table now carries an explicit unwired row for Modulate, the same treatment Google Chirp already has: on the board, no adapter behind it. Without the row, a board roll-forward could not tell 'provider deliberately unwired' from 'board id nobody taught the table about'.
  • No other provider, transport or language changes. Routing, ranking and every existing pin behave exactly as before.

Action: No migration is required. To transcribe with Modulate today, use the Speko platform gateway rather than a router pin.

AddedScheduled
APIModels

Russian text-to-speech is now routable

Russian (ru) synthesis now routes on models that carry a curated Russian voice roster, and GET /v1/models advertises each TTS model's declared languages.

  • POST /v1/audio/speech and the streaming synthesis socket now accept language ru. Previously a Russian synthesis request was refused with an unsupported-language error.
  • Russian synthesis routes only on models with a curated Russian voice roster (ElevenLabs eleven_v3 and Cartesia sonic-3.5 today). A Russian request pinned to a model without a Russian roster fails closed with a no-provider error instead of dialing an unvetted voice; a model gains Russian automatically once its curated roster is added.
  • Russian remains an endpoint capability and is deliberately not added to the benchmarked language list: no blind-panel naturalness study backs Russian synthesis, so it is not ranked among the most natural voices.
  • Each TTS entry in GET /v1/models now includes an additive languagesDeclared array (languages the model measures, unioned with languages it curates voices for), so a consumer can tell which model and language synthesis pairs actually route.
  • Russian transcription and LLM handling, and every other language, are unchanged.

Action: No migration is required. To synthesize Russian, pin elevenlabs:eleven_v3 or cartesia:sonic-3.5 on /v1/audio/speech, or leave voice auto and route by a Russian-language request.

AddedScheduled
APIModels

Two new model ids published in the router catalog

The generated router id list now publishes livekit:google/gemma-4-31b-it as routable and modulate:velma-2-stt-streaming-english-v2 as published but not yet live-routable. No existing ids were renamed or removed.

  • Added the routable model id livekit:google/gemma-4-31b-it to the published list.
  • Added the model id modulate:velma-2-stt-streaming-english-v2 to the published list with its board measurements; the gateway carries the vendor deliberately unwired and keeps it off live routing until a Modulate upstream ships, so requests naming it are refused rather than dispatched.
  • The gateway scoring snapshot now carries today's board capture.
  • No existing model id was renamed, aliased, or removed in this change, and no defaults or input precedence changed.

Action: No migration required. Existing requests keep working unchanged. To use the new LiveKit model, send its id as the model value.

AddedAvailable
APIConsole

public voice search API and the speko.ai/voices finder

A new unauthenticated endpoint, GET/POST https://api.speko.dev/v1/public/voices, searches Speko's cross-provider voice catalog by plain-language description with narration and phone-agent lanes, gender, accent, and provider filters. It powers speko.ai/voices, a public tool where anyone can describe a voice, hear previews, and take the voice into a Speko key. The authenticated search API at api.speko.dev/v1/voices/search is unchanged.

  • GET/POST api.speko.dev/v1/public/voices accepts q (description, up to 200 chars), lane (narration | phone), gender, accent, vendor, and topK (up to 24); responses carry only public voice metadata (vendor, voiceId, name, gender, accent, languages, styles, previewUrl) plus provider and accent facet counts.
  • The endpoint is rate limited to 60 requests per minute per client and answers 429 with code RATE_LIMITED beyond that.
  • English-only in this release: rows without language tags are treated as English-eligible on this public surface; the authenticated search keeps its stricter language matching.
  • speko.ai/voices ships as a server-rendered page: search, lane switching, sample queries, stored-clip previews, and per-voice links into the Speko console.
  • No authenticated surface changed: api.speko.dev/v1/voices/search requests and responses are byte-compatible with the previous release.
AddedScheduled
APIDocs

transcription: guide OpenAI's transcribe models with a prompt

The provider-options document now forwards openai.prompt, so a caller can seed OpenAI's prompt-guided transcription models with vocabulary and style -- product names, speaker names, spelling conventions -- on every transcription route. The prompt travels in OpenAI's own spelling on each transport and reaches OpenAI only; providers that never read it never receive it.

  • The forwardable settings allow-list gains its fourth provider: openai accepts prompt, alongside the existing deepgram, soniox and smallest entries. Send it where provider options already travel -- the X-Speko-Provider-Options header on the buffered routes, the provider_options form field on POST /v1/audio/transcriptions, the providerOptions config-frame field on GET /v1/transcribe/stream, or an API key's saved policy -- as {"openai":{"prompt":"Speko, Clio, Casey"}}.
  • The prompt is spelled the way OpenAI reads it on each transport: a multipart prompt field on a buffered request, and audio.input.transcription.prompt in the realtime session frame, the same fields the Speko platform adapter sends. On POST /v1/audio/transcriptions, a prompt field the caller wrote in their own multipart wins over the options document, for the same reason the form field already beats the header: the value written next to the audio is describing that audio.
  • Provider-options semantics are unchanged and are the point: the prompt is not a routing requirement, so a request that offers one is still served by whichever provider failover selects, and only OpenAI reads it. The OpenAI-compatible vendors that share the wire adapter never receive another provider's document. whisper-1 reads at most 224 prompt tokens, which the existing 256-character setting cap keeps under. temperature remains unforwardable: nothing evidences what the transcribe models do with it.
  • One validation rule tightened alongside: a provider-option string value may no longer carry control characters. The prompt is written into a multipart body verbatim, where a value carrying a CR/LF pair and a boundary could mint fields the validation never saw; no evidenced setting has a use for a newline. Such a document now answers 400 invalid_parameter naming the setting.

Action: Nothing requires migration. The option is opt-in and requests that do not send it are byte-identical to what the router always produced, on every transport. The one observable tightening: a provider-option string value containing a control character (a newline, a tab) is now refused with 400 invalid_parameter where it was previously accepted; no evidenced setting had a legitimate use for one.

ImprovedScheduled
Models

English text-to-speech naturalness is now an arena Elo, not a 1-5 MOS

The blind human A/B panel behind the naturalness column was refit onto an arena rating, so every published number on that column changes scale: ElevenLabs eleven_v3 reads 1590 where it read 3.51. The votes are the same votes; only the fit changed.

  • Naturalness on the English board is an arena Elo with a field mean of 1500, fitted from the same blind A/B votes on phone-agent lines that produced the previous internal MOS. Higher is still better and the win % column beside it is unchanged, so the ordering question the column answers is the same one.
  • Read the interval, not the point. Overlapping 95% confidence intervals still mean a statistical tie, and the top of the board remains one - the gap between the leading systems is smaller than their intervals.
  • The per-language text-to-speech boards (Spanish, Tamil, Telugu) are unaffected and still publish an internal MOS. The two units are never compared with each other: quality is only ever ranked within one language.
  • Routing changes at the top of the English board. Ranking normalizes quality against the range of its own board, so the rescale by itself reorders nothing - but the refit does not merely rescale, it separates two rows that used to be equal. Gemini 3.1 Flash TTS and ElevenLabs eleven_v3 both read exactly 3.51 under the old MOS, and a tie fell to the earlier candidate; they now read 1591 and 1590. A quality-objective text-to-speech request for English selects Gemini where it selected ElevenLabs, on a one-point difference whose confidence intervals overlap almost entirely. Pin a model, or send X-Speko-Allow, if you need the previous choice.
  • GET /v1/models reports qualityUnit "Elo" for tts, where it reported "MOS". The field moves in the same release as the numbers it labels.
FixedScheduled
API

Narration segments are sized for the smallest vendor cap in the catalog

A narration segment was sized at 4000 characters on the assumption that OpenAI's 4096 was the binding limit. Two vendors refuse past 2000, and the vendor is chosen after the first segment is already sent, so a segment sized for one vendor could be refused by whichever one actually answered. Segments are now 1900 characters.

  • Every routable text-to-speech candidate was probed with one pinned request. deepgram:aura-2 refuses past 2000 characters and inworld:inworld-tts-2 refuses past 2000; the other thirteen accept 3000. At 1900 every candidate accepts.
  • The segment size has to clear the smallest cap rather than a typical one, because the candidate that serves a narration is chosen from the first segment's response and so cannot influence how the text was split.
  • The bound is now asserted at compile time against the two vendors that set it, and the splitter's tests read the same constant instead of a copied number, so a future change cannot outgrow the cap unnoticed.

Action: No migration is required. A narration is divided into more segments than before, each one shorter; the audio and the voice are unchanged.

AddedScheduled
API

Long narration and long recordings now route instead of hitting a limit

Synthesis past a vendor's character cap answered with the vendor's own refusal, and a recording past 8 MiB answered 413 on the route the OpenAI SDK reaches. Long text is now split at sentence boundaries and narrated on one voice, and a transcription upload is allowed the 25 MiB OpenAI documents plus the first-byte budget the bytes actually need.

  • Synthesis longer than 1900 characters is split at sentence boundaries and synthesised a segment at a time. Measured on 2026-08-08, 10 000 characters answered 400 text_too_long with the vendor's message and failover count 0; the same request now returns audio.
  • The whole narration stays on the candidate that answered its first segment, so the speaker cannot change between two sentences. A segment that fails fails the request and the error names the segment, rather than returning audio that is quietly short.
  • Segments are joined as 24 kHz mono PCM, which every provider is already asked for, and a wav container is written once around the finished narration instead of once per segment.
  • On the streaming routes the first segment starts playing while the rest are still being synthesised. A segment that fails after the response has begun aborts the transfer, because the status line has already gone out and a truncated body must not look complete.
  • A narration over 16 segments is refused up front with the character count and the ceiling, rather than holding a connection for minutes.
  • Transcription uploads are allowed the 25 MiB OpenAI documents for the route callers reach with its SDK, plus headroom for multipart framing, which counts against the request body but not against OpenAI's file limit. At 8 MiB the router was three times stricter: a 12 MiB upload measured 413 request_too_large. Staying under the limit is also what keeps failover and model rewriting working, both of which a streamed body loses.
  • A batch transcription's first-byte deadline now includes an allowance for the bytes it ships, capped at two minutes. A 24 MiB upload measured 502 all_upstreams_failed with cause timeout on a flat 10 s, while the same audio at 12 MiB returned 200 in 13.2 s.
  • Requests within the old limits are unchanged: text at or under 1900 characters reaches the vendor in one request exactly as before, and the latency guard on synthesis and chat is untouched.
  • Usage attribution survives a narration: the provider, model and candidate that served it are reported on the narration's own response, and every segment's failover attempts are carried with it.
  • The segment size clears the smallest cap in the catalog, not a typical one, because the vendor is chosen from the first segment's response and cannot influence the size. Probing every routable candidate found deepgram:aura-2 and inworld:inworld-tts-2 refusing past 2000 characters, well below OpenAI's 4096.

Action: No migration is required. Callers that split long text themselves can keep doing so. A narration counts as one request per segment in router metrics, because that is how many upstream calls it makes, and it is attributed to the single candidate that served it.

FixedScheduled
API

A buffered narration now fails with a reason instead of outrunning the response deadline

A long narration on the buffered route could run past the platform's request deadline and come back 500 with no routing headers, giving the caller nothing to act on. It now stops at four minutes with a named cause and points at the streaming route.

  • Measured on 2026-08-08, 10 000 routed characters ran 264 seconds and returned 500 with no routing headers, because the response never reached the router's own error path.
  • How long a narration takes cannot be predicted from its segment count: the same 6000 characters took 188 seconds on one vendor and 46 seconds on another. The budget is measured from the narration's own observed pace and checked before each remaining segment.
  • Reaching it returns 504 narration_exceeds_one_response, naming how many segments finished and how long it took, and pointing at POST /v1/audio/speech/stream, which starts audio at the first segment and has no equivalent ceiling.

Action: No migration is required. A narration long enough to hit the budget previously failed anyway, with a less useful error. Long narration belongs on the streaming route.

ImprovedAvailable
API

voice "auto" and OpenAI preset names now follow the key's voice settings

A request that named any voice - including "auto" or an OpenAI preset like "alloy" - silently switched off the key's pinned voice and gender settings, and a preset then landed on each vendor's house default, so the voice could change identity or gender when routing failed over. "auto" now always defers to the key, and a preset defers to the key whenever the key has voice settings.

  • voice "auto" (and an empty voice) on /v1/audio/speech is treated exactly like an omitted voice on every routing decision: the key's pinned voice, gender preference, and per-model overrides apply, and accent routing is not suppressed.
  • An OpenAI preset name (alloy, nova, shimmer, ...) defers to the key's voice settings when the key has any. On a key with no voice settings, presets behave as before: served verbatim when OpenAI answers, the vendor's default voice otherwise.
  • A real pin - a provider-prefixed voice (the provider name, a colon, then the vendor's voice id), a Cartesia UUID, or an OpenAI voice id - is unchanged and still overrides the key.
  • With the key's settings applied, the voice is resolved per candidate, so a gender preference holds across failover instead of flipping with the vendor's house default.

Action: No migration is required. If you rely on the name alloy selecting OpenAI's alloy voice on a key that also has voice settings, pin it explicitly as a provider-prefixed voice (the provider name, a colon, then alloy).

AddedScheduled
APISDK

transcription: words, speakers, segments and the detected language

Transcription responses and streaming transcript frames now carry the word timings, speaker labels, utterance segments, duration, confidence and detected language the serving provider already sent and the router used to discard. text is byte for byte what it always was, and every added field is omitted when the provider that answered cannot supply it.

  • POST /v1/transcribe and POST /v1/audio/transcriptions answer the same JSON object as before plus language, duration, confidence, words[] and segments[]. Each word carries text, start, end and, where the provider labels speakers, speaker; each segment carries a provider utterance or a speaker turn. All offsets are seconds regardless of the unit the provider measured in, and a word the provider sent without offsets is omitted rather than published at 0. A field the serving provider has no contract for is left out, not sent as null, so a response from a provider that supplies nothing extra is identical to what the route answered before.
  • Streaming transcript frames on GET /v1/transcribe/stream gain the same words -- speaker labels included -- and a language field when the provider names one in the frame. words stays null, as it has been since the route shipped, for a provider whose socket carries no word-level detail. Soniox is the change callers will notice: it streams sub-word tokens that carry a speaker, which the router decoded and threw away, so a two-speaker conversation was indistinguishable from a monologue in real time. Those tokens are now aggregated into words with their speaker attached, on the live socket and in buffered responses alike.
  • Speaker labels are the serving provider's own vocabulary: Deepgram numbers speakers, AssemblyAI letters them, ElevenLabs prefixes them. They are comparable within one response and not across providers, and failover can change which provider answers, so treat every field but text as optional per response. The frame and response shapes are published as TranscriptFrame and Transcription in the OpenAPI document.
  • What each provider can report differs and the router does not invent the difference. Deepgram, AssemblyAI, ElevenLabs and Soniox return word detail, and an OpenAI-compatible verbose transcription body is read when a caller asks for one; AssemblyAI's socket publishes no word text, timing or speaker, and Cartesia, Smallest, Alibaba, Gradium and xAI return the transcript alone. Diarization and word timestamps still depend on what the provider sends by default; requesting them explicitly arrives in a later release.
  • Fixed: a Gradium transcription response spanning more than one line failed the request with an invalid-JSON error and moved the caller to another provider. Gradium answers in newline-delimited JSON and the normalizer parsed the whole body as a single document before handing it to the line-by-line decoder, so only single-line responses ever succeeded.
  • @speko/web now exports TranscriptFrame, Transcription, TranscriptWord and TranscriptSegment types. The LiveKit plugin maps word speakers into TimedString.speaker_id and reports the provider's detected language on its SpeechData instead of echoing the requested one; the Pipecat plugin maps the reported language onto its transcription frames, and the whole frame -- words and speakers with it -- continues to ride on result.

Action: No migration is required. Every field is additive and text is unchanged, so a caller that reads only the transcript needs no change. Callers that read the new fields should treat all of them as optional per response, because providers differ in what they report and failover can change which one answers.

AddedScheduled
APISDKDocs

transcription: ask for diarization, word timings, language hints and detection

All three transcription routes now take the same five options -- diarize, word_timestamps, smart_format, detect_language and language_hints -- and route only to providers that can honour them. A request no available provider can serve is refused rather than answered without the feature. Streaming config frames now reject unknown fields with INVALID_CONFIG instead of ignoring them in silence.

  • POST /v1/transcribe and POST /v1/audio/transcriptions accept diarize (diarization is accepted too), word_timestamps, smart_format, detect_language and language_hints as query parameters; language_hints is comma-separated, at most eight tags, and every tag must be a language this router is configured to serve. GET /v1/transcribe/stream accepts the same five in its config frame as diarize, wordTimestamps, smartFormat, detectLanguage and languageHints, each also accepted in snake_case. Query keys the router does not own keep reaching the selected provider exactly as before.
  • The options narrow the candidate set and fail closed. When no available provider can serve one on the transport in use, a buffered request answers 422 unsupported_feature naming the feature and the transport, and the socket answers an UNSUPPORTED_FEATURE error frame and closes; neither spends an upstream call. A transcript with no speakers in it is indistinguishable from a recording of one speaker, so a request that was accepted and quietly not honoured would never be reported. smart_format is the exception and narrows nothing.
  • What a provider can do depends on the transport, and the router now records that per transport rather than per provider. AssemblyAI labels speakers and times words on a buffered upload and its socket carries neither; ElevenLabs is the same; Deepgram's language detection is a pre-recorded feature; Soniox is the only provider that accepts several candidate languages, on either transport, and its hint list finally carries all of them instead of the one language the request resolved to. Diarization on OpenAI transcription remains unavailable: OpenAI diarizes only through a model that is not on the routing board.
  • POST /v1/audio/transcriptions also honours OpenAI's own two parameters. timestamp_granularities[] containing word means the same as word_timestamps=true, with the query parameter winning when both are sent. response_format accepts json (the default) and verbose_json, which both answer the same transcription object, and text, which answers text/plain with the transcript alone; srt and vtt answer 400 unsupported_response_format because both are built from segment timings the answering provider may supply none of. This also fixes response_format=text and response_format=verbose_json, which were forwarded to the provider verbatim and failed the request with 502 after trying every candidate.
  • GET /v1/routing/preview accepts transport=buffered|streaming (buffered by default) and the five options, applying the same narrowing. A streaming preview reports the socket's own dial order and verified-language set, so a Russian streaming preview now reports soniox:stt-rt-v5 where the buffered preview reports OpenAI -- the disagreement disclosed when Russian streaming shipped. A streaming preview only names configured providers, so routable_only=false cannot widen it.
  • @speko/web takes diarize, wordTimestamps, smartFormat, detectLanguage and languageHints on connectTranscription and sends only the options that were set.

Action: One behavior change needs attention: a GET /v1/transcribe/stream config frame carrying a field the router does not define, including inside sttOptions, is now refused with the existing {"type":"error","code":"INVALID_CONFIG"} frame and the socket closes. Such a frame was previously accepted and the unknown field ignored, so a misspelled option produced a working socket that did none of what was asked. Send only type, language, interimResults, sampleRate, sttOptions.language and the five options above; both plugins and @speko/web already do. Nothing else requires migration: every option is opt-in, transcripts for callers that send none are unchanged, and a caller that sets one should expect 422 unsupported_feature or an UNSUPPORTED_FEATURE frame when their allow list leaves no provider that can serve it.

Earlier updates

33 older entries, listed by date. Each one's summary, details, and migration action stay in the JSON and RSS feeds.

  • Fixedtranscription: regional languages and browser streaming both reach the provider
  • Addedtranscription: forward a vendor's own settings, and set defaults on a key
  • BreakingElevenLabs batch and realtime transcription become distinct models
  • FixedA voice policy now picks a voice curated for the language
  • ImprovedEvery text-to-speech model on the board now publishes a cost
  • AddedRussian streaming transcription routes through Soniox
  • ImprovedUsage, activity, and API key limits follow the active organization
  • ImprovedAPI keys follow the active organization, and Russian gains LLM routing
  • FixedLanguage lists are alphabetical and Norwegian is labeled plainly
  • AddedHindi, Tamil and Telugu are routable
  • AddedGroq, Baseten, Fireworks and Amazon Polly are wired
  • Fixedvoice: auto no longer refuses to synthesize
  • ImprovedPer-language routing evidence for thirteen more languages
  • FixedComplete /v1/models schema in the OpenAPI contract
  • ImprovedArabic, German, French and Norwegian are routable, and Spanish transcription follows its own numbers
  • AddedDeepgram Flux is routable, Hindi, Tamil and Telugu transcription are published, and vendor pricing returns to two boards
  • AddedVoice constraints on router keys: gender, pinned voice, per-model overrides
  • AddedVoice policy controls for router keys
  • AddedNamed voice rosters in the TTS catalog
  • FixedStreaming transcription now honours the provider and language a request asks for
  • ImprovedStreaming TTS reaches every provider that can stream, including the regional-accent voices
  • AddedStreaming transcription adds ElevenLabs and Soniox, and prefers them for Spanish
  • FixedRouting preview reflects the per-language transcription preference
  • FixedA regional language tag no longer silences WebSocket TTS providers
  • FixedA synthesis request with no text returns 400 instead of a provider error
  • ImprovedGemini TTS audio now leaves the router as it arrives
  • FixedAn omitted speed no longer breaks synthesis on Hume and Smallest AI
  • AddedTwo new OpenAI STT models, Spanish routing moves to AssemblyAI, and the API contract is corrected to match the router
  • FixedStreaming TTS works without a provider pin, and the WebSocket text lifecycle has one owner
  • AddedOpenAI realtime speech-to-text
  • FixedAssemblyAI streaming transcription names the model with the parameter AssemblyAI still accepts
  • FixedAn accent tag no longer reduces the provider board to two candidates
  • FixedAPI compatibility restoration for Gemini, AssemblyAI, and Russian STT