Terra API Mobile SDK
Guidance for integrating the Terra API mobile SDK on iOS, Android, React Native, and Flutter. The workflow and every cross-platform gotcha live here in SKILL.md; per-platform setup detail (capabilities, manifest entries, install coordinates, code snippets, platform quirks) lives in references/.
From the terminal
Account configuration lives in the Terra dashboard, which an agent cannot click. The terra CLI does the same from a terminal, and for a mobile integration it supplies the credential your backend holds and confirms that a device actually registered.
Setting up, once. The backend that mints per-user tokens needs a credential of its own:
terra environments list --json dev_id,name # the dev-id the app is built against
terra data-tokens create --env <dev-id> --name mobile --scopes auth:write --reveal
Checking your work, as often as you like. These are read-only and print no credential material:
terra data-tokens list --env <dev-id> --json token_id,name,scopes,expires_at,last_used_at,revoked_at
terra users list --env <dev-id> --provider APPLE --json user_id,reference_id,active
data-tokens list is metadata only, which is what makes it the right check: it says whether a usable token exists and what it is scoped for, without re-showing any bearer. Re-showing one is the separate, keys:read-gated terra data-tokens secret retrieve.
Exercising the mint path by hand. This one is not a read: it mints the short-lived token initConnection() takes, and prints it.
terra data-api /auth/tokens -X POST -q reference_id=<your id>
reference_id is a query parameter here, so it takes -q. Passing it with -d puts it in a JSON body the endpoint does not read, and the call still succeeds: you get back a token that is silently unbound. That matters, because binding is what stops a token attaching a device to the wrong user. Bound, the token is tied to that identifier and redemption ignores whatever the SDK sends. (/auth/generateWidgetSession does take a body, so -d is right there. Check with terra api list --data-api <path> --format json rather than assuming.)
Useful for checking the flow end to end before the backend exists, but it is still a credential on stdout, so keep it out of CI logs.
The reference files and shipped SDK versions call POST /auth/generateAuthToken. That is the deprecated spelling of this endpoint and is identical to it, same parameters and same response, kept indefinitely because released SDKs call that path. Either works; new code should use /auth/tokens.
Reach for terra environments api-key retrieve --reveal only when something genuinely needs the environment's API key. Nothing in the mobile flow does, and it prints the webhook signing secret alongside it.
Never ship the admin token or the environment API key in an app. The SDK takes a short-lived auth token minted per user, and terra data-tokens create --scopes auth:write is the explicitly scoped, revocable credential for the backend that mints them: several coexist per environment, so rotation is mint-new then revoke-old with no cutover.
After a device connects, terra users list --reference-id <ref> says whether the record landed and stayed active, which separates an SDK problem from a permissions one.
Install it with brew install tryterra/tap/terra on macOS or npm install -g @tryterra/cli elsewhere. The terra-cli skill carries the guardrails (--reveal on anything returning a credential, --yes on anything destructive), the exit codes, and a playbook per task. It administers the integration; it does not replace the API calls this skill describes.
When the mobile SDK is the right tool
The mobile SDK exists for one reason: to reach health sources that have no web API. Only three integrations require it:
- Apple Health / HealthKit (iOS)
- Samsung Health (Android)
- Health Connect (Android)
Every other provider (Garmin, Fitbit, Oura, Whoop, Strava, Dexcom, and 500+ more) connects through the Unified API, not the SDK. Google Fit can be read through the SDK via Health Connect, but the web API is the preferred, more reliable route for it. If a provider you need has a web API, use the web API.
Data captured by the SDK flows to the same Data Destination (webhook) as web API data, and connections are managed with the same auth model. If you are building the receiving webhook or storing the health data, that is the terra-unified-api skill's territory; this skill covers the on-device connection.
The six-step workflow
The shape is identical on every platform. Platform differences are in the references/ files.
- Install the SDK and grant native capabilities. Add the package, declare HealthKit/background capabilities (iOS) or manifest permissions and minSDK 28 (Android). See the per-platform reference.
- Initialize the SDK on every app start and every foreground. Create the
TerraManager(iOS/Android) or callinitTerra(RN/Flutter). This call is asynchronous and must complete before any other SDK call. Initializing is a prerequisite for everything else, so do it every time the app opens, not just once per install. - Mint a single-use auth token from YOUR BACKEND.
POST https://api.tryterra.co/v2/auth/generateAuthTokenwith yourdev-idandx-api-keyin the headers. The token is single-use and expires in 3 minutes (the response includesexpires_in), so mint it just-in-time when the user initiates the connection, not at app start. It exists so the connection endpoint cannot be abused. In production, never ship the API key in the client; call this from your server and hand the token to the client through your own channel. During development only, a client-side call (exposing the key) is tolerable. - Open the connection with
initConnection. Pass the connection type, the token, and your permission set. This triggers the OS permission popup. See "Permission popup behavior" below – it is the highest-friction part of the integration. - Validate with
getUserIdon every re-initialization.getUserIdis synchronous and returns theuser_idif the connection is live ornil/nullif not. Call it right after every init; onnil, callinitConnectionagain to reconnect. Re-connecting an already-connected user triggers no popup and does not interrupt the user. - iOS only: enable background delivery. Call
Terra.setUpBackgroundDelivery()in yourAppDelegate'sdidFinishLaunchingWithOptions. Without it, Apple Health data is not pushed automatically.
Deauthenticating a user uses the same endpoint as web integrations: DELETE /auth/deauthenticateUser, called from your backend. See the web API auth reference.
Permission popup behavior (read before shipping)
- The popup fires once. On both Apple Health and Health Connect, the OS shows the permission screen only the first time. Calling
initConnectionagain is a no-op for the popup. It re-triggers only if: (a) you callinitConnectionwith an expanded set ofcustomPermissions, (b) the app is deleted and reinstalled, or (c) – Health Connect only – you request a permission that Google has not approved for your app on release. - The HealthKit popup is a WebView. If your app is itself WebView-based, you must interrupt your WebView, call
initConnection, and reopen your WebView on completion. Otherwise the popup cannot render. getUserIdreturningnilmeans reconnect, not re-popup. Reconnecting a still-authorized user is silent.
Background delivery (iOS)
Terra.setUpBackgroundDelivery() enables automatic Apple Health pushes, but the delivery is constrained:
- Fires at a much lower frequency when the app is killed.
- Fires only when the phone is unlocked.
- Fires only with a network connection.
- Delivers only data types enabled on the Terra Dashboard.
terra unified-api data scopes list --env <dev-id>reads that set without opening it, andterra unified-api data scopes replacechanges it. - If you use
customPermissions, background delivery needs specific per-category permissions enabled: Daily needsSTEPS; Sleep needsSLEEP_ANALYSIS; Body needsBMIandHEART_RATE; Activity needsWORKOUT_TYPE; Nutrition needsNUTRITION_CALORIES.
schedulerOn has no effect on iOS – background delivery is controlled entirely by setUpBackgroundDelivery(). On Android, schedulerOn = true enables Terra API to make scheduled requests while the app is in the foreground.
Cross-platform gotchas
- Samsung Health needs an approved partnership. Terra API has a privileged Samsung partnership that bypasses Health Connect, but you must apply for access. While waiting, develop against Health Connect; switching to Samsung direct later is a version bump with no code changes. Release builds need the ProGuard rule
-keep class com.samsung.android.** { *; }(R8 strips the Samsung classes without it and crashes at runtime), and test devices need Developer Mode enabled in Samsung Health. - Health Connect needs manifest work and Google approval. Your manifest needs a privacy-policy
<activity>with theACTION_SHOW_PERMISSIONS_RATIONALEandVIEW_PERMISSION_USAGEintent-filters. Before going live you must apply to Google for Health Connect data-type access and addtools:node="remove"<uses-permission>lines for every permission you do not use. Seereferences/android.md. setIgnoredSourcesis iOS-only and not persisted. It filters out data from specific source apps in HealthKit (useful when a user has both a cloud Terra API connection and that provider's companion app syncing into Apple Health, which produces duplicates). It resets on every app restart, so call it after every init. On Android it is a no-op.- A new device or reinstall replaces the connection – handle
user_reauth. When an SDK auth arrives with the same(dev-id, reference_id, provider)as an existing connection but a new device identifier (second device, or a reinstall that reset the vendor ID), Terra API issues a newuser_id, deletes every previous connection for that tuple, and sends auser_reauthwebhook carryingold_userandnew_user. Handle that event by migrating your storedold_user.user_idtonew_user.user_id; there is only ever one active connection per(dev-id, reference_id, provider). This replacement only fires when areference_idis supplied – with an emptyreference_idstale connections accumulate instead, one more reason to always setreference_id. postActivityrequiresdevice_dataand iOS 14+. Writing a completed activity fails withoutdevice_datain the payload.
Decisions left to you
These are your calls; the docs do not prescribe them. Make them deliberately:
- Which data types / permissions to request. Empty
customPermissionsdefaults to all available; narrowing reduces the permission surface but you must keep background-delivery categories (above) enabled. - Immediate vs deferred HealthKit prompting.
requestPermissions: falseonTerra.instancerecognizes returning users silently and lets you gate the popup on an explicit user action. Seereferences/ios.md. - Device-switch handling. A second device or reinstall replaces the existing connection and fires
user_reauth(see above), so decide whether to let any device connect (and migrateuser_ids onuser_reauth) or gateinitConnectionto a single primary device. - Samsung direct vs Health Connect as your Android route (and whether to start on Health Connect while the Samsung partnership is pending).
- Backfill approach. The
getDaily/getSleep/etc. getters accepttoWebhook: true(push to your destination) ortoWebhook: false(return the payload to the client callback). - How your backend hands the auth token to the client, and your
reference_idscheme (this is the join key in every webhook).
Platform references
Read the file for the platform you are building on:
references/ios.md– read when integrating TerraiOS natively (Swift): capabilities, info.plist keys,Terra.instance, deferred prompting,setUpBackgroundDelivery,setIgnoredSources, writing data, planned-workout WorkoutKit sync.references/android.md– read when integrating TerraAndroid natively (Kotlin):build.gradlecoordinates, minSDK 28, Samsung vs Health Connect setup, ProGuard, manifest intent-filters,startIntent(deprecated, always null).references/react-native.md– read when integrating terra-react: install, iOS + Android native setup,initTerra/initConnection, the AppDelegate background-delivery edit,postActivity(Apple Health only).references/flutter.md– read when integrating terra_flutter_bridge:flutter pub, native setup,TerraFlutterAPI, AppDelegate background delivery, getters.
Full docs: docs.tryterra.co (append .md to any docs URL for a markdown version). Where a signature or enum is not shown here, see the SDK reference linked from docs.tryterra.co. If the terra-docs MCP server (https://docs.tryterra.co/~gitbook/mcp) is connected, use its tools to search and fetch the docs instead.