iOS (Swift)

Connections

The iOS SDK supports the following connection types:

Value
Description

Connections.APPLE_HEALTH

Apple Health (HealthKit)

CustomPermissions

Use these to request a subset of permissions instead of all available ones. When not specified, all permissions from your developer scopes are requested. The table below shows what each value requests in Apple Health; see Permissions mapping for the full cross-platform reference.

Each CustomPermissions value maps to one or more HealthKit types. When you don't pass customPermissions, the SDK requests every type associated with the data-type groups in your developer scopes — so the HealthKit prompt can show a long list. Pass customPermissions to request a narrower set.

Activity & workouts

CustomPermissions value

HealthKit type(s) requested

WORKOUT_TYPE

Workouts

ACTIVITY_SUMMARY

Activity summary, Stand time, Exercise time

ACTIVE_DURATIONS

Exercise time

CALORIES

Active energy burned

BASAL_ENERGY_BURNED

Resting (basal) energy burned

STEPS

Steps

FLIGHTS_CLIMBED

Flights climbed

EXERCISE_DISTANCE

Distance (walking/running, cycling, swimming, wheelchair)

SWIMMING_SUMMARY

Swimming stroke count

LOCATION

Workout route

SPEED

Walking speed (iOS 14+), running speed (16+), cycling speed & cadence (17+)

POWER

Running power (iOS 16+), cycling power (17+)

MINDFULNESS

Mindful sessions

Heart & cardiovascular

CustomPermissions value

HealthKit type(s) requested

HEART_RATE

Heart rate

RESTING_HEART_RATE

Resting heart rate

HEART_RATE_VARIABILITY

Heart rate variability (SDNN)

INTERBEAT

Beat-to-beat measurements

ELECTROCARDIOGRAM

Electrocardiograms (iOS 14+)

VO2MAX

VO₂ max

BLOOD_PRESSURE

Blood pressure (systolic & diastolic)

Vitals

CustomPermissions value

HealthKit type(s) requested

RESPIRATORY_RATE

Respiratory rate

OXYGEN_SATURATION

Blood oxygen

BLOOD_GLUCOSE

Blood glucose

BODY_TEMPERATURE

Body temperature (+ wrist temperature on iOS 16+)

Body measurements

CustomPermissions value

HealthKit type(s) requested

HEIGHT

Height

WEIGHT

Body mass

BMI

Body mass index

BODY_FAT

Body fat percentage

LEAN_BODY_MASS

Lean body mass

Sleep

CustomPermissions value

HealthKit type(s) requested

SLEEP_ANALYSIS

Sleep analysis

Nutrition

CustomPermissions value

HealthKit type(s) requested

NUTRITION_CALORIES

Dietary energy consumed

NUTRITION_PROTEIN

Dietary protein

NUTRITION_CARBOHYDRATES

Dietary carbohydrates

NUTRITION_FAT_TOTAL

Dietary total fat

NUTRITION_FIBRE

Dietary fiber

NUTRITION_SUGAR

Dietary sugar

NUTRITION_SODIUM

Dietary sodium

NUTRITION_CHOLESTEROL

Dietary cholesterol

NUTRITION_VITAMIN_C

Dietary vitamin C

NUTRITION_VITAMIN_A

Dietary vitamin A

NUTRITION_WATER

Dietary water

Reproductive health

CustomPermissions value

HealthKit type(s) requested

MENSTRUATION

Menstrual flow

Profile

CustomPermissions value

HealthKit type(s) requested

GENDER

Biological sex

DATE_OF_BIRTH

Date of birth

Symptoms

CustomPermissions value

HealthKit type(s) requested

SYMPTOM_COUGHING

Coughing (iOS 13.6+)

SYMPTOM_FEVER

Fever (iOS 13.6+)

SYMPTOM_SORE_THROAT

Sore throat (iOS 13.6+)

Requesting a data-type group (rather than customPermissions) requests a broad superset of types. For example, the Body group requests heart rate, HRV, VO₂ max, glucose, blood pressure, body temperature, ECG and more — not just body-composition metrics. Use customPermissions when you want the HealthKit prompt to show only specific toggles.

Initialization

Terra.instance

Creates and authenticates a TerraManager instance. This makes a network call to Terra's servers to validate your developer ID and retrieve your account configuration.

