Skip to main content
This guide walks you through the complete integration flow with Open Wearables - creating users from your backend, handing users off to wearable providers via OAuth (the frontend redirect dance included), and fetching normalized health data back.

Prerequisites

Before you begin, ensure you have:
  • Open Wearables instance running (self-hosted or cloud)
  • API Key generated from the settings tab in Open Wearables Developer portal
  • Your backend framework ready (examples use Python/FastAPI, but concepts apply to any language)

Environment Variables

Add these to your application’s environment:

Authentication

All API requests require the X-Open-Wearables-API-Key header. This is not a Bearer token.
Common mistake: Using Authorization: Bearer YOUR_API_KEY won’t work. Always use the X-Open-Wearables-API-Key header.

SDK Authentication (Mobile Apps)

If you’re building a mobile app that pushes health data (e.g., Apple Health from iOS), use SDK tokens instead of API keys. SDK tokens are user-scoped JWT tokens that only authorize data push endpoints.

Step 1: Create an Application

First, register an application to get app_id and app_secret:
Response:
Store app_secret securely in your backend. It’s only returned once and cannot be retrieved again.

Step 2: Exchange Credentials for User Token

When a user logs into your mobile app, your backend exchanges the app credentials for a user-scoped token:
Response:

Step 3: Use Token in Mobile App

The mobile app uses this token to push health data to the SDK sync endpoints:
Samsung endpoint is in BETA: The /sync/samsung endpoint is currently under development. It accepts authentication but does not process data yet. The endpoint path may change in future versions. Do not use in production.
SDK tokens are only valid for /sdk/ endpoints. All other API endpoints will return 401 for SDK tokens. Use API keys for those endpoints. The user_id in the URL must match the user the token was issued for.

Integration Flow Overview


Step 1: User Registration

When a user registers in your application, create a corresponding user in Open Wearables.

Create User

All fields in the create-user payload are optional - you can even POST an empty body and get back a valid user.

Response

Any field you omit from the request will come back as null.
Store the Open Wearables User ID! You’ll need this ID for all subsequent API calls.

Update Your User Model

Add a field to store the Open Wearables user ID:
Open Wearables does not enforce uniqueness on email, first_name, or last_name - calling POST /api/v1/users twice with the same email will happily create two separate user records. Your backend is the source of truth for user identity and is responsible for ensuring you only create one Open Wearables user per user in your system (e.g. by storing open_wearables_user_id on your own user row and checking it before creating).
The legacy external_user_id column does still carry a DB-level unique constraint, so sending a duplicate value there will fail with an integrity error. The field is deprecated and no data-fetching endpoint accepts it - do not rely on it for deduplication. Use the pattern above (store the Open Wearables UUID on your side) instead.

Step 2: Connect Wearable Provider

Connect users to their wearable devices via OAuth.

List Available Providers

The list of providers is dynamic - which ones are enabled depends on your instance’s configuration and whether OAuth credentials are set up. Fetch it at runtime rather than hardcoding a list:
Query parameters: Response:
Response fields:
icon_url is a relative path. To render the provider icon, prepend your API base URL: {OPEN_WEARABLES_API_URL}{icon_url}.

Update Provider Setting

For providers where live_sync_configurable is true, the instance admin can switch the live sync mode between periodic REST polling and real-time webhook delivery.
Request body (all fields optional — omit any field to leave it unchanged):
200 — Success:
400 — Provider does not support live sync mode configuration: Returned when live_sync_mode is included in the request body for a provider where live_sync_configurable is false (e.g. Garmin, which is webhook-only).
400 — Invalid live_sync_mode value: Returned when an invalid mode string or an explicit null is sent for live_sync_mode.
Passing "live_sync_mode": null in the JSON body is invalid and returns 400. To leave the current mode unchanged, omit the field entirely from the request body.
Pull mode and late revisions. In pull mode each periodic sync starts from the connection’s last synced time and moves forward, so a day a provider revises after we passed it (e.g. Oura finalising a step count once the ring finishes uploading) is not re-fetched. Set the server-side PULL_SYNC_LOOKBACK env var (compact duration — 2d, 20h, 90m, 1d12h; disabled by default, capped per provider by its max_historical_days) to re-fetch a trailing window on every live pull. Re-fetched days are upserted, so unchanged values stay as they are. webhook mode is unaffected — providers push the exact object that changed.

Get Authorization URL

Provider names must be lowercase: garmin, polar, suunto

