Skip to main content
This guide walks you through the complete integration flow for connecting your backend application to Open Wearables. You’ll learn how to create users, connect wearable providers via OAuth, sync data, and retrieve health metrics.

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:
OPEN_WEARABLES_API_URL=http://localhost:8000
OPEN_WEARABLES_API_KEY=sk-your-api-key-here

Authentication

All API requests require the X-Open-Wearables-API-Key header. This is not a Bearer token.
curl http://localhost:8000/api/v1/users \
  -H "X-Open-Wearables-API-Key: YOUR_API_KEY"
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.
AuthenticationUse Case
API KeyBackend-to-backend integration, fetching data, OAuth flows
SDK TokenMobile apps pushing health data to Open Wearables

Step 1: Create an Application

First, register an application to get app_id and app_secret:
curl -X POST http://localhost:8000/api/v1/applications \
  -H "Authorization: Bearer YOUR_DEVELOPER_JWT" \
  -H "Content-Type: application/json" \
  -d '{"name": "My iOS App"}'
Response:
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "app_id": "app_abc123",
  "name": "My iOS App",
  "created_at": "2025-01-15T10:30:00Z",
  "app_secret": "secret_xyz789..."  // Store securely! Only shown once.
}
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:
curl -X POST http://localhost:8000/api/v1/users/{user_id}/token \
  -H "Content-Type: application/json" \
  -d '{
    "app_id": "app_abc123",
    "app_secret": "secret_xyz789..."
  }'
Response:
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "bearer"
}

Step 3: Use Token in Mobile App

The mobile app uses this token to push health data to the SDK sync endpoints:
curl -X POST http://localhost:8000/api/v1/sdk/users/{user_id}/sync/apple/healthion \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{ ... health data ... }'
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

curl -X POST http://localhost:8000/api/v1/users \
  -H "X-Open-Wearables-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "external_user_id": "your-internal-user-id"
  }'

Response

