SuperAPI AI Directory

terra-streaming

Agents & models

View repository

Updated 10-16 01:26

About this skill

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, start from the published example and adapt it to the user's requirements: streaming-mobile-app for a React Native producer, streaming-consumer-web-app for a web consumer. Run terra version and, if the CLI is missing, offer to install it (see below). Then list the current catalog and clone the closest fit:

terra examples list --select name,title,description
terra examples clone streaming-mobile-app my-app

Neither needs a login. 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. In a chat assistant, which cannot run the CLI, give the user these commands to run in their own terminal, or link the source at https://github.com/tryterra/terra-examples/tree/main/examples/streaming-mobile-app.

Account tools: CLI or MCP

The streaming websocket surface has no account commands, and neither the CLI nor the MCP mints the producer or consumer token a socket authenticates with: your backend mints those, per session, through the flow this skill describes. What the account tools cover is the layer under that: the long-lived credential your backend holds, 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:

terra environments list --select dev_id,name
terra data-tokens list --env <dev-id> --select token_id,name,scopes,expires_at,last_used_at,revoked_at
terra users list --env <dev-id> --user-id <uuid> --select user_id,provider,active
terra unified-api sources list --env <dev-id>

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 <dev-id> --name streaming --scopes auth:write returns the bearer, and several tokens coexist per environment so the old one keeps working until you revoke it. Over MCP, minting a data token needs the user's approval in the dashboard and an access level the default MCP sign-in does not grant, so this one step is simpler with the CLI.

In a coding agent (Claude Code, Codex, Cursor and the like, on the user's machine or in the cloud), use the CLI, and offer to install it if it is missing: curl -fsSL https://cli.tryterra.co/install.sh | sh on macOS and Linux, irm "https://cli.tryterra.co/install.ps1" | iex in Windows PowerShell, or Homebrew or npm where the user already uses them. In a chat assistant (claude.ai, Claude Desktop, ChatGPT), use the Terra API admin MCP server, even when it can run code: its sandbox cannot install the CLI or sign in. Each admin command is a tool whose name and method spell it (terra users list is users_read with method: "list"). Only the CLI has terra data-api, terra admin-api and terra examples, and --select and --jq have no MCP equivalent. The terra-cli and terra-mcp skills carry the guardrails (confirming changes, keeping credentials out of transcripts), the errors, and a playbook per task. They administer the integration; they do 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 for the full enum. Anything with a longer span – workouts, sleep, daily totals, body, nutrition – belongs to the Unified API, not here. If you need a completed workout summary rather than a live feed, you are on the wrong API. Connecting Apple Health, Samsung Health or Health Connect is the mobile SDK (terra-mobile-sdk), not the Real-Time SDK.

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 API 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.

Architecture: producer, broker, consumer

Realtime streaming has four parts and you build three connections between them. See getting-started.

  1. Wearable – the strap, watch, or sensor, broadcasting over BLE or ANT+.
  2. Producer – your mobile app, running a Terra API 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 API 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, which authenticates the mint with an auth:write data token (Authorization: Bearer terra_dt_...) or the environment's dev-id and x-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 a credential that can mint tokens into the app, in development or production; mint server-side and hand the token off. For the phone-registration token, the credential your backend holds is the auth:write data token from "Account tools" above, not the environment API key.

Token Endpoint Used by IDENTIFY type
Phone-registration POST https://api.tryterra.co/v2/auth/tokens RT SDK initConnection (registers the phone as a producer) n/a (SDK-managed)
Producer POST https://ws.tryterra.co/auth/user?id=<terra_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 the phone-registration endpoint lives on the main API (api.tryterra.co); the reference files and shipped SDKs call it by its deprecated, identical name /auth/generateAuthToken, and new code should use /auth/tokens. 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 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 API 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.

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 when writing the frames.

Protocol gotchas

These are the things that bite. The step-by-step consumer walkthrough is in 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 API dashboard, 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 – 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 – 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 – read when the producer app is native iOS or you are wiring an Apple Watch. Apple Developer Program membership is required.
  • references/android.md – read when the producer app is native Android, including ANT+ and programmatic device scans.
  • references/react-native.md – read when the producer app is React Native.
  • references/flutter.md – read when the producer app is Flutter (note the startRealtimeToApp vs startRealtimeToServer split).
  • references/wear-os.md – read when streaming from a Wear OS watch paired to an Android phone.

Full docs: docs.tryterra.co/streaming-api. Ask terra docs ask (or docs_ask over MCP) before fetching a page: it answers from the docs and cites its sources. Append .md to any docs URL for markdown.

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.