Integrate the Open Wearables iOS SDK into a Swift app. Covers backend auth setup, SDK configuration, sign-in flow, and HealthKit permission requests. From setup to production.
This guide walks you through the complete integration of the Open Wearables iOS SDK into a native Swift application, from backend setup to production deployment.
The SDK supports two authentication modes: token-based (recommended) and API key. The token-based flow keeps your App credentials safe on your backend:
Your Backend generates token
Your backend calls the Open Wearables API with your App credentials (app_id + app_secret) to generate a user-scoped token (server-to-server, HTTPS) via Create User Token endpoint. Open Wearables returns access_token + refresh_token.
Your Backend returns tokens to the app
Your backend exposes its own custom endpoint that forwards the access_token and refresh_token to the mobile app. Never expose app_id or app_secret to the client.
Mobile App calls SDK signIn
The iOS app receives the tokens and passes them to sdk.signIn(accessToken:, refreshToken:).
SDK stores & syncs
iOS SDK stores credentials in Keychain and uses accessToken to sync health data directly to Open Wearables.
Never embed your app_id / app_secret in the mobile app. App credentials should only exist on your backend server. Only the access_token and refresh_token are passed to the mobile app.
Return the access_token and refresh_token to the mobile app
Node.js
Python
Ruby
// Express.js example — runs on YOUR backend (e.g. https://api.yourapp.com)const express = require('express');const app = express();app.post('/api/health/connect', authenticateUser, async (req, res) => { try { const owUserId = req.user.openWearablesUserId; // Call Open Wearables API to generate a user-scoped token const response = await fetch( `${process.env.OPENWEARABLES_HOST}/api/v1/users/${owUserId}/token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ app_id: process.env.OPENWEARABLES_APP_ID, app_secret: process.env.OPENWEARABLES_APP_SECRET, }), } ); if (!response.ok) { throw new Error('Failed to generate token'); } const { access_token, refresh_token } = await response.json(); // Return tokens to the mobile app (NOT the app credentials!) res.json({ userId: owUserId, accessToken: access_token, refreshToken: refresh_token, }); } catch (error) { console.error('Health connect error:', error); res.status(500).json({ error: 'Failed to connect health' }); }});
# FastAPI example — runs on YOUR backend (e.g. https://api.yourapp.com)from fastapi import FastAPI, Depends, HTTPExceptionimport httpximport osapp = FastAPI()@app.post("/api/health/connect")async def connect_health(current_user = Depends(get_current_user)): ow_user_id = current_user.open_wearables_user_id # Call Open Wearables API to generate a user-scoped token async with httpx.AsyncClient() as client: response = await client.post( f"{os.environ['OPENWEARABLES_HOST']}/api/v1/users/{ow_user_id}/token", json={ "app_id": os.environ["OPENWEARABLES_APP_ID"], "app_secret": os.environ["OPENWEARABLES_APP_SECRET"], }, ) if response.status_code != 200: raise HTTPException(500, "Failed to generate token") data = response.json() # Return tokens to the mobile app return { "userId": str(ow_user_id), "accessToken": data["access_token"], "refreshToken": data["refresh_token"], }
# Rails controller example — runs on YOUR backend (e.g. https://api.yourapp.com)class HealthController < ApplicationController before_action :authenticate_user! def connect ow_user_id = current_user.open_wearables_user_id # Call Open Wearables API to generate a user-scoped token response = HTTParty.post( "#{ENV['OPENWEARABLES_HOST']}/api/v1/users/#{ow_user_id}/token", headers: { 'Content-Type' => 'application/json' }, body: { app_id: ENV['OPENWEARABLES_APP_ID'], app_secret: ENV['OPENWEARABLES_APP_SECRET'] }.to_json ) if response.success? # Return tokens to the mobile app render json: { userId: ow_user_id, accessToken: response['access_token'], refreshToken: response['refresh_token'] } else render json: { error: 'Failed to connect' }, status: 500 end endend
The user_id in the URL is the Open Wearables User ID (UUID). You should store this mapping in your database when you first Create User via the Open Wearables API.
The Open Wearables API base URL — host only, without path suffix (e.g. https://api.openwearables.io)
Provide only the base host URL, e.g. https://your-domain.com. Do not append /api/v1/ or any other path — the SDK adds the required path prefix automatically.
// For self-hosted Open Wearablessdk.configure(host: "https://your-domain.com")
The SDK automatically persists credentials in the iOS Keychain. On app launch, call configure() to restore any existing session:
sdk.configure(host: "https://api.openwearables.io")// Check if user was previously signed inif sdk.isSessionValid { print("Welcome back!") // User is already signed in, can resume sync} else { // Need to sign in first}
func connectHealth() { // 1. Get tokens from YOUR backend (e.g. https://api.yourapp.com) yourAPI.post("/api/health/connect") { result in switch result { case .success(let credentials): // 2. Sign in with the SDK OpenWearablesHealthSDK.shared.signIn( userId: credentials.userId, accessToken: credentials.accessToken, refreshToken: credentials.refreshToken, // Optional: enables auto-refresh apiKey: nil ) print("Connected: \(credentials.userId)") case .failure(let error): print("Failed to connect: \(error)") } }}
API key authentication embeds the key in the app. Only use this for internal or trusted applications. For production apps, always use token-based authentication.
When you provide a refreshToken, the SDK automatically handles 401 responses by refreshing the access token and retrying the request. No additional configuration is needed.You can also update tokens manually if needed:
The HealthDataType enum provides type-safe access to all supported health data types. The string-based requestAuthorization(types: [String], completion:) overload is deprecated — use the enum variant instead.
On iOS, users can grant partial permissions. The SDK will sync whatever data the user allows. Apple’s privacy model means your app cannot determine which specific types were denied.
By default, the SDK syncs all available historical data on the first sync. Use the syncDaysBack parameter to limit how far back the sync goes:
// Sync only the last 90 days of dataOpenWearablesHealthSDK.shared.startBackgroundSync(syncDaysBack: 90) { started in print("Sync started: \(started)")}// Sync last 30 daysOpenWearablesHealthSDK.shared.startBackgroundSync(syncDaysBack: 30) { started in print("Sync started: \(started)")}// Full sync — all available history (default)OpenWearablesHealthSDK.shared.startBackgroundSync { started in print("Sync started: \(started)")}
Parameter
Type
Default
Description
syncDaysBack
Int?
nil
Number of days of historical data to sync. Syncs from the start of the day that many days ago. When nil, syncs all available history. The value is persisted and used for subsequent background syncs until changed.
Immediate delivery when new health data is written
BGAppRefreshTask
Scheduled every ~15 minutes (system-managed)
BGProcessingTask
Network-required background processing
Background sync frequency is controlled by iOS and may vary based on battery level, network conditions, and user behavior. The 15-minute interval is a minimum - actual syncs may be less frequent.
Control SDK log output using setLogLevel. By default, the SDK uses .debug, which outputs logs only in debug builds:
let sdk = OpenWearablesHealthSDK.shared// Always show logs (including release builds)sdk.setLogLevel(.always)// Only show logs in debug builds (default)sdk.setLogLevel(.debug)// Disable all logssdk.setLogLevel(.none)
Level
Description
.none
No logs at all (neither console nor onLog callback)
.always
Logs are always emitted regardless of build configuration
.debug
Logs are emitted only in debug builds (default)
Set .always during development or when troubleshooting sync issues in production. Switch to .none if you want to suppress all SDK output.