# Live STT WebSocket protocol -- `wss://.../v1/stream`

OpenAPI can't express WebSockets; this document is the contract for the
stt-proxy Worker's live-transcription relay. Implementation:
`workers/stt-proxy/src/index.js` + `translate.js` (keep in sync).
Consumers: the desktop's `WorkerProxyAdapter` (`src/main/agent/stt/`),
future React Native capture.

## Connect

```
wss://seameet-stt-proxy-dev.seameet.workers.dev/v1/stream?lang=<iso-639-1, optional>
Authorization: Bearer <supabase user JWT>
```

Auth is the standard `Authorization` header on the upgrade request --
Node/Electron and React Native WebSocket clients can set it; **browser
`WebSocket` cannot set headers**, so the web library does NOT do live STT
(by design -- capture is a device feature). Verification: ES256 against the
project JWKS with HS256 fallback. Entitlement: same `checkEntitlement` as
sync-api (plan `sync`, active/trialing, not ai_disabled); failures close
the socket immediately after upgrade.

The STT provider (Soniox `stt-rt-v4` / Deepgram `nova-3`) is chosen
server-side from the `provider_routing` table -- clients never pick. The
`lang` query hint participates in routing (`match_lang` rules).

## Client -> server frames

| Frame | Encoding | Meaning |
|---|---|---|
| Binary | PCM 16-bit LE, 16 kHz, mono | Audio; send in ~100-250 ms chunks |
| Text `{"type":"finalize"}` | JSON | Flush upstream + emit remaining finals; server closes when drained |

## Server -> client frames (JSON text)

| `type` | Payload | Meaning |
|---|---|---|
| `ready` | `provider` | Upstream relay connected; start sending audio |
| `segment` | `segment: TranscriptSegment` | One transcript segment. `segment.isFinal` distinguishes interim (replaces the previous interim) from committed text. Shape contract: `src/main/agent/stt/STTProvider.js` -- `{text, speaker?, language?, confidence?, startMs?, endMs?, isFinal}` |
| `error` | `error` (code string) | Fatal; socket closes next. Notable codes: `all_providers_unavailable` |

## Lifecycle & limits

- One session = one recording. Sessions are metered into `usage_events`
  (`feature: 'stt'`, `metadata.path: 'managed'`; Soniox sessions bill by
  the documented token conversion, Deepgram per second).
- Reconnect policy (client): one immediate reconnect on unexpected
  mid-session close (the desktop's WorkerProxyAdapter does this);
  captions resume as a new session -- no replay.
- Concurrency is capped per provider account; `GET /v1/health` exposes
  live session counts (see `openapi.yaml`).
