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

`WORKOUT_TYPES`, `ACTIVITY_SUMMARY`, `LOCATION`, `CALORIES`, `STEPS`, `HEART_RATE`, `HEART_RATE_VARIABILITY`, `VO2MAX`, `HEIGHT`, `ACTIVE_DURATIONS`, `WEIGHT`, `FLIGHTS_CLIMBED`, `BMI`, `BODY_FAT`, `EXERCISE_DISTANCE`, `GENDER`, `DATE_OF_BIRTH`, `BASAL_ENERGY_BURNED`, `SWIMMING_SUMMARY`, `RESTING_HEART_RATE`, `BLOOD_PRESSURE`, `BLOOD_GLUCOSE`, `BODY_TEMPERATURE`, `MINDFULNESS`, `LEAN_BODY_MASS`, `OXYGEN_SATURATION`, `SLEEP_ANALYSIS`, `RESPIRATORY_RATE`, `NUTRITION_SODIUM`, `NUTRITION_PROTEIN`, `NUTRITION_CARBOHYDRATES`, `NUTRITION_FIBRE`, `NUTRITION_FAT_TOTAL`, `NUTRITION_SUGAR`, `NUTRITION_VITAMIN_C`, `NUTRITION_VITAMIN_A`, `NUTRITION_CALORIES`, `NUTRITION_WATER`, `NUTRITION_CHOLESTEROL`, `MENSTRUATION`, `INTERBEAT`, `SPEED`, `POWER`, `ELECTROCARDIOGRAM`

## **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).

```swift
Terra.instance(devId: String,
               referenceId: String?,
               requestPermissions: Bool = true,
               completion: @escaping (TerraManager?, TerraError?) -> Void)
```

* `devId: String` ➡ Your developer ID from the [Terra Dashboard](https://dashboard.tryterra.co).
* `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`](#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.

{% hint style="warning" %}
This function requires network connectivity. It will fail with `TerraError.NoInternet` if the device is offline.
{% endhint %}

**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) |

