Speko Docs
RouterReference

Realtime WebSocket

The contract both streaming sockets share: subprotocol, config frame, ephemeral tokens, shared error codes.

Both realtime routes speak the same protocol. This page is the contract; the per-modality frames and fields live on STT streaming and TTS streaming.

RouteSocket
STTwss://api.speko.ai/v1/transcribe/stream
TTSwss://api.speko.ai/v1/synthesize/stream

The shape of a session

  1. Connect with your key and the speko.realtime.v1 subprotocol.
  2. Send one JSON config frame. It must be the first message, within 5 seconds.
  3. Receive {"type":"ready"} naming the provider that answered.
  4. Send your payload — binary audio frames for STT, {"type":"text"} frames for TTS.
  5. Receive resultstranscript frames for STT, binary PCM for TTS.
  6. Send {"type":"end"} when you are done, and wait for the last frame before closing.

The config frame

It must be the first message and must be text, not binary. type: "config" is required; every other field is per-modality and documented on that modality's page.

Miss the 5-second window, send binary first, or omit type: "config" and the socket closes with INVALID_CONFIG. This is the most common integration failure — the payload starts flowing before the config frame is written.

Shared error codes

CodeCause
INVALID_CONFIGThe first frame was late, binary, not JSON, or not type: "config".
UNSUPPORTED_LANGUAGEThe language is not enabled for this router.
UPSTREAMEvery ranked provider failed to open a socket.

An error frame is followed by the socket closing. Before the upgrade you get ordinary HTTP statuses instead — 403 origin_not_allowed when a browser Origin is not on the allowlist, and a route-specific 503 when no streaming-capable provider is configured at all. TTS carries four further codes; see TTS streaming.

Failover, and what happens after the first byte

Selection, the upstream dial and the vendor handshake all finish before the first byte is committed, so failover across candidates happens in that window.

Failover stops once the payload flows. If a provider drops mid-utterance the socket closes. A vendor is never swapped mid-utterance. Reconnect and resume; do not expect the router to hide it.

From a browser

Browsers cannot set headers on a WebSocket handshake, so mint a short-lived token server-side and pass it in the subprotocol list:

curl -X POST https://api.speko.ai/v1/realtime/sessions \
  -H "Authorization: Bearer $SPEKO_API_KEY"
{ "client_secret": { "value": "eyJ….Ab3…", "expires_in": 60 } }
new WebSocket('wss://api.speko.ai/v1/transcribe/stream', [
  'speko.realtime.v1',
  `speko.ephemeral.${clientSecret}`,
]);

The token is valid for 60 seconds and carries the same policy as the key that minted it. Never ship a router key to a browser.

On this page