raehDocs
Reference

WebSocket /stream/subscribe

Wire-level reference for client-side insight delivery.

The endpoint client apps use to receive live insights.

wss://api.raeh.io/stream/subscribe

Query parameters

NameRequiredDescription
api_keyYour raeh_* API key. Determines which account's insights you receive.

No device_id, no session_id, no filter parameters. The subscription is account-scoped; filter client-side if you only care about a specific session or device.

Subscribe ack

Right after the WebSocket opens, the server sends one small JSON message:

{ "status": "subscribed" }

Drain it and discard. Every subsequent message is an insight.

Insight payload

{
  "type": "hr",
  "value": 72.4,
  "unit": "bpm",
  "ts": 1760000000000,
  "session_id": "ad225407-36de-48b1-bcf2-14d5c057aeca",
  "device_id": "xyz-watch-a3b5",
  "confidence": 0.87,
  "sqi_class": "excellent"
}
FieldTypeDescription
typestringhr, spo2, or rr. Treat unknown types as future-compatible noise.
valuenumberReading in the unit below.
unitstringbpm for HR, % for SpO₂, brpm for RR.
tsnumberPublish time (ms since epoch).
session_idstringUUID of the session the insight came from.
device_idstringExternal device ID; the same string passed to /stream/ingest.
confidencenumberSignal Quality Index, 0..1. How much of the analysis window was actually covered by fresh, gap-free data. Higher is better.
sqi_classstringDiscrete bucket: "excellent" (≥ 0.8), "acceptable" (≥ 0.5), or "unfit" (< 0.5). Matches the PPG SQI literature.

Cadence and starting delay: see Building a Client App → What insights you can expect.

How to read confidence / sqi_class

Every algorithm in the pipeline emits a number, but not every number is equally trustworthy. confidence is a continuous quality score summarizing how much of the requested analysis window was actually filled by in-window, low-gap data from the input the algorithm depends on. It's derived as coverage × (1 − gap_fraction) and clamped to [0, 1].

Recommended UI treatment:

  • sqi_class == "excellent": show the value at full opacity, no annotation.
  • sqi_class == "acceptable": show the value, optionally dim it slightly (e.g. 70% opacity) to indicate a hint of uncertainty.
  • sqi_class == "unfit": don't show the value as if it's authoritative; either hide it, show a dash, or label it "estimating…".

confidence can also come from algorithm-specific logic (e.g. R-peak regularity for HR), in which case it overrides the generic geometric score. Treat the buckets as the canonical UI contract; the continuous number is for finer-grained animations or thresholds.

Close codes

CodeReasonWhen
1000Normal closureEither side cleanly closed.
4001Authentication failedapi_key missing, invalid, revoked, or bound to a different account.

Subscription longevity

The subscription is intended to be long-lived. There's no server-enforced idle timeout today. Typical pattern:

  1. Client opens on app foreground.
  2. Streams insights as long as the connection is open.
  3. Reconnects with exponential backoff on network drop.
  4. Client closes on app background or user logout.

Insights published while the subscription is disconnected are not replayed on reconnect. If you need to reconcile the gap, query historical insights via REST:

GET /v1/sessions/{session_id}/insights

No filtering parameters (yet)

A single subscription receives insights from every session under the account. A watch under user A's phone and a watch under user B's phone both deliver to a key scoped to the shared account. In practice:

  • Per-user keys (recommended for production): each user's key only sees their sessions naturally.
  • Shared key + client-side filter: filter on session_id or device_id in the app.

Server-side subscription filters (e.g. ?session_id=...) are on the roadmap.

Multiple subscribers

Several clients can subscribe concurrently with the same key. Every insight is delivered to every subscriber. Example: a phone app + a web dashboard both listening; both see every reading.

Heartbeats

No server-side ping is sent today. If you're running in an environment where idle TCP connections get dropped by an intermediary (corporate proxy, mobile carrier NAT), send a WebSocket ping from the client side every 30–60 s and watch for the pong.

On this page