Response

Frontend Integration Flow

1

User clicks 'Connect Garmin'

Your frontend initiates the connection flow.
2

Your backend calls the authorize endpoint

Request the authorization URL from Open Wearables.
3

Redirect user to authorization_url

The user authenticates with their wearable provider.
4

Provider redirects to Open Wearables callback

Open Wearables handles the OAuth callback automatically.
5

Open Wearables redirects to your app

Configure a redirect_uri parameter to return users to your app.

Custom Redirect URI

To redirect users back to your app after OAuth:

Check Connection Status

Verify a user’s connected providers:

Response


Step 3: Historical Backfill (Automatic)

You do not need to explicitly trigger a sync after OAuth completes. On the first successful provider connection, Open Wearables automatically dispatches a historical backfill:
  • Up to 90 days for pull-based providers (Polar, Suunto)
  • Up to 30 days for Garmin (webhook-based backfill capped at 30 days from the user’s consent date - further back is not retrievable)
This behaviour is controlled by the HISTORICAL_SYNC_ON_CONNECT flag (default: true). It’s a grace-period flag introduced in v0.4.3 - the default will flip to false in a future release and the flag will eventually be removed. Once you’re ready to control historical backfill yourself, set HISTORICAL_SYNC_ON_CONNECT=false and call POST /api/v1/providers/{provider}/users/{user_id}/sync/historical explicitly. See PR #897 for background.
Ongoing sync after the initial backfill happens automatically - webhooks push updates for providers that support them, and pull-based providers are polled on a schedule. Your integration only needs to connect the user; fetching data (next step) is the read side.

Step 4: Retrieve Health Data

All data endpoints require the X-Open-Wearables-API-Key header and return a PaginatedResponse wrapper:
To page through results, pass cursor=<next_cursor> until has_more is false. The health-scores endpoint uses offset/limit instead of a cursor but returns the same envelope.

Activity summary

Daily aggregates: steps, distance, floors, active/total kcal, active/sedentary minutes, intensity minutes, heart-rate stats.

Sleep summary

Daily sleep: duration, efficiency, stages (awake/light/deep/rem minutes), interruptions, naps, avg HR/HRV/SpO2.

Body summary

Point-in-time body metrics grouped into slow_changing (weight, height, BMI, body fat), averaged (resting HR, HRV over 1-7 days) and latest (temperature, blood pressure within a recency window).

Timeseries

Granular samples for any SeriesType (heart rate, steps, SpO2, etc.) with optional resolution bucketing (raw, 1min, 5min, 15min, 1hour).

Workouts

Workout sessions with type, duration, calories, distance, avg/max heart rate, pace, elevation gain, and source provider metadata.

Health scores

Provider-computed scores (sleep, recovery, readiness, stress, body battery, strain) with optional components breakdown. Filter by category or provider.

Response Shapes

Every field apart from date and source is nullable.

Complete Integration Example

Here’s a complete Python client class for Open Wearables integration:

Troubleshooting

If your app runs in Docker and Open Wearables runs on the host machine:
docker-compose.yml
Ensure you’re using the correct header format:
email has no unique constraint in Open Wearables - calling POST /api/v1/users twice with the same email will create two separate records. Your backend is the source of truth for user identity, so store the Open Wearables id (UUID) on your own user row the first time you create it, and reuse that ID for every subsequent call:
The legacy external_user_id field is still DB-unique, but it is deprecated and no data-fetching endpoint accepts it - don’t use it for deduplication.
Historical backfill is dispatched asynchronously on connect and can take anywhere from seconds to several minutes depending on the provider and the amount of history. Poll GET /api/v1/users/{user_id}/connections - last_synced_at flips from null to a timestamp once the first sync finishes. For Garmin’s async export, full history can take hours.If HISTORICAL_SYNC_ON_CONNECT=false in your instance, no backfill runs automatically - call POST /api/v1/providers/{provider}/users/{user_id}/sync/historical yourself.
start_time, end_time, and types are all required. types is repeated:
Also verify the SeriesType you’re requesting exists for this user - check GET /api/v1/users/{user_id}/summaries/data for a breakdown of what’s been ingested.
Pass the redirect_uri parameter when getting the authorization URL:

Next Steps

API Reference

Complete API documentation with all endpoints.

Provider Setup

Configure OAuth credentials for each provider.

Data Model

Understand the unified health data model.

GitHub

View source code and contribute.