--- name: terra-streaming description: Best practices for the Terra API Streaming (Real-Time) API – live, roughly per-second biometric data from wearables over websockets. Use when building realtime streaming with Terra API, TerraRT SDKs, or wss://ws.tryterra.co; streaming live heart rate, steps, distance, acceleration, ECG, HRV, RR intervals, calories, MET, power, cadence, speed, location, or gyroscope; pairing a wearable over BLE or ANT+; producing sensor data from a mobile app; consuming a live stream on a backend; or streaming from an Apple Watch or Wear OS watch. Covers the producer/broker/consumer architecture, the websocket handshake and opcodes, token minting, close codes, replay/backfill, and per-platform SDK setup (iOS, Android, React Native, Flutter, Wear OS). license: MIT compatibility: Requires network access to docs.tryterra.co for the full websocket protocol reference metadata: author: terra version: "1.0.0" --- # Terra API Streaming Best Practices Guidelines for building on the Terra API Streaming (Real-Time) API, which delivers live, roughly per-second metrics from wearables. This skill carries the architecture and the protocol gotchas inline; platform setup and the full consumer protocol live in `references/`. ## Start from an example app For a new app, prefer the published example for a React Native producer; use `streaming-consumer-web-app` for a web consumer, then adapt it to the user's requirements. Install the CLI if missing, discover the current catalog, and clone the closest fit: ```sh npm install -g @tryterra/cli terra examples list --select name,title,description terra examples clone streaming-mobile-app my-app ``` Listing and cloning need no login or selected environment. The destination must not exist and its parent must exist. Read the downloaded README and AGENTS.md and follow `next_steps` to install dependencies, configure, and run it before building on it. Cloning only downloads files. For an existing app, use the example as a reference and adapt the relevant pieces in place. ## From the terminal Account configuration lives in the [Terra dashboard](https://dashboard.tryterra.co), which an agent cannot click. The `terra` CLI does the same from a terminal. The streaming websocket surface itself has no CLI commands, and **the CLI does not mint the short-lived token a streaming client authenticates with**: that is minted by your backend, per session, through the flow this skill describes. What the CLI covers is the layer under that: the long-lived credential your backend holds in order to mint them, and whether the user being streamed for exists at all. A stream that will not open is usually a credential without the right scope, a user id with no connection behind it, or a provider that was never enabled in this environment. All three are read-only checks, so run them before debugging the socket: ```sh terra environments list --select dev_id,name terra data-tokens list --env --select token_id,name,scopes,expires_at,last_used_at,revoked_at terra users list --env --user-id --select user_id,provider,active terra unified-api sources list --env ``` `data-tokens list` answers the credential question without printing a secret: it shows which tokens exist, what each is scoped for, and whether one has expired or been revoked. **Do not mint a token to find out whether a token works, and do not reach for `terra environments retrieve-api-key` here**: it prints the environment's API key and webhook signing secret, neither of which tells you anything about a socket, and in CI both land in the job log. Minting is a setup step rather than a diagnostic. When the list genuinely shows no usable token, `terra data-tokens create --env --name streaming --scopes auth:write` returns the bearer once, and several tokens coexist per environment so the old one keeps working until you revoke it. Install it with `brew install tryterra/tap/terra` on macOS or `npm install -g @tryterra/cli` elsewhere. The `terra-cli` skill carries the guardrails (`--yes` on destructive commands and careful handling of credential output), the exit codes, and a playbook per task. It administers the integration; it does not replace the API calls this skill describes. ## Streaming vs Health & Fitness The Streaming API is for realtime, sub-second-to-per-second signals only. The RT SDKs accept seventeen `DataTypes` values, covering cardiac (heart rate, HRV, RR intervals, ECG), movement (steps, cadence, distance, speed, floors climbed, activity), cycling (power, bike cadence), motion sensors (acceleration, gyroscope), energy (calories, MET), and location. See [references/data-types.md](references/data-types.md) for the full enum. Anything with a longer span – workouts, sleep, daily totals, body, nutrition – belongs to the [Unified API](https://docs.tryterra.co/unified-api/getting-started), not here. If you need a completed workout summary rather than a live feed, you are on the wrong API. Devices only appear on the stream when they actually broadcast over BLE, ANT+, or a supported custom Bluetooth protocol (heart-rate straps like the Polar H10 or Wahoo TICKR, and some watches). No broadcast means no stream. **Requesting a data type is not the same as receiving it.** Terra does not gate or filter by device: the broker passes payloads through opaquely, so what arrives is decided entirely by the wearable's own broadcast profile. Most heart-rate straps are cardiac only and will never produce `STEPS` or `LOCATION` however the SDK is configured. Render per-signal state and degrade gracefully rather than treating a missing data type as an error. See [references/data-types.md](references/data-types.md). ## Architecture: producer, broker, consumer Realtime streaming has four parts and you build three connections between them. See [getting-started](https://docs.tryterra.co/streaming-api/getting-started). 1. **Wearable** – the strap, watch, or sensor, broadcasting over BLE or ANT+. 2. **Producer** – your mobile app, running a Terra Real-Time (RT) SDK. It receives the wearable's data and forwards it to the Terra API. 3. **Terra API WebSocket broker** – the server that routes the live stream. This is Terra API infrastructure; you never host it. 4. **Consumer** – your backend, which connects to the broker and receives the stream. The three connections you build: - **Wearable to app**: the user pairs their wearable to your app over Bluetooth/ANT+ using an RT SDK. - **App to broker**: your app opens a _producer_ connection and forwards the wearable's data. - **Broker to backend**: your backend opens a _consumer_ connection and receives the data live. You identify a user by your own `reference_id`. The Terra API mints a Terra user ID for that user (no auth widget needed); that ID is what the token endpoints take and what arrives as the `uid` field on every payload. ## Tokens Every websocket connection authenticates with a short-lived token minted by your backend from your Dev ID and API key. **All three tokens are single-use** – the server deletes each one after a successful IDENTIFY, so every reconnect needs a freshly minted token. Never ship your API key into the app; mint tokens server-side and hand them off. | Token | Endpoint | Used by | IDENTIFY type | | -------------------- | ---------------------------------------------------------- | ----------------------------------------------------------- | ----------------- | | Phone-registration | `POST https://api.tryterra.co/v2/auth/generateAuthToken` | RT SDK `initConnection` (registers the phone as a producer) | n/a (SDK-managed) | | Producer | `POST https://ws.tryterra.co/auth/user?id=` | producer connection sending data | 0 (USER) | | Consumer / developer | `POST https://ws.tryterra.co/auth/developer` | your backend consumer | 1 (DEVELOPER) | Note the hosts: only `generateAuthToken` lives on the main API (`api.tryterra.co`). The producer and consumer token endpoints are served over HTTPS by the websocket host (`ws.tryterra.co`) and do **not** exist on `api.tryterra.co`. For the exact request/response schemas, fetch [the REST endpoints reference](https://docs.tryterra.co/reference/streaming-api/api-endpoints.md) when building the request. The phone-registration token is single-use and **expires 3 minutes** after minting (returned as `expires_in`), so mint it just-in-time – when the app is about to call `initConnection`, not at app startup or ahead of a queue. The producer endpoint needs the Terra user ID in the `id` query parameter; retrieve it from the SDK's `getUserId`. ## The websocket There is one endpoint for both roles: `wss://ws.tryterra.co/connect`. The IDENTIFY `type` field (0 producer, 1 consumer) decides which role the connection plays. The SDKs open and drive the producer connection for you; you write the consumer connection by hand against the walkthrough in [references/consumer-protocol.md](references/consumer-protocol.md). The protocol is opcode-framed: HELLO and heartbeats, then IDENTIFY and READY, then DISPATCH data payloads, with REPLAY for backfill (SUBMIT is producer-side and SDK-abstracted). For the full opcode table and exact JSON payload shapes, fetch [terra-greater-than-your-backend.md](https://docs.tryterra.co/streaming-api/terra-greater-than-your-backend.md) when writing the frames. ## Protocol gotchas These are the things that bite. The step-by-step consumer walkthrough is in [references/consumer-protocol.md](references/consumer-protocol.md). - **IDENTIFY within 15 seconds** of connecting or the server closes with **4000**. - **Tokens are deleted after a successful IDENTIFY.** A dropped connection cannot reuse its token; mint a fresh one before reconnecting. - **Heartbeats.** HELLO carries `heartbeat_interval` (ms). Send the first heartbeat after `heartbeat_interval * jitter` (jitter random in 0..1), then at most once per interval. If you get no HEARTBEAT_ACK, close and reconnect. If the server sees no heartbeat within the window it closes with **4005**. - **Close codes split into two groups.** 4000, 4003, 4004, and 1003 signal a client bug – fix the client, do not retry-loop, since a blind reconnect just loops on the same error. 4001 means mint a fresh token. 4002 means the server's consumer session cap rejected the connection – do not architect around any guaranteed number of concurrent consumers; on 4002, close an existing session or back off rather than retry-looping. - **`seq` is monotonic but sparse.** Gaps between consecutive sequence numbers are normal and do not mean lost data. Use `seq` only to order DISPATCHes and as the `after` bound for replay. - **REPLAY takes exclusive bounds; send both.** `after` and `before` are both required – a REPLAY that omits `before` returns no messages. On reconnect, set `after` to the last `seq` you processed and `before` to the `seq` of the first live DISPATCH to backfill exactly the gap. - **Replay lags a few seconds.** A payload becomes replayable a few seconds after it was delivered live. If a REPLAY returns fewer messages than expected, wait a moment and request again. - **Test without hardware.** From the Streaming page of the [Terra dashboard](https://dashboard.tryterra.co/dashboard/streaming), create a test user; the Terra API streams synthetic live data through the real API so you can validate a consumer end to end before touching a device. - **Re-init the RT SDK on every app open or foreground.** Producer registration does not survive backgrounding. - **Apple Watch records at reduced frequency outside a workout session.** Start a workout session on the watch to capture data at the highest frequency. ## References Read the reference for the surface you are building. - [references/consumer-protocol.md](references/consumer-protocol.md) – **read this before writing the backend consumer.** The handshake walkthrough (HELLO, heartbeats, IDENTIFY, READY, DISPATCH), DISPATCH field semantics, the REPLAY backfill recipe, and close-code handling, with pointers to the live doc for exact payload shapes. - [references/data-types.md](references/data-types.md) - read when choosing what to request from the RT SDK, or when a stream is silent. The full `DataTypes` and `Connections` enums, why requesting is not receiving, and the Wear OS exercise-prefixed type labels. - [references/ios.md](references/ios.md) – read when the producer app is native iOS or you are wiring an Apple Watch. Apple Developer Program membership is required. - [references/android.md](references/android.md) – read when the producer app is native Android, including ANT+ and programmatic device scans. - [references/react-native.md](references/react-native.md) – read when the producer app is React Native. - [references/flutter.md](references/flutter.md) – read when the producer app is Flutter (note the `startRealtimeToApp` vs `startRealtimeToServer` split). - [references/wear-os.md](references/wear-os.md) – read when streaming from a Wear OS watch paired to an Android phone. Full docs: [docs.tryterra.co/streaming-api](https://docs.tryterra.co/streaming-api/getting-started) (append `.md` to any docs URL for a markdown version). If the terra-docs MCP server (`https://docs.tryterra.co/~gitbook/mcp`) is connected, use its tools to search and fetch the docs instead. ## Decisions that are yours to make The docs deliberately leave these open; pick what fits your app rather than assuming a default: - **Which data types to stream.** You pass the set of types to the SDK; stream only what you use. - **Device-scan caching.** Flags like `useCache` and `showWidgetIfCacheNotFound` (Android) trade a faster reconnect to a known device against always showing the picker. - **Local-only vs server streaming.** Streaming to your app locally and streaming to the broker are separate choices. Flutter forces the split explicitly: `startRealtimeToApp` (local callback only) vs `startRealtimeToServer` (broker only). On other platforms the same `startRealtime` call streams to the broker when you pass a token. - **Token hand-off.** How the app fetches a producer token from your backend (endpoint shape, auth) is up to you. - **Reconnect and backfill strategy.** How aggressively you reconnect, whether you replay on every drop, and how you persist the last processed `seq` are your call. - **Consumer topology.** One consumer fanning out internally vs several is your architecture (subject to the session cap). - **`reference_id` scheme.** What your `reference_id` maps to in your own system.