Apple Health, Health Connect and Samsung Health have no cloud API - their data comes from your mobile app through the SDK. For that part, see the Mobile SDK Integration Guide. Steps 1 and 4 below apply to both.
Prerequisites
- An Open Wearables instance (self-hosted or cloud)
- An API key from Settings → Credentials in the developer portal
- Any backend language - examples use Python (
httpx) and TypeScript
Authentication
Send the API key in theX-Open-Wearables-API-Key header:
Integration Flow Overview

Step 1: User Registration
When a user registers in your application, create a corresponding user in Open Wearables. All fields are optional - you canPOST {} and get back a valid user.
id (UUID) along with the fields you sent - see Create User for the full shape.
Step 2: Connect Wearable Provider
List available providers
Which providers are enabled depends on your instance, so fetch the list at runtime:enabled_only- only providers enabled in your instance (defaultfalse).cloud_only- only OAuth providers, without SDK-only ones like Apple Health (defaultfalse). Use it for a web “connect” screen.icon_urlis relative - prepend your API base URL.
Get the authorization URL
Provider names are lowercase (garmin, oura, whoop, polar, suunto, …). Pass redirect_uri to send the user back to your app after OAuth:
authorization_url. After they approve access, the provider calls Open Wearables, which stores the connection and redirects the user to your redirect_uri.
Check connection status
provider and a status (active, revoked or expired). See Get Connections for all fields.
Step 3: Historical Backfill (Automatic)
You don’t need to trigger a sync after OAuth. When a user connects a provider, Open Wearables dispatches a historical backfill:- up to 90 days for pull-based providers (Polar, Suunto, Oura, Whoop, Strava, …)
- up to 30 days for Garmin, whose backfill is capped at 30 days before the user’s consent date
HISTORICAL_SYNC_ON_CONNECT (default true). The default is planned to change in a future release. To control the range yourself, set it to false and call POST /api/v1/providers/{provider}/users/{user_id}/sync/historical (days defaults to 90, max 365).
Step 4: Retrieve Health Data
Data endpoints return a paginated envelope:cursor=<next_cursor> until has_more is false. total_count is only set on the first page. Health scores use offset / limit instead of a cursor.
Activity summary
Daily steps, distance, floors, calories, active/sedentary minutes, intensity minutes, heart rate.
Sleep summary
Daily sleep duration, efficiency, stages, sessions, naps, and average HR/HRV/SpO2.
Body summary
Weight, height, BMI, body fat, averaged resting HR/HRV, latest temperature and blood pressure.
Timeseries
Granular samples (heart rate, steps, SpO2, …) with optional
resolution bucketing.Workouts
Workout sessions with type, duration, calories, distance, heart rate, pace, and elevation.
Health scores
Provider-computed sleep, recovery, readiness, stress, body battery, and strain scores.
- Nullable fields. Apart from identifiers, dates and
source, any field can benull- providers report different subsets. See the coverage matrix. source.provideris the provider slug (garmin,apple,health_connect, …).source.deviceis the raw device model.- Sleep durations are in minutes and exclude naps, which are reported separately.
- Recovery summary returns data only for WHOOP.
- Body summary is a single object, not a paginated list, and is
nullwhen the user has no body data. - Timeseries requires
start_timeandend_time.typesis optional and repeated (?types=heart_rate&types=steps) - without it, all series types are returned.
Example client
Troubleshooting
Docker container can't reach Open Wearables on localhost
Docker container can't reach Open Wearables on localhost
If your app runs in Docker and Open Wearables runs on the host machine:
docker-compose.yml
Data endpoints return empty results right after OAuth
Data endpoints return empty results right after OAuth
The backfill runs asynchronously and takes from seconds to several minutes, depending on the provider and the amount of history - Garmin can take hours.
last_synced_at on the connection is set when the user connects, so it doesn’t tell you whether the backfill finished. Check GET /api/v1/users/{user_id}/sync/runs for sync progress instead.If HISTORICAL_SYNC_ON_CONNECT=false on your instance, no backfill runs automatically - see Step 3.Timeseries endpoint returns empty data
Timeseries endpoint returns empty data
Check that
start_time / end_time cover the period you expect, and that the user has that series type - GET /api/v1/users/{user_id}/summaries/data shows what’s been ingested.OAuth callback doesn't redirect to my app
OAuth callback doesn't redirect to my app
Pass
redirect_uri when requesting the authorization URL.Next Steps
API Reference
All endpoints with request and response schemas.
Mobile SDK
Add Apple Health, Health Connect and Samsung Health via your mobile app.
Provider Setup
Configure OAuth credentials for each provider.
Data Model
Understand the unified health data model.