If an existing Apple Health user is found for this device + referenceId, the SDK will automatically reconnect. By default this also triggers the HealthKit authorization flow, which may display the HealthKit permission popup if iOS has no stored authorization for your app's requested data types (e.g. after a reinstall that revoked HealthKit permissions).

  • devId: String ➡ Your developer ID from the Terra Dashboard.

  • referenceId: String? ➡ An identifier for your app's user. This value appears as reference_id in webhook payloads and API responses, allowing you to map Terra users back to your own user system.

  • (Optional) requestPermissions: Bool ➡ Controls whether the HealthKit permission popup is presented automatically when an existing Apple Health user is reconnected. Defaults to true for backwards compatibility. Set to false when you want the user to be recognized silently on launch and defer the permission prompt to an explicit user action — call requestHealthKitPermissions later to trigger it. Has no effect when no existing Apple Health user is found for this device/referenceId, because there is nothing to prompt against in that case.

  • completion: @escaping (TerraManager?, TerraError?) -> Void ➡ Called when initialization completes. You must wait for this callback before calling any other SDK function. On failure, TerraManager will be nil and TerraError will describe the issue.

Possible errors:

Error
Cause

TerraError.InvalidDevID

The devId is not recognized by Terra

TerraError.NoInternet

Network request failed

TerraError.HealthKitUnavailable

Device does not support HealthKit (e.g. iPad)

When does Terra.instance trigger the HealthKit prompt?

The prompt only appears when all three of these are true:

  1. The Terra backend has an existing Apple Health connection for this (device, referenceId) pair (e.g. a returning user).

  2. requestPermissions is true (default).

  3. iOS has no stored authorization decision for the requested types — typically after the user revokes permissions in Settings, or after a reinstall where the device's vendor ID was retained (iOS clears HealthKit authorization on uninstall).

If any of these is false, no prompt is shown. See the User identity & prompting behavior section below for the full matrix across device, install, and referenceId combinations.

Static methods on Terra

setUpBackgroundDelivery

Registers HealthKit observer queries and a BGTaskScheduler task to automatically send new health data to your webhook destination, even when your app is in the background.

This is a static method on the Terra class (not on TerraManager). It must be called in your AppDelegate's didFinishLaunchingWithOptions.

Delivery frequency by data type:

Data type
Frequency

Activity, Sleep, Daily

Immediate (on new data)

Body, Nutrition, Menstruation

Hourly

Prerequisites:

  • Terra.instance must have been called at least once previously (the SDK uses your stored scopes to determine which data types to observe).

  • Your app's Info.plist must include co.tryterra.data.post.request in the BGTaskSchedulerPermittedIdentifiers array.

  • HealthKit Background Delivery and Background Processing capabilities must be enabled in Xcode.

If a data post fails due to no network, the SDK caches the payload and retries via BGTaskScheduler when connectivity is restored.

checkAuthentication

Checks whether a connection is authenticated by making a network call to Terra's servers.

  • connection: Connections ➡ The connection type to check.

  • devId: String ➡ Your developer ID.

  • completion: @escaping (Bool) -> Voidtrue if the connection is authenticated on the server, false otherwise (including network failures).

setIgnoredSources

Filters out health data from specific apps when reading from Apple Health. Use this to prevent double-counting when a user connects a data source both through Terra's API (e.g. WHOOP, Garmin) and has that same app syncing into Apple Health.

  • ignored: [String] ➡ An array of app bundle identifiers to exclude (e.g. ["com.whoop.app", "com.garmin.connect.mobile"]). Any HealthKit samples originating from these apps will be filtered out of all data reads.

  • Can be called at any time — takes effect on the next data fetch (including background delivery).

  • Not persisted across app launches — call this on every app start if needed.

  • Does not affect which data sources can write to HealthKit, only what the Terra SDK reads.

TerraManager Instance methods

All methods below are called on the TerraManager instance returned by Terra.instance.

Connection setup/management

initConnection

Authenticates a new user connection with Terra's servers and triggers the platform permission dialog. This makes a network call and, for Apple Health, shows the HealthKit permission popup.

