Speko Docs
Text to speech

Streaming synthesis

GET /v1/tts/stream — incremental text in, audio out, over WebSocket.

Connecting

Open a WebSocket to wss://relay.speko.dev/v1/tts/stream with Authorization and Idempotency-Key headers on the upgrade, then send session.configure as the first frame (within 10 seconds):

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

Optionally include "voice" to pin a provider voice ID. As with one-shot synthesis, streaming TTS is currently English-only — see the note on the speech endpoint.

Client frames

FrameMeaning
session.configureFirst frame; fixes routing, audio format, and the idempotency hash
input.append{"type":"input.append","text":"..."} — add text to synthesize (text non-empty)
input.commitFlush: synthesize everything appended so far
input.cancelStop synthesizing the current utterance
session.closeEnd the session

Binary frames from the client are rejected — TTS input is text.

Server frames

Audio arrives as binary frames between utterance.started and utterance.done markers:

FrameMeaning
session.readyAlways first: {"type":"session.ready","request_id":"...","route":{...}}
utterance.started{"type":"utterance.started","sequence":1} — sequence is the 1-based utterance index
(binary frames)Audio for the current utterance, in the configured format
utterance.done{"type":"utterance.done","sequence":1}
usage.updatedRunning character usage
session.closedClean end, with final usage
errorTerminal failure — standard error envelope

Exactly one terminal frame (session.closed or error) ends every session.

Metering and budgets

Characters are counted when an input.append is accepted — before synthesis, and regardless of whether you later cancel. input.cancel stops audio, not billing, for text already accepted.

Streaming sessions admit against an initial character budget and extend it transparently as you append more text. If your organization's credit cannot cover an extension, the next append terminates the stream with budget_exhausted; if the session's lease cannot be renewed, it terminates with lease_expired. Both codes appear only on established streams.

Liveness

The relay pings every 20 seconds. Frames are capped at 1 MiB.

On this page