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
  • Installation
  • Initialize the SDK
  • Connect to Apple Health
  • Enable Background Delivery
  • Validate the Connection
  • Disconnecting a user
  • Historical Data retrieval
  • Writing data
  • Writing Planned Workouts
  • Overview
  • Usage

Was this helpful?

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

iOS (Swift)

Last updated 2 months ago

Was this helpful?

Installation

Initialize the SDK

First, initialize the TerraManager class within your ContentView file.

Terra initialization

Terra should be initialized every time your app is brought into the foreground

This is a necessary prerequisite for other SDK functions to run as expected

To do this, run the Terra manager initialization function as below:

import TerraiOS

var terra: TerraManager
Terra.instance(devId: "YOUR DEV ID", referenceId: "REFERENCE ID"){manager, error in
	terra = manager 
}

Terra is thereafter ready to be used with other functions

Connect to Apple Health

Once Terra is initialized, you can create an Apple Health connection.

initConnection only needs to be run a single time.

Apple Health prohibits the permission popup to appear more than once for any given permission, so calling initConnection more than once will result in no action at all

The only case where it would re-appear is if:

  • you call initConnection with an expanded set of customPermissions

  • the app is deleted & reinstalled.

// Initialize connection with example data
let token = "example_token"
let customPermissions: Set<CustomPermissions> = []
let schedulerOn = true // Enables background delivery to occur

terra.initConnection(
    type: .APPLE_HEALTH, 
    token: token, 
    customReadTypes: customPermissions, 
    schedulerOn: schedulerOn
) { success, error in
    if success {
        print("Connection successful!")
    } else if let error = error {
        print("Connection failed: \(error.message)")
    }
}

customPermissions

🚧 Webviews & HealthKit permissions screen

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

Enable Background Delivery

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

Data will

  • still be sent even if your app is exited/killed completely

  • only be sent if the device is unlocked

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

Validate the Connection

  • if the connection exists, all is good! 🎉 keep vibing along

Disconnecting a user

In order to disconnect an Apple Health user, you may use the same endpoint as for Web API-based integrations, called from your backend

Historical Data retrieval

You can request for historical data using one of the data retrieval functions.

You may set toWebhook to false if you wish for the callback function to return the data payload on the client side.

// e.g.
terra.getDaily(type: Connections.APPLE_HEALTH, startDate: startDate, endDate: endDate, toWebhook: false) {
  succ, payload, err in
    if succ {
      if let d = payload {
        print(d)
      } else {
        print("failed to unpack payload")
      }
    } else {
      print("get failed")
    }
}

Check out the iOS SDK reference for details about all the functions in the SDK

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.

Internal implementation

import TerraiOS

// Function to post an activity to Terra
func postActivityData() {
    // Create an instance of TerraManager
    let terraManager = TerraManager()

    // Define the connection type (e.g., APPLE_HEALTH or FREESTYLE_LIBRE)
    let connectionType: Connections = .APPLE_HEALTH

    // 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: connectionType, payload: activityData) { success, error in
        if success {
            print("Activity posted successfully!")
        } else if let error = error {
            print("Failed to post activity: \(error.localizedDescription)")
        } else {
            print("Failed to post activity for an unknown reason.")
        }
    }
}

// Call the function to post the activity
postActivityData()

import TerraiOS

// 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, payload: bodyData) { success in
    if success {
        print("Body data successfully written to Apple Health.")
    } else {
        print("Failed to write body data.")
    }
}
import TerraiOS

// 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, payload: nutritionData) { success in
    if success {
        print("Nutrition data successfully written to Apple Health.")
    } else {
        print("Failed to write nutrition data.")
    }
}

Writing Planned Workouts

Planned Workouts are only available for iOS 17 and above

Overview

This will typically look like the following example:

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

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

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.

Usage

import TerraiOS

// 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
if #available(iOS 17, *) {
    terra.postPlannedWorkout(type: .APPLE, payload: plannedWorkout) { success, error in
        if success {
            print("Planned workout successfully written to Apple Health.")
        } else {
            if let error = error {
                print("Failed to write planned workout. Error: \(error)")
            } else {
                print("Failed to write planned workout for an unknown reason.")
            }
        }
    }
} else {
    print("iOS 17 is required to write planned workouts.")
}

Run the on the terra instance you created above to have the Apple Health permission screen pop up.

customPermissions is used to customize the permissions list shown in the Apple Health popup when calling . When empty, it defaults to all available permissions

Add to your AppDelegate's didFinishLaunchingWithOptions function.

To ensure a connection is still valid on the client side, use the method. This function is synchronous and returns the user_id right away or nil if none exists.

Always use this method right after to see if the connection still exists,

if you expect the connection to exist, but returns nil, to re-establish it. No permission screen will be shown again and the user flow will be unaffected

if it is expected for the connection to no longer exist, you may allow the user to re-connect Apple Health if they so choose (you'd then call , but no permission screen would be shown)

postActivity uses the class internally, which has been deprecated as of iOS 17.

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

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

HKWorkout
commonly used with Zwift
initializing Terra
postPlannedWorkout
instance of terra you created above
call terra.initConnection
as before

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

In order to generate it, place the call below on your server, and provide it to your client side using your own logic.

Testing & developing

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

Example workout from
https://whatsonzwift.com/workouts/baby-on-board
terra.initConnection
initConnection
Terra.setUpBackgroundDelivery()
terra.getUserid()
terra.getUserid()
terra.initConnection
postActivity
postNutrition
postBody

Generates an authentication token for the Terra mobile SDKs

post

Creates a token to be used with initConnection() functions in the Terra mobile SDKs in order to create a user record for Apple Health or Samsung Health (or equivalent)

Header parameters
dev-idstringRequired

your developer ID

Example: testingTerra
x-api-keystringRequired

your API key

Example: OtHJok60oQmT8zhnUWc4SWBJI7ztPTs88C0gOsJJ
Responses
200
200
application/json
404
404
application/json
post
POST /v2/auth/generateAuthToken HTTP/1.1
Host: api.tryterra.co
dev-id: text
x-api-key: text
Accept: */*
{
    "status": "success",
    "token": "250c68b9c21b78e40e7a3285a2d538d3bc24aabd3b4c76a782fb0a571ca4501d",
    "expires_in": 180
}

Summary:

  1. Add https://github.com/tryterra/TerraiOS as a package dependency

  2. Add Capabilities:

    1. Healthkit > Healthkit Background Delivery

    2. Background Modes > Background processing

    3. Background modes > Background fetch

  3. In your info.plist, add:

    1. Privacy - Health Share Usage Description: custom text shown in the Apple Health permission screen

    2. Permitted background task scheduler: co.tryterra.data.post.request

If you do not need to access the , there is no reason to use Terra mobile SDKs!

If you need to access Terra's Health & Fitness API, it is only secure to do so from your server backend and send the desired data to your mobile frontend.

Never expose your API key on the client side unless you are simply testing

mobile-only integrations