This function should only be called once per user/connection type. On subsequent app launches, Terra.instance will automatically reconnect existing users.

  • type: Connections ➡ The connection type to initialize (e.g. .APPLE_HEALTH).

  • token: String ➡ A single-use authentication token generated from your backend server via the Generate Authentication Token endpoint. Each token can only be used once.

  • (Optional) customReadTypes: Set<CustomPermissions> ➡ Customize which permissions appear in the HealthKit popup. When empty, defaults to all permissions from your developer scopes.

  • schedulerOn: Bool ➡ Enables background delivery of health data. Defaults to true. When enabled, new data is automatically sent to your webhook destination. Requires calling Terra.setUpBackgroundDelivery() in your AppDelegate for background delivery to function.

  • completion: @escaping (Bool, TerraError?) -> Void ➡ Called when the connection is established (or fails).

Possible errors:

Error
Cause

TerraError.InvalidDevID

Developer ID not recognized

TerraError.InvalidToken

Token is invalid or already used

TerraError.UserLimitsReached

Your plan's user limit has been reached

TerraError.NoInternet

Network request failed

TerraError.HealthKitUnavailable

Device does not support HealthKit

requestHealthKitPermissions

Explicitly presents the iOS HealthKit permission popup for an Apple Health connection that has already been established. Use this when you initialized the SDK with Terra.instance(..., requestPermissions: false) and want to defer the prompt until a specific user action (e.g. the user tapping "Connect Apple Health" in your UI).

  • (Optional) customReadTypes: Set<CustomPermissions> ➡ Overrides the set of HealthKit read scopes to request. When empty, the SDK uses the most recently persisted read types, falling back to the scopes registered on the dashboard for this devId.

  • completion: @escaping (Bool, TerraError?) -> Void ➡ Called when the system authorization sheet completes. success == true means the sheet finished without a system error — note that iOS intentionally hides which individual read permissions the user granted or denied.

Possible errors:

Error
Cause

TerraError.HealthKitUnavailable

Device does not support HealthKit

TerraError.Unauthenticated

No Apple Health user is linked — call initConnection first, or Terra.instance with a referenceId that has an existing connection

TerraError.HealthKitAuthorizationError

The HealthKit authorization request returned a system-level error (e.g. restricted entitlements)

Typical usage pattern for deferred prompting:

getUserId

Returns the Terra user ID for a connection, or nil if no connection exists. This is a synchronous, local read with no network call.

The returned user_id is the same identifier used in webhook payloads, API requests, and the Terra dashboard.

  • type: Connections ➡ The connection to retrieve the user ID for.

Returns nil when:

  • initConnection was never called for this connection type

  • No existing user was found during Terra.instance initialization

  • The connection failed

User identity and prompting behavior

The Apple Health integration identifies a user by two values together:

  • Device identifier — derived from UIDevice.current.identifierForVendor. Persists across installs only if another app from the same vendor remains installed on the device.

  • referenceId — the value you pass to Terra.instance(...).

The Terra backend keys every Apple Health connection on the tuple (deviceId, devId, referenceId). This has important implications when any of those values change.

Scenarios

Scenario

Does Terra.instance find an existing user?

Does Terra.instance trigger the HealthKit prompt (default requestPermissions: true)?

Resulting Terra user_id

Same device identifier, same referenceId

Yes

Only if iOS has no stored authorization (user revoked in Settings, or reinstall where vendor ID persisted)

Same user_id as previous sessions

Same device identifier, different referenceId

No

No — nothing to prompt against

A new user_id is created when you call initConnection

Different device (e.g. iPhone → iPad), same referenceId

No

No — nothing to prompt against

A new user_id is created when you call initConnection

Reinstall where the device's vendor ID resets (common when your app is the only one from your vendor), same referenceId

No

No — the device now looks new to the backend

A new user_id is created when you call initConnection

Multi-device and reinstall dedup

A single human user connecting Apple Health from multiple devices (e.g. iPhone and iPad) or across a reinstall that reset the vendor ID produces multiple Terra user_ids under the same referenceId. Webhook payloads include both fields:

Deduplicate data on your side by grouping on reference_id. Terra does not automatically link connections that share a reference_id but differ on device identifier.

When to use requestPermissions: false

Pass requestPermissions: false to Terra.instance when you want returning users to be recognized silently on launch and the HealthKit prompt to be triggered only by an explicit user action in your UI. The flag only materially changes behavior in the narrow case where the backend finds an existing connection for this (device, referenceId) but iOS has no stored HealthKit authorization for your app (typically a reinstall where the vendor ID persisted and iOS cleared the authorization).

