Speko Docs
Speech to text

Streaming transcription

GET /v1/stt/stream — live transcription over WebSocket.

Connecting

Open a WebSocket to wss://relay.speko.dev/v1/stt/stream with both required headers on the upgrade:

Authorization: Bearer sk_speko_...
Idempotency-Key: <unique key>

Browsers cannot set these headers on a WebSocket upgrade — connect from your backend.

Session lifecycle

The first frame you send must be session.configure, within 10 seconds of connecting. Its exact bytes are the idempotency content hash for the session.

{
  "type": "session.configure",
  "routing": { "mode": "auto", "objective": "latency" },
  "audio": { "encoding": "pcm_s16le", "sample_rate_hz": 16000, "channels": 1 },
  "language": "en"
}
audio.encodingstringrequired

pcm_s16le or opus.

audio.sample_rate_hznumberrequired

8000–192000.

audio.channelsnumberrequired

1–8.

Then:

  • Send audio as binary frames in the configured format.
  • Send {"type":"input.commit"} to signal end of input and ask for final results.
  • Send {"type":"session.close"} to end the session.

Server frames

The first server frame is always session.ready:

{ "type": "session.ready", "request_id": "rreq_...", "route": { "provider": "cartesia", "model": "ink-2", "region": "eu-west-1", "attempt_id": "ratt_..." } }
FrameMeaning
session.readySession admitted and routed; always first
transcript.deltaInterim hypothesis: {"type":"transcript.delta","text":"..."} (text is always non-empty)
transcript.finalFinalized text: {"type":"transcript.final","text":"...","segments":[...]}
usage.updatedRunning usage snapshot
session.closedClean end: {"type":"session.closed","usage":{...}}
errorTerminal failure — the standard error envelope

Exactly one terminal frame ends every session: session.closed or error. A socket that drops without either was truncated — treat results as incomplete.

Liveness and limits

  • The relay pings every 20 seconds; frames are capped at 1 MiB.
  • Streaming sessions meter audio duration against a lease that the relay renews internally. If the session's budget or lease cannot be extended, the stream terminates with budget_exhausted or lease_expired — these two codes only ever appear on established streams, never as HTTP statuses.

On this page