iOS (Swift)
Connections
The iOS SDK supports the following connection types:
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 asreference_idin 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 totruefor backwards compatibility. Set tofalsewhen you want the user to be recognized silently on launch and defer the permission prompt to an explicit user action — callrequestHealthKitPermissionslater 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,TerraManagerwill be nil andTerraErrorwill describe the issue.
This function requires network connectivity. It will fail with TerraError.NoInternet if the device is offline.
Possible errors:
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:
The Terra backend has an existing Apple Health connection for this
(device, referenceId)pair (e.g. a returning user).requestPermissionsistrue(default).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:
Activity, Sleep, Daily
Immediate (on new data)
Body, Nutrition, Menstruation
Hourly
Prerequisites:
Terra.instancemust have been called at least once previously (the SDK uses your stored scopes to determine which data types to observe).Your app's
Info.plistmust includeco.tryterra.data.post.requestin theBGTaskSchedulerPermittedIdentifiersarray.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) -> Void➡trueif the connection is authenticated on the server,falseotherwise (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 totrue. When enabled, new data is automatically sent to your webhook destination. Requires callingTerra.setUpBackgroundDelivery()in your AppDelegate for background delivery to function.completion: @escaping (Bool, TerraError?) -> Void➡ Called when the connection is established (or fails).
Possible errors:
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 thisdevId.completion: @escaping (Bool, TerraError?) -> Void➡ Called when the system authorization sheet completes.success == truemeans the sheet finished without a system error — note that iOS intentionally hides which individual read permissions the user granted or denied.
This method requires an Apple Health user to already be linked — either established in a prior session via initConnection, or recognized on launch by Terra.instance with a known referenceId. If no Apple Health user is linked, it returns (false, TerraError.Unauthenticated) without prompting.
Possible errors:
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:
initConnectionwas never called for this connection typeNo existing user was found during
Terra.instanceinitializationThe 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 toTerra.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
requestPermissions: falsePass 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➡ Whentrue, 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 theDateoverload (notTimeInterval). Defaults tofalse.
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):
Bool➡trueif the request succeeded.Payload?➡ WhentoWebhook: true, contains only areferencestring. WhentoWebhook: false, contains the full normalized data.nilon 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
TerraActivityDataobject. Required fields:metadata.start_time,metadata.end_time, and at least one field indevice_data. Optional:distance_data.summary.distance_meters,calories_data.net_activity_calories.completion:
Boolfor 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
TerraNutritionDataobject withmetadata.start_time/end_timeand nutrition values insummary.macros/summary.micros. Supports 33+ nutrient types.completion:
Boolonly (noTerraError). Returnsfalseif 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
TerraBodyDataobject with measurement samples.completion:
Boolonly (noTerraError). Returnsfalseif dates are invalid or HealthKit save fails.
Last updated
Was this helpful?