In the multi-device and vendor-ID-reset reinstall scenarios, the flag is a no-op because the backend returns no existing connection — there is no auto-prompt to suppress. In those cases, gate your call to initConnection behind the user's explicit opt-in to control when the prompt appears.

Data retrieval

All data retrieval functions make network calls — even with toWebhook: false, the SDK sends data to Terra's normalization servers and returns the normalized result.

Each function accepts dates as either Date or TimeInterval (Unix timestamp). Each also accepts an optional withSamples: Bool? = nil parameter.

toWebhook behavior:

Both paths require network connectivity.

toWebhook

What happens

Completion returns

true (default)

Data is fetched from the health source and sent to your webhook destination

A reference ID string only (not the data itself)

false

Data is fetched, sent to Terra for normalization, and the normalized payload is returned locally

The full normalized data payload

withSamples parameter: Controls whether the server includes granular time-series sample arrays in the response. When nil (default), the server uses its default behavior. Set to true to explicitly request samples, or false to exclude them for smaller payloads.

Date ranges over 31 days are automatically chunked into weekly segments and processed serially.

getActivity

Retrieves workout and activity data (exercise sessions, mindfulness sessions, heart rate during workouts, GPS routes, etc.).

getDaily

Retrieves daily summary data (total steps, calories, distance, resting heart rate, HRV, floors climbed, etc.).

getSleep

Retrieves sleep session data (sleep stages, duration, heart rate during sleep, HRV, SpO2, respiratory rate).

getBody

Retrieves body measurement data (weight, height, BMI, body fat, heart rate, HRV, blood pressure, blood glucose, SpO2, body temperature, ECG).

  • latestReading: Bool ➡ When true, ignores the date range and returns only the single most recent reading for each body metric (weight, height, BMI, etc.). Heart rate and HRV samples still use the date range. Only available on the Date overload (not TimeInterval). Defaults to false.

getNutrition

Retrieves nutrition and meal data (macronutrients, micronutrients, water intake, individual meals).

getMenstruation

Retrieves menstrual cycle data.

getAthlete

Retrieves the user's athlete profile (biographical data, no date range needed).

Completion parameters (same structure for all data getters):

  • Booltrue if the request succeeded.

  • Payload? ➡ When toWebhook: true, contains only a reference string. When toWebhook: false, contains the full normalized data. nil on failure.

  • TerraError? ➡ Describes the error if the request failed. Possible values: TerraError.Unauthenticated (no connection for this type), TerraError.UnsupportedResource (unsupported data type for this connection).

subscribe

Registers for real-time data updates via HealthKit anchored object queries. When new data arrives, the Terra.updateHandler closure is called with the new samples.

  • forDataTypes: Set<DataTypes> ➡ Data types to subscribe to. Available values: STEPS, HEART_RATE, HEART_RATE_VARIABILITY, CALORIES, DISTANCE.

Before calling subscribe, you must set the update handler:

Set this in your AppDelegate's didFinishLaunchingWithOptions so it persists across app launches. The handler is called both for real-time updates and for catching up on data that arrived while the app was closed.

Writing data

These functions write data directly into Apple Health. No network calls are made — data is written to the local HealthKit store.

postActivity

Writes a workout to Apple Health.

  • type: The connection type (e.g. .APPLE_HEALTH).

  • payload: A TerraActivityData object. Required fields: metadata.start_time, metadata.end_time, and at least one field in device_data. Optional: distance_data.summary.distance_meters, calories_data.net_activity_calories.

  • completion: Bool for success, TerraError? for errors (e.g. StartTimeCannotBeNil, InvalidDateFormat, InvalidDevice, PermissionsDenied).

postNutrition

Writes nutrition data (a food entry with macros/micros) to Apple Health.

  • payload: A TerraNutritionData object with metadata.start_time/end_time and nutrition values in summary.macros/summary.micros. Supports 33+ nutrient types.

  • completion: Bool only (no TerraError). Returns false if dates are invalid, all write permissions are denied, or the HealthKit save fails.

postBody

Writes body measurement data to Apple Health (weight, height, BMI, heart rate, blood pressure, glucose, SpO2, temperature, etc.).

  • payload: A TerraBodyData object with measurement samples.

  • completion: Bool only (no TerraError). Returns false if dates are invalid or HealthKit save fails.

Last updated

Was this helpful?