{
  "id": "176be8de-8452-4eb7-a7ea-147fec925d9d",
  "email": "user@example.com",
  "external_user_id": "your-internal-user-id",
  "created_at": "2025-01-15T10:30:00Z",
  "first_name": null,
  "last_name": 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:
from uuid import UUID
from sqlalchemy.orm import Mapped, mapped_column

class User(Base):
    id: Mapped[UUID] = mapped_column(primary_key=True)
    email: Mapped[str]
    open_wearables_user_id: Mapped[UUID | None]  # Add this!

Handling Duplicate Users

Open Wearables allows multiple users with the same email. To prevent duplicates in concurrent requests (common in SPAs), implement a check-then-create pattern:
async def get_or_create_user(email: str, external_id: str) -> dict:
    async with httpx.AsyncClient() as client:
        # Check if user exists by external_user_id
        response = await client.get(
            f"{OPEN_WEARABLES_API_URL}/api/v1/users",
            headers={"X-Open-Wearables-API-Key": API_KEY},
            params={"external_user_id": external_id, "limit": 1}
        )
        
        users = response.json().get("items", [])
        if users:
            return users[0]
        
        # Create new user
        response = await client.post(
            f"{OPEN_WEARABLES_API_URL}/api/v1/users",
            headers={"X-Open-Wearables-API-Key": API_KEY},
            json={"email": email, "external_user_id": external_id}
        )
        return response.json()

Step 2: Connect Wearable Provider

Connect users to their wearable devices via OAuth.

Supported Providers

ProviderOAuth SupportData Types
GarminWorkouts, activities
PolarExercises
SuuntoWorkouts, 24/7 data (sleep, recovery)
Apple HealthSDK onlyAll health data

Get Authorization URL

Provider names must be lowercase: garmin, polar, suunto
curl "http://localhost:8000/api/v1/oauth/garmin/authorize?user_id=176be8de-8452-4eb7-a7ea-147fec925d9d" \
  -H "X-Open-Wearables-API-Key: YOUR_API_KEY"

Response

{
  "authorization_url": "https://connect.garmin.com/oauthConfirm?oauth_token=...",
  "state": "abc123..."
}

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:
curl "http://localhost:8000/api/v1/oauth/garmin/authorize?user_id=USER_ID&redirect_uri=https://yourapp.com/oauth/callback" \
  -H "X-Open-Wearables-API-Key: YOUR_API_KEY"

Check Connection Status

Verify a user’s connected providers:
curl "http://localhost:8000/api/v1/users/176be8de-8452-4eb7-a7ea-147fec925d9d/connections" \
  -H "X-Open-Wearables-API-Key: YOUR_API_KEY"

Response

[
  {
    "id": "a1b2c3d4-...",
    "user_id": "176be8de-8452-4eb7-a7ea-147fec925d9d",
    "provider": "garmin",
    "status": "active",
    "provider_user_id": "12345678",
    "created_at": "2025-01-15T10:30:00Z",
    "updated_at": "2025-01-15T10:30:00Z",
    "last_synced_at": null
  }
]

Step 3: Sync Data from Provider

After OAuth connection, sync the user’s health data.
Each provider has different sync requirements! This is the most common source of integration errors.

Provider-Specific Parameters

ProviderRequired ParamsConstraints
Garminsummary_start_time, summary_end_timeMax 24 hours range
Suuntosince (Unix timestamp)Max 28 days from since
PolarNoneUses pull token internally

Garmin Sync

curl -X POST "http://localhost:8000/api/v1/providers/garmin/users/USER_ID/sync?data_type=all&summary_start_time=2025-01-15T00:00:00&summary_end_time=2025-01-15T23:59:59" \
  -H "X-Open-Wearables-API-Key: YOUR_API_KEY"

Suunto Sync

# 'since' = Unix timestamp (7 days ago = 1736899200)
curl -X POST "http://localhost:8000/api/v1/providers/suunto/users/USER_ID/sync?data_type=all&since=1736899200" \
  -H "X-Open-Wearables-API-Key: YOUR_API_KEY"

Polar Sync

curl -X POST "http://localhost:8000/api/v1/providers/polar/users/USER_ID/sync?data_type=all" \
  -H "X-Open-Wearables-API-Key: YOUR_API_KEY"

Python Helper Function

from datetime import datetime, timedelta

async def sync_provider(provider: str, user_id: str) -> dict:
    """Sync data with provider-specific parameters."""
    params = {"data_type": "all"}
    now = datetime.utcnow()
    
    if provider == "garmin":
        # Garmin requires time range (max 24 hours)
        start = now - timedelta(hours=24)
        params["summary_start_time"] = start.isoformat()
        params["summary_end_time"] = now.isoformat()
    elif provider == "suunto":
        # Suunto requires Unix timestamp
        params["since"] = int((now - timedelta(days=7)).timestamp())
    # Polar requires no additional params
    
    async with httpx.AsyncClient() as client:
        response = await client.post(
            f"{OPEN_WEARABLES_API_URL}/api/v1/providers/{provider}/users/{user_id}/sync",
            headers={"X-Open-Wearables-API-Key": API_KEY},
            params=params,
            timeout=60.0  # Sync can take time
        )
        return response.json()

Common Sync Errors

ErrorCauseSolution
"User not connected to {provider}"OAuth not completedComplete OAuth flow first
"Missing time range parameters"Garmin missing timesAdd summary_start_time and summary_end_time
422 Unprocessable EntitySuunto missing sinceAdd since Unix timestamp parameter
"InvalidPullTokenException"Token expiredUser must reconnect via OAuth
"duplicate key value"Data already syncedSafe to ignore

Step 4: Retrieve Health Data

After syncing, fetch the normalized health data.

Workouts

curl "http://localhost:8000/api/v1/users/USER_ID/events/workouts?start_date=2025-01-01&end_date=2025-01-31" \
  -H "X-Open-Wearables-API-Key: YOUR_API_KEY"

Response

{
  "data": [
    {
      "id": "abc123...",
      "type": "running",
      "name": "Morning Run",
      "start_time": "2025-01-15T07:30:00Z",
      "end_time": "2025-01-15T08:15:00Z",
      "duration_seconds": 2700,
      "calories_kcal": 450.5,
      "distance_meters": 5200.0,
      "avg_heart_rate_bpm": 155,
      "max_heart_rate_bpm": 178,
      "source": {
        "provider": "garmin",
        "device": "Garmin Forerunner 255"
      }
    }
  ],
  "pagination": {
    "cursor": null,
    "has_more": false
  }
}

Sleep Sessions

curl "http://localhost:8000/api/v1/users/USER_ID/events/sleep?start_date=2025-01-01&end_date=2025-01-31" \
  -H "X-Open-Wearables-API-Key: YOUR_API_KEY"

Response

{
  "data": [
    {
      "id": "def456...",
      "start_time": "2025-01-15T22:30:00Z",
      "end_time": "2025-01-16T06:45:00Z",
      "duration_seconds": 29700,
      "efficiency_percent": 92.5,
      "is_nap": false,
      "stages": {
        "awake_seconds": 1200,
        "light_seconds": 10800,
        "deep_seconds": 7200,
        "rem_seconds": 10500
      },
      "source": {
        "provider": "suunto"
      }
    }
  ],
  "pagination": {
    "cursor": null,
    "has_more": false
  }
}

Timeseries Data

All three parameters are required: start_time, end_time, and types
curl "http://localhost:8000/api/v1/users/USER_ID/timeseries?start_time=2025-01-15T00:00:00Z&end_time=2025-01-15T23:59:59Z&types=heart_rate&types=steps" \
  -H "X-Open-Wearables-API-Key: YOUR_API_KEY"

Available Timeseries Types

TypeUnit
heart_ratebpm
resting_heart_ratebpm
heart_rate_variability_sdnnms
oxygen_saturationpercent
blood_glucosemg/dL
respiratory_ratebreaths/min
body_temperaturecelsius

Complete Integration Example

Here’s a complete Python client class for Open Wearables integration:
import httpx
from datetime import datetime, timedelta
from typing import Literal

class OpenWearablesClient:
    def __init__(self, base_url: str, api_key: str):
        self.base_url = base_url
        self.headers = {"X-Open-Wearables-API-Key": api_key}
    
    async def get_or_create_user(self, email: str, external_id: str) -> dict:
        """Create user or return existing."""
        async with httpx.AsyncClient() as client:
            # Check if exists
            resp = await client.get(
                f"{self.base_url}/api/v1/users",
                headers=self.headers,
                params={"external_user_id": external_id, "limit": 1}
            )
            users = resp.json().get("items", [])
            if users:
                return users[0]
            
            # Create new
            resp = await client.post(
                f"{self.base_url}/api/v1/users",
                headers=self.headers,
                json={"email": email, "external_user_id": external_id}
            )
            return resp.json()
    
    async def get_auth_url(
        self, 
        provider: Literal["garmin", "polar", "suunto"], 
        user_id: str,
        redirect_uri: str | None = None
    ) -> str:
        """Get OAuth authorization URL."""
        params = {"user_id": user_id}
        if redirect_uri:
            params["redirect_uri"] = redirect_uri
            
        async with httpx.AsyncClient() as client:
            resp = await client.get(
                f"{self.base_url}/api/v1/oauth/{provider}/authorize",
                headers=self.headers,
                params=params
            )
            return resp.json()["authorization_url"]
    
    async def get_connections(self, user_id: str) -> list[dict]:
        """Get user's connected providers."""
        async with httpx.AsyncClient() as client:
            resp = await client.get(
                f"{self.base_url}/api/v1/users/{user_id}/connections",
                headers=self.headers
            )
            return resp.json()
    
    async def sync_provider(
        self, 
        provider: Literal["garmin", "polar", "suunto"], 
        user_id: str
    ) -> dict:
        """Sync data with provider-specific params."""
        params = {"data_type": "all"}
        now = datetime.utcnow()
        
        if provider == "garmin":
            start = now - timedelta(hours=24)
            params["summary_start_time"] = start.isoformat()
            params["summary_end_time"] = now.isoformat()
        elif provider == "suunto":
            params["since"] = int((now - timedelta(days=7)).timestamp())
        
        async with httpx.AsyncClient() as client:
            resp = await client.post(
                f"{self.base_url}/api/v1/providers/{provider}/users/{user_id}/sync",
                headers=self.headers,
                params=params,
                timeout=60.0
            )
            return resp.json()
    
    async def get_workouts(
        self, 
        user_id: str, 
        start_date: str, 
        end_date: str
    ) -> list[dict]:
        """Get workouts in date range."""
        async with httpx.AsyncClient() as client:
            resp = await client.get(
                f"{self.base_url}/api/v1/users/{user_id}/events/workouts",
                headers=self.headers,
                params={"start_date": start_date, "end_date": end_date}
            )
            return resp.json().get("data", [])
    
    async def get_sleep(
        self, 
        user_id: str, 
        start_date: str, 
        end_date: str
    ) -> list[dict]:
        """Get sleep sessions in date range."""
        async with httpx.AsyncClient() as client:
            resp = await client.get(
                f"{self.base_url}/api/v1/users/{user_id}/events/sleep",
                headers=self.headers,
                params={"start_date": start_date, "end_date": end_date}
            )
            return resp.json().get("data", [])
    
    async def get_timeseries(
        self, 
        user_id: str, 
        start_time: str, 
        end_time: str,
        types: list[str]
    ) -> list[dict]:
        """Get timeseries data."""
        async with httpx.AsyncClient() as client:
            resp = await client.get(
                f"{self.base_url}/api/v1/users/{user_id}/timeseries",
                headers=self.headers,
                params={
                    "start_time": start_time,
                    "end_time": end_time,
                    "types": types
                }
            )
            return resp.json().get("data", [])


# Usage example
async def main():
    client = OpenWearablesClient(
        base_url="http://localhost:8000",
        api_key="sk-your-api-key"
    )
    
    # 1. Create or get user
    user = await client.get_or_create_user(
        email="user@example.com",
        external_id="auth0|123456"
    )
    user_id = user["id"]
    
    # 2. Get OAuth URL for Garmin
    auth_url = await client.get_auth_url(
        provider="garmin",
        user_id=user_id,
        redirect_uri="https://myapp.com/callback"
    )
    print(f"Redirect user to: {auth_url}")
    
    # 3. After OAuth callback, check connections
    connections = await client.get_connections(user_id)
    garmin_connected = any(
        c["provider"] == "garmin" and c["status"] == "active" 
        for c in connections
    )
    
    if garmin_connected:
        # 4. Sync data
        await client.sync_provider("garmin", user_id)
        
        # 5. Fetch workouts
        workouts = await client.get_workouts(
            user_id,
            start_date="2025-01-01",
            end_date="2025-01-31"
        )
        print(f"Found {len(workouts)} workouts")

Troubleshooting

If your app runs in Docker and Open Wearables runs on the host machine:
docker-compose.yml
services:
  app:
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      - OPEN_WEARABLES_API_URL=http://host.docker.internal:8000
Ensure you’re using the correct header format:
# ✅ Correct
-H "X-Open-Wearables-API-Key: YOUR_API_KEY"

# ❌ Wrong
-H "Authorization: Bearer YOUR_API_KEY"
Open Wearables allows duplicate emails by design. Use external_user_id to identify users uniquely:
# Always check by external_user_id before creating
existing = await client.get(
    "/api/v1/users",
    params={"external_user_id": your_user_id}
)
Check provider-specific requirements:
ProviderFix
GarminAdd summary_start_time and summary_end_time
SuuntoAdd since parameter (Unix timestamp)
PolarUsually works without extra params
Ensure all required parameters are provided:
# All three are REQUIRED
?start_time=2025-01-15T00:00:00Z
&end_time=2025-01-15T23:59:59Z
&types=heart_rate
Also verify data exists for the requested time range.
Pass the redirect_uri parameter when getting the authorization URL:
/api/v1/oauth/garmin/authorize?user_id=...&redirect_uri=https://myapp.com/callback

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.