Streaming
The canonical session WebSocket — subprotocol, frames, and the event schema.
Attaching
GET /v1/sessions/{session_id}/stream upgrades to a WebSocket. Requirements:
Authorization: Bearer <SPEKO_LOCAL_AUTH_TOKEN>- The subprotocol
speko.voice.v0.r3, sent asSec-WebSocket-Protocol(missing →400 unsupported_protocol) - One consumer per session — a second attach returns
409 stream_already_attached - Attach within 30 seconds of session creation
Client → gateway frames
| Frame | Meaning |
|---|---|
| binary | Raw audio in the session's media format |
{"type":"audio.commit"} | End of the current audio input |
{"type":"text.append","data":{"text":"Hello"}} | Append text input (TTS/LLM) |
{"type":"text.commit"} | Flush appended text |
{"type":"response.cancel"} | Cancel the in-flight response |
{"type":"session.close"} | Close the session cleanly |
Anything else is a connection error. Input is bounded per session: 64 queued messages / 1 MiB queued bytes — producers that outrun the provider see backpressure errors rather than unbounded memory growth.
Gateway → client frames
Binary frames carry synthesized audio. Text frames are JSON events:
{
"type": "transcript.final",
"event_id": "evt_...",
"session_id": "session_...",
"attempt_id": "attempt_...",
"sequence": 12,
"created_at_ms": 1754000000000,
"data": { "text": "Hello there." }
}Event types:
| Category | Events |
|---|---|
| Lifecycle | session.ready, session.recovering, session.recovered, session.closed |
| Speech | speech.started, speech.ended |
| Transcription | transcript.delta, transcript.final |
| Generation | response.started, text.delta, text.done, tool.call, response.done, response.cancelled |
| Audio | audio.started, audio.done (audio itself arrives as binary frames) |
| Metadata | alignment, route.selected, usage.observed, usage.reported, warning, error |
The error event
{
"type": "error",
"data": {
"code": "provider_unavailable",
"message": "...",
"retryable": true,
"terminal": true,
"source": "provider",
"provider_status": 503
}
}source is runtime or provider. Provider-sourced codes: provider_unavailable, provider_rate_limited, provider_quota_exceeded, authentication_failed, invalid_request, input_too_large. Runtime codes include internal and session_lease_expired. The latter means the fixed deadline in the verified plan elapsed; provider-direct sessions do not renew it mid-call.
Liveness
Frames the gateway writes time out after 15 seconds if the consumer stalls; slow consumers see the session terminated rather than blocking other sessions.