Terra Docs
Dashboard
  • Docs
  • API Reference
  • Changelog
  • Get started ⚡️ Choose your integration
  • Health & Fitness API
    • Overview
    • Set up your Integrations
    • Connect a User
    • Receive data updates
    • Request Historical data
    • Write data
    • Debugging FAQ
    • Mobile-Only Sources: Apple, Samsung, Google Fit
      • iOS (Swift)
      • Android (Kotlin)
      • React Native
      • Flutter
  • Streaming API
    • Getting Started
    • Wearable -> Your app
      • iOS (Swift)
      • Android
    • Your app -> Terra
      • iOS (Swift)
      • Android
    • Terra -> Your Backend
  • Teams API
  • Biomarkers API - Upcoming
Powered by GitBook
On this page
  • Overview
  • 1. Install the SDK
  • 2. Initialize the SDK
  • 3. Connect an Apple Health user
  • 4. Validate the Connection
  • 5. Enable Background Delivery
  • 📱 App Example
  • Disconnecting a user
  • Historical Data retrieval
  • Writing Data

Was this helpful?

  1. Health & Fitness API
  2. Mobile-Only Sources: Apple, Samsung, Google Fit

iOS (Swift)

The iOS SDK allows you to seamlessly send data from iOS to your backend or to your app directly!

Last updated 9 days ago

Was this helpful?

Overview

This guide will walk you through the necessary steps to integrate the Terra Mobile SDK with Apple Health in your iOS app. It covers everything from SDK initialization, user connection, permission handling, and background data updates.

The TerraiOS SDK supports the following integrations:

  • Apple Health

1. Install the SDK

  1. Add https://github.com/tryterra/TerraiOS as a package dependency to your Xcode project.

  2. Add Capabilities:

    1. Healthkit > Healthkit Background Delivery

    2. Background Modes > Background processing

    3. Background modes > Background fetch

  3. In your info.plist, add the following:

Key
Value

Privacy - Health Share Usage Description

Description of how Health data is used

(Min 3 words)

Privacy - Health Records Usage Description

Description of how Health data is used

(Min 3 words)

Privacy - Health Update Usage Description

Description of how Health data is used

(Min 3 words)

Permitted background task scheduler

co.tryterra.data.post.request


2. Initialize the SDK

The first step is to initialize the Terra SDK. This is done by creating a TerraManager instance which allows you to interact with the Terra SDK.

The initialization only needs to be done once on app start, (e.g. in your ContentView.swift or an equivalent file), and ideally, whenever the app is brought into the foreground. This ensures that the SDK is properly set up and ready to be used.

Step 1: Import TerraiOS

In your Xcode project, you should now be able to import Terra SDK

ContentView.swift
import TerraiOS

Step 2: Get a TerraManager Instance

In order to interact with the SDK, you need a TerraManager instance.

Call Terra.instance() with the following arguments:

  • devId: Your Developer ID provided by Terra.

  • referenceId: An ID of your choice to identify your app user.

  • completion : Callback with a TerraManager or an instance TerraError

ContentView.swift
import TerraiOS

var terra: TerraManager

Terra.instance(devId: "<YOUR_DEV_ID>", referenceId: "<REFERENCE_ID>", completion: { manager, error in
    guard error == nil else {
        fatalError("🤯: \(error?.localizedDescription)")
    }
    
    // TerraManager instance to access the SDK functions
    terra = manager
})

The TerraManager instance is thereafter ready to be used to call SDK functions!

(N.B This call is asynchronous, please ensure this is complete before using the manager).


3. Connect an Apple Health user

This method triggers the Apple Health permission popup and establishes the connection.

  • type: Specify the connection type as .APPLE_HEALTH to connect the Apple Health account.

  • token: A one-time authentication token generated from your backend server using the /auth/generateAuthToken endpoint. This ensures secure communication between your app and the Terra servers.

  • customReadTypes: A set of permissions that define what data you want to request from Apple Health (e.g., heart rate, steps). If empty, it defaults to all available permissions.

  • schedulerOn: Defaults the Background Delivery option to true. This will make Terra send new data from the provider to your data destination (e.g. webhook) automatically.

UserConnectionManager.swift
// Prepare the arguments
let token = "<AUTH_TOKEN>" // replace with the token generated from your backend with Terra
let customPermissions: Set<CustomPermissions> = [] // customize permissions if needed
let schedulerOn = true // enables background delivery

// Start a connection to Apple Health
terra.initConnection(
    type: .APPLE_HEALTH, 
    token: token, 
    customReadTypes: customPermissions, 
    schedulerOn: schedulerOn
) { success, error in
    guard error == nil && !success else {
        // Handle error correctly        
        fatalError("🤯: \(error?.localizedDescription)")
    }
}

After a successful call of initConnection() with a valid token, an Apple Permission screen will pop up!

Apple Health Kit Permission Screen: initConnection()

Apple Health only shows the permission popup once, so calling initConnection() multiple times won’t trigger the popup again unless:

a. you call initConnection with an expanded set of customPermissions

b. the app is deleted & reinstalled.

Apple Health Kit Permission Screen: WebViews 🚧

  • Apple HealthKit implements the permissions popup as a WebView.

  • If your app is also based on a WebView, you will need to interrupt your WebView, call initConnection, then upon completion re-open your WebView.

4. Validate the Connection


5. Enable Background Delivery

  1. Go to your AppDelegate's didFinishLaunchingWithOptions function.

This will ensure you get updates for the user's Apple Health data automatically sent to your destination.

AppDelegate.swift
import UIKit
import TerraiOS

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(
      _ application: UIApplication, 
      didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        Terra.setUpBackgroundDelivery()
        return true
    }
}

Now you'll start receiving health data events automatically to your Data Destination (e.g. webhook)!

You can also request historical data to backfill, to verify that data exists, or as a fallback.


📱 App Example

  1. Go to the main entry point of your app, and initialize the TerraiOS SDK.

ContentView.swift
import SwiftUI
import TerraiOS

// Main entry point for the app
struct ContentView: View {
    
    @State private var terra: TerraManager?

    // Function to initialise the Terra SDK once here for the whole project (async)
    private func initializeTerraSDK(_ completion: @escaping () -> Void) {
        Terra.instance(devId: "YOUR_DEV_ID", referenceId: "<YOUR REFERENCE ID>") { manager, error in
            guard let manager = manager, error == nil  else {
                fatalError("Error initialising Terra SDK: \(error?.localizedDescription)")
            }
            terra = manager   // TerraManager initialised!
            completion() 
        }
    }

    // Function to ensure the user is connected to Apple Health or restablish it.
    private func ensureUserConnection() {
        guard let terra = terra else { return }
        connectUser(terra, "AUTH TOKEN") {
            // User connection ensured!
            // Continue here if more processing needed...
        }
    }

    var body: some View {
        Text("Welcome to my App!")
            .onAppear {
                // initialise SDK and ensure user is connected
                initializeTerraSDK(ensureUserConnection)
            }
    }
}
  1. Define the core functions of the SDK that you need to use, e.g. getUserId(), intiConnection()

UserConnectionManager.swift
import TerraiOS

// connect the user to Apple Health (triggers permission popup if not already connected)
func connectUser(_ terra: TerraManager, _ token: String, _ completion: @escaping () -> Void) {
    // check if the user is already connected (synchronous)
    guard terra.getUserId(type: .APPLE_HEALTH) == nil else {
        // User is connected, we can move forward
        completion()
        return
    }
    
    // trigger the permission screen if user is not connected (asynchronous)
    terra.initConnection(type: .APPLE_HEALTH, token: token) {success, error in
        guard success && error == nil else {
            fatalError("🤯: \(error?.localizedDescription)")
        }
        // Apple Health user connected!
        completion()
    }
}

Disconnecting a user


Historical Data retrieval

Swift
terra.getDaily(type: Connections.APPLE_HEALTH, startDate: startDate, endDate: endDate, toWebhook: false) {
  success, payload, err in
    guard err == nil && success else {
      fatalError("🤯: \(err.localizedDescription)")
    }
    // Data is stored in `payload`
    print(payload)
}

The getter functions are asynchronous, so include a callback as in the example above.


Writing Data

You may write other types of data into Apple Health through one of the helper functions in the SDK

device_data must be passed in for postActivity to succeed.

Swift
import TerraiOS

// Function to post an activity to Terra
func postActivityData(terraManager: TerraManager, _ completion: @escaping () -> Void) {

    // Create a sample TerraActivityData payload
    let activityData = TerraActivityData(
        metadata: TerraActivityMetaData(
            name: "Morning Run",
            start_time: Date().addingTimeInterval(-3600).terraString, // 1 hour ago
            end_time: Date().terraString, // Now
            type: TerraActivityType.RUNNING.rawValue
        ),
        calories_data: TerraCaloriesData(total_burned_calories: 600.0),
        heart_rate_data: TerraHeartRateData(
            summary: TerraHeartRateSummaryData(
                avg_hr_bpm: 140.0,
                max_hr_bpm: 180.0
            )
        ),
        device_data: TerraDeviceData(
            name: "Terra"
        ),
        distance_data: TerraActivityDistanceData(
            summary: TerraDistanceSummaryData(distance_meters: 5000.0) // 5 km
        )
    )

    // Post the activity data using the `postActivity` function
    terraManager.postActivity(type: .APPLE_HEALTH, payload: activityData) { success, error in
        guard success && error == nil else {
            fatalError("🤯: \(error.localizedDescription)")
        }
        
        // Activity posted!    
        print("Activity posted successfully!")
        completion()
    }
}
Swift
import TerraiOS