{% hint style="info" %}
**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](#user-identity-and-prompting-behavior) section below for the full matrix across device, install, and `referenceId` combinations.
{% endhint %}

## 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`.

```swift
Terra.setUpBackgroundDelivery()
```

**Delivery frequency by data type:**

| Data type                     | Frequency               |
| ----------------------------- | ----------------------- |
| Activity, Sleep, Daily        | Immediate (on new data) |
| Body, Nutrition, Menstruation | Hourly                  |

{% hint style="info" %}
**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.
  {% endhint %}

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.

```swift
Terra.checkAuthentication(connection: Connections,
                          devId: String,
                          completion: @escaping (Bool) -> Void)
```

* `connection: Connections` ➡ The connection type to check.
* `devId: String` ➡ Your developer ID.
* `completion: @escaping (Bool) -> Void` ➡ `true` 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.

```swift
Terra.setIgnoredSources(_ ignored: [String])
```

* `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.

{% hint style="info" %}

* 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*.
  {% endhint %}

## **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.

```swift
func initConnection(type: Connections,
               token: String,
               customReadTypes: Set<CustomPermissions> = [],
               schedulerOn: Bool = true,
               completion: @escaping (Bool, TerraError?) -> Void = { _, _ in })
```

* `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](https://docs.tryterra.co/reference/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)`](#terra.instance) and want to defer the prompt until a specific user action (e.g. the user tapping "Connect Apple Health" in your UI).

```swift
func requestHealthKitPermissions(customReadTypes: Set<CustomPermissions> = [],
                                 completion: @escaping (Bool, TerraError?) -> Void = { _, _ in })
```

* (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.

{% hint style="warning" %}
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.
{% endhint %}

**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:**

```swift
// On app launch — recognize the returning user silently, no prompt.
Terra.instance(devId: "<DEV_ID>", referenceId: currentUserRefId, requestPermissions: false) { manager, error in
    // manager is ready to use; HealthKit has not been prompted.
}

// Later, when the user explicitly opts in:
manager.requestHealthKitPermissions { success, error in
    // Sheet shown (if iOS hasn't already determined the perms) and dismissed.
}
```

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

```swift
func getUserId(type: Connections) -> String?
```

* `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_id`s** under the **same `referenceId`**. Webhook payloads include both fields:

```json
{
  "user": {
    "user_id": "<terra-user-id>",
    "reference_id": "<your-user-id>"
  }
}
```

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.

{% hint style="info" %}
**`toWebhook` behavior:**
{% endhint %}

| `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**                 |

{% hint style="info" %}
Both paths require network connectivity.
{% endhint %}

{% hint style="info" %}
**`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.
{% endhint %}

**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.).

```swift
func getActivity(type: Connections,
         startDate: Date,
         endDate: Date,
         toWebhook: Bool = true,
         withSamples: Bool? = nil,
         completion: @escaping (Bool, TerraActivityDataPayloadModel?, TerraError?) -> Void = { _, _, _ in })
```

### **getDaily**

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

```swift
func getDaily(type: Connections,
         startDate: Date,
         endDate: Date,
         toWebhook: Bool = true,
         withSamples: Bool? = nil,
         completion: @escaping (Bool, TerraDailyDataPayloadModel?, TerraError?) -> Void = { _, _, _ in })
```

### getSleep

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

```swift
func getSleep(type: Connections,
         startDate: Date,
         endDate: Date,
         toWebhook: Bool = true,
         withSamples: Bool? = nil,
         completion: @escaping (Bool, TerraSleepDataPayloadModel?, TerraError?) -> Void = { _, _, _ in })
```

### getBody

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

```swift
func getBody(type: Connections,
         startDate: Date,
         endDate: Date,
         toWebhook: Bool = true,
         latestReading: Bool = false,
         withSamples: Bool? = nil,
         completion: @escaping (Bool, TerraBodyDataPayloadModel?, TerraError?) -> Void = { _, _, _ in })
```

* `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).

```swift
func getNutrition(type: Connections,
         startDate: Date,
         endDate: Date,
         toWebhook: Bool = true,
         withSamples: Bool? = nil,
         completion: @escaping (Bool, TerraNutritionDataPayloadModel?, TerraError?) -> Void = { _, _, _ in })
```

### getMenstruation

Retrieves menstrual cycle data.

```swift
func getMenstruation(type: Connections,
         startDate: Date,
         endDate: Date,
         toWebhook: Bool = true,
         withSamples: Bool? = nil,
         completion: @escaping (Bool, TerraMenstruationDataPayloadModel?, TerraError?) -> Void = { _, _, _ in })
```

### **getAthlete**

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

```swift
func getAthlete(type: Connections,
         toWebhook: Bool = true,
         completion: @escaping (Bool, TerraAthleteDataPayloadModel?, TerraError?) -> Void = { _, _, _ in })
```

**Completion parameters** (same structure for all data getters):

* `Bool` ➡ `true` 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.

```swift
func subscribe(forDataTypes: Set<DataTypes>,
                completion: @escaping(Bool, TerraError?) -> Void)
```

* `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:**

```swift
Terra.updateHandler = { (type: DataTypes, update: Update) -> Void in
    // type: which data type triggered the update
    // update.lastUpdated: Date? — when the previous update was recorded
    // update.samples: [TerraData] — array of new data points
    //   each TerraData has: value (Double), timestamp (Date)
}
```

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.

```swift
@available(iOS 14, *)
func postActivity(
    type: Connections,
    payload: TerraActivityData,
    completion: @escaping (Bool, TerraError?) -> Void = { _, _ in }
)
```

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

```swift
func postNutrition(
    type: Connections,
    payload: TerraNutritionData,
    completion: @escaping (Bool) -> Void = { _ in }
)
```

* **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.).

```swift
func postBody(
    type: Connections,
    payload: TerraBodyData,
    completion: @escaping (Bool) -> Void = { _ in }
)
```

* **payload**: A `TerraBodyData` object with measurement samples.
* **completion**: `Bool` only (no `TerraError`). Returns `false` if dates are invalid or HealthKit save fails.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.tryterra.co/reference/health-and-fitness-api/sdk-references/ios-swift.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