// Function to post body data to Terra
func postBodyData(terra: TerraManager, _ completion: @escaping () -> Void) {
    // Create body data (e.g., body measurements and biometrics)
    let bodyMetaData = TerraBodyMetaData(
        start_time: Date().terraString, // Start time of the body measurement
        end_time: Date().terraString // End time of the body measurement
    )
    
    let heartRateData = TerraBodyHeartData(
        pulse_wave_velocity_samples: [], // Pulse wave velocity samples (optional)
        heart_rate_data: TerraHeartRateData(summary: TerraHeartRateSummaryData(
            user_max_hr_bpm: 190, // Max HR during the day
            avg_hr_bpm: 70, // Average HR
            resting_hr_bpm: 60 // Resting HR
        )),
        rr_interval_samples: [RRIntervalSample(timestamp: Date().terraString, rr_interval_ms: 850.0, hr_bpm: 70)] // RR Interval data
    )
    
    let bodyData = TerraBodyData(
        metadata: bodyMetaData,
        heart_data: heartRateData,
        oxygen_data: TerraOxygenData(avg_saturation_percentage: 98.0), // Average Oxygen saturation
        glucose_data: TerraGlucoseData(day_avg_blood_glucose_mg_per_dL: 90.0), // Average blood glucose data
        hydration_data: TerraHydrationData(day_total_water_consumption_ml: 2500), // Water consumption in ml
        weight_kg: 70.0 // Body weight in kg
    )
    
    // Post the body data to Apple Health
    terra.postBody(type: .APPLE_HEALTH, payload: bodyData) { success in
        guard success else {
            fatalError("🤯: Error occurred posting body data")
        }
        // Successfully posted body data
    }
Swift
import TerraiOS


// Function to post nutrition data to Terra
func postBodyData(terra: TerraManager, _ completion: @escaping () -> Void) {
    // Create nutrition data (e.g., meals, water consumption, and nutrient intake)
    let nutritionSummary = TerraNutritonSummary(
        macros: TerraMacrosModel(
            protein_g: 100.0, // Protein intake in grams
            carbohydrates_g: 300.0, // Carbohydrates intake in grams
            fat_g: 50.0, // Fat intake in grams
            calories: 2500.0 // Total calorie intake
        ),
        water_ml: 2000.0 // Water intake in milliliters
    )
    
    let meal1 = TerraMealData(
        name: "Breakfast Cereal",
        quantity: TerraQuantityModel(unit: 0, amount: 1.0), // 1 serving
        macros: TerraMacrosModel(protein_g: 30.0, carbohydrates_g: 50.0, fat_g: 15.0, calories: 450.0), // Macro breakdown for breakfast
        micros: TerraMicrosModel(vitamin_C_mg: 60.0, iron_mg: 8.0), // Micro-nutrients
        timestamp: Date().terraString // Timestamp for the meal
    )
    
    let meal2 = TerraMealData(
        name: "Steak and fries",
        quantity: TerraQuantityModel(unit: 0, amount: 1.0), // 1 serving
        macros: TerraMacrosModel(protein_g: 40.0, carbohydrates_g: 80.0, fat_g: 20.0, calories: 600.0), // Macro breakdown for lunch
        micros: TerraMicrosModel(vitamin_D_mg: 10.0, calcium_mg: 300.0), // Micro-nutrients
        timestamp: Date().terraString // Timestamp for the meal
    )
    
    let nutritionData = TerraNutritionData(
        meals: [meal1, meal2], // Add multiple meals
        summary: nutritionSummary // Summary of nutrition data
    )
    
    // Post the nutrition data to Apple Health
    terra.postNutrition(type: .APPLE_HEALTH, payload: nutritionData) { success in
        guard let success else {
            fatalError("🤯: error posting nutrition data")
        }
        // Successfully posted nutrition data
        completion()
    }

Planned Workouts are only available for iOS 17 and above

Swift
import TerraiOS


// Function to post planned workout data to Terra
func postPlannedWorkout(terra: TerraManager, _ completion: @escaping () -> Void) {
    guard #available(iOS 17, *) else {
        // Can't work with iOS < 17.0
        completion()
        return
    }
    
    // Create workout steps with durations and targets
    let durationTime = PlannedWorkoutStepDuration.Time(TimePlannedWorkoutStepDuration(seconds: 600)) // 10 minutes
    let durationDistance = PlannedWorkoutStepDuration.Distance(DistancePlannedWorkoutStepDuration(distance_meters: 2000)) // 2km
    
    let targetSpeed = PlannedWorkoutStepTarget.Speed(SpeedPlannedWorkoutStepTarget(speed_meters_per_second: 2.5)) // 2.5 m/s
    let targetHeartRate = PlannedWorkoutStepTarget.HR(HRPlannedWorkoutStepTarget(hr_bpm_low: 120, hr_bpm_high: 150)) // 120-150 bpm
    
    // Define the first step of the workout (e.g., 10-minute warm-up)
    let warmUpStep = PlannedWorkoutStep(
        order: 1,
        type: .STEP,
        name: "Warm-up",
        description: "Light jogging to warm up",
        intensity: 1, // low intensity
        durations: [durationTime], // 10-minute duration
        targets: [targetHeartRate] // Heart rate target: 120-150 bpm
    )
    
    // Define the second step of the workout (e.g., 2km running at target speed)
    let runningStep = PlannedWorkoutStep(
        order: 2,
        type: .STEP,
        name: "2km Run",
        description: "Steady run with a speed target",
        intensity: 3, // medium intensity
        durations: [durationDistance], // 2km distance
        targets: [targetSpeed] // Speed target: 2.5 m/s
    )
    
    // Metadata for the planned workout
    let workoutMetadata = TerraPlannedWorkoutMetaData(
        id: UUID(), // Unique identifier for the workout
        name: "Morning Run",
        type: .RUNNING, // Activity type: Running
        description: "A planned morning run with warm-up and steady pace",
        estimated_duration_seconds: 1800, // Estimated total duration: 30 minutes
        estimated_distance_meters: 5000, // Estimated total distance: 5km
        estimated_calories: 300, // Estimated calories burned
        planned_date: Date().terraString // Planned date of the workout
    )
    
    // Create the planned workout with steps and metadata
    let plannedWorkout = TerraPlannedWorkout(
        steps: [
            .PlannedWorkoutSteps(warmUpStep),
            .PlannedWorkoutSteps(runningStep)
        ],
        metadata: workoutMetadata
    )
    
    // Post the planned workout for Apple Health
    terra.postPlannedWorkout(type: .APPLE_HEALTH, payload: plannedWorkout) { success, error in
        guard error == nil && success else {
            fatalError("🤯: \(error.localizedDescription)
        }
        // PlanWorkout posted!
        completion()
    }
}

Planned workouts consist of a series of steps. Each step has:

  • Completion criteria (called duration for all intents and purposes). For example:

    • distance (keep going until you cover 500m),

    • energy expenditure (keep going until you burn 200 kcal)

    • heart rate threshold (keep going until your heart rate reaches above 150 bpm)

    • etc..

  • A target to be maintained

    • heart rate (maintain heart rate between certain bounds)

    • speed (maintain speed between certain bounds)

    • grade (inclination above a certain threshold)

    • etc...

  • An associated intensity label

    • Warmup

    • Active

    • Cooldown

    • etc..

  • An exercise type

    • Running

    • Cycling

    • Deadlifting

    • etc...

  • A series of sub-steps

Once written, a planned workout will be available in the user's Workout app on their Apple Watch. They will then be able to follow the workout and receive guidance throughout each step.z

These modalities allow any workout to be described in a series of defined steps for each of its segments.


Find more details in the SDK Reference:

Once you’ve initialized the SDK, you can connect the Apple Health user. This is done by calling the method provided by the TerraManager instance that you created above.

1. Call

From TerraManager, call with the following arguments:

To learn more about these parameters and their different options, check the

To ensure a connection is still valid on the client side, make sure to use the from TerraManager every time you reinitialize the SDK. This function is synchronous and returns a user_id right away if a user is connected or nil if none exists.

Add

Check out the for details about all the functions in the SDK.

In order to disconnect an Apple Health user, you may use , called from your backend.

You can request for historical data using one of the . You may set toWebhook to false if you wish for the callback function to return the data payload on the client side.

: allows you to write a completed activity to a user's Apple Health

: allows you to write a nutrition log to a user's Apple Health

: allows you to write a body measurement to a user's Apple Health

: allows you to write a workout plan to a user's Apple Health

In order to write a completed activity to a user's Apple Health, use the function as below, with the .

In order to write Body Measurement data to a user's Apple Health, use the function as below, with the .

In order to write Nutrition log to a user's Apple Health, use the function as below, with the

In order to write a planned workout for the user to follow on their Apple Watch, use the function as below, with the .

Writing planned workouts allows you to create a workout plan for the user (such as those ).

commonly used with Zwift
postPlannedWorkout
postPlannedWorkout
instance of terra you created above
instance of terra you created above
instance of terra you created above
instance of terra you created above
Terra manager initialization function
initConnection()
initConnection()
initConnection()
iOS SDK Reference.
getUserid()
Terra.setUpBackgroundDelivery()
iOS SDK reference
the same endpoint as for Web API-based integrations
data retrieval functions
postActivity
postNutrition
postBody
postActivity
postBody
postNutrition

iOS Background Delivery Behaviour:

  • Data will still be triggered if the app is killed (with much lower frequency)

  • Data will only be triggered when phone is unlocked

  • Data can only be triggered where there is network connection

  • Only enabled Data Types from will be triggered by background delivery.

the Terra Dashboard

Data Sources requiring the Terra Mobile-SDK

The Mobile SDK is ONLY used to connect to the following integrations:

Apple Health - iOS

Samsung Health - Android

Google Fit - Android. Note: the is the preferred way to connect to Google Fit until its sunsetting on June 30, 2025 due to better reliability

For ALL other integrations, please refer to the

Health & Fitness API
Health & Fitness API

2. Generate an Auth Token

To be able to call the initConnection() method, you need to pass a token as an argument.

This token is a single-use token created to ensure the authentication endpoint for creating a connection (and connecting the SDK to Terra's servers) does not get abused.

Generate the token with this endpoint POSThttps://api.tryterra.co/v2/auth/generateAuthToken . Make sure to call it with your Terra x-api-key and dev-id in the headers from your backend server. After you generate the token, provide the response to your client side using your own logic.

Go to the SDK Reference to find more details on the .

  • During the development phase, it it acceptable to place this call on the client side, exposing your API key in your mobile app.

  • For a production implementation, DO NOT expose your API key this way, and make sure to only make this call from your backend.

Always validate the connection before using the SDK

Check if a user_id exists right after initializing the Terra SDK to see if the connection still exists.

  1. Check if the User is Connected

    1. If the function returns a user ID, the user is still connected, 🎉 keep vibing along!

    2. If the function returns nil, the user needs to reconnect.

  2. Re-connect if Needed If the connection is lost, you can call terra.initConnection() again to re-establish the connection.

Calling terra.initConnection() when the user is already connected or just needs a reconnection will not trigger any permission screens from Apple Health, and the user flow will remain uninterrupted. The connection will be re-established if necessary.

Generate the Mobile SDK Auth Token API Endpoint