Back to blog

How to integrate Suunto data into your health app

Open Wearables Team · · 6 min read

Key Takeaways

  • Integrating Suunto directly requires building OAuth 2.0 flows, managing token refresh, polling Suunto's cloud API, and normalizing data to your own schema. Open Wearables handles all of that.
  • Suunto in Open Wearables provides both workout data and 24/7 continuous monitoring: sleep with stages, recovery scores, and activity metrics.
  • The sync endpoint accepts data_type values of workouts, 247, or all, so you pull exactly what you need.
  • Historical sync is a single POST call. Use it on user onboarding to backfill data before going to incremental polling.
  • All data comes out in canonical units: bpm, meters, Celsius, UTC. The same read endpoints work across all connected providers.
  • Open Wearables is MIT-licensed, self-hostable via Docker, and costs $0 per user.
  • Suunto is one of four providers with full 24/7 continuous data support in Open Wearables, alongside Garmin, with Oura and Ultrahuman coming.

The Problem: What You Build When You Integrate the Suunto API Directly

If you want to integrate the Suunto API in your app, here is what you are signing up for.

OAuth 2.0 implementation. Suunto uses OAuth 2.0. You register an application, build an authorization redirect, handle the callback, exchange the code for access and refresh tokens, store both securely, and write logic to detect expiry and refresh before every API call. If you are building for multiple users, this logic runs at scale.

Polling. Suunto's API is cloud-based with REST polling. There is no webhook push. You write a scheduler that polls on some interval, tracks what you have already fetched via a since timestamp, and handles pagination when results exceed a page size.

Data normalization. Suunto returns data in its own schema and units. Your app likely needs a consistent internal representation. You write mapping code that converts Suunto's fields to your schema, handles missing or nullable fields, and converts units. Then you do the same thing again when you add the next provider.

None of this is unsolvable. It is just a lot of undifferentiated work before you get to build anything specific to your product.

What Open Wearables Does for You

Open Wearables is an open-source middleware layer that handles the full OAuth 2.0 flow including token storage and refresh, polls Suunto's cloud API, normalizes the response into a canonical schema, and exposes the data through a single REST API that you own and host.

Canonical units are consistent across all providers: heart rate in bpm, distance in meters, temperature in Celsius, timestamps in UTC. The read endpoints are the same regardless of whether a user has Suunto, Garmin, or any other supported device. You write your data layer once.

The Suunto Differentiator: 24/7 Continuous Data

Suunto is one of four providers in Open Wearables with full 24/7 continuous data support. In practice, this means you get more than workout records.

When a user connects their Suunto watch, you have access to workouts with GPS traces, heart rate, speed, and elevation; sleep including duration and stages; recovery scores and readiness data; and continuous activity data including steps, calories, and intraday metrics.

For health apps, the difference between workout-only and 24/7 data is significant. If you are building a recovery tracker, a training load tool, or anything that reasons about a user's day rather than just their runs, you need the continuous stream. Suunto gives you that. The data_type parameter on the sync endpoint controls which subset you pull.

Setup and OAuth Flow

            git clone https://github.com/the-momentum/open-wearables.git
cd open-wearables
docker compose up -d
          

The API is available at http://localhost:8000. Authenticate all requests with the X-Open-Wearables-API-Key header.

To connect a Suunto account, redirect the user to:

            GET /api/v1/oauth/suunto/authorize?user_id={user_id}&redirect_uri={your_callback_url}
          

Open Wearables handles the OAuth handshake, stores the tokens, and manages refresh from that point forward. You do not touch the tokens directly.

Syncing Data

The Sync Endpoint

            POST /api/v1/providers/suunto/users/{user_id}/sync
          

Parameters:

  • since (Unix timestamp, default 0): fetch data modified after this time. 0 fetches all available data.
  • limit (default 50, max 100): records per page
  • offset (default 0): pagination offset
  • filter_by_modification_time (default true): filter by modification time rather than creation time
  • data_type: workouts, 247, or all

Choosing data_type

workouts pulls recorded exercise sessions only. Use this if your app is activity-focused and does not use sleep or continuous health data.

247 pulls the continuous monitoring stream: sleep, recovery, intraday activity. Use this if you are building recovery dashboards, sleep tracking, or anything that works with the user's baseline state.

all pulls both. Use this for general health apps or when you are not sure what downstream features will need.

A typical incremental polling call:

            curl -X POST "http://localhost:8000/api/v1/providers/suunto/users/user_123/sync" \
  -H "X-Open-Wearables-API-Key: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"since": 1748736000, "limit": 100, "data_type": "all"}'
          

Set since to the timestamp of your last successful sync. Store that value and increment it after each successful run.

Historical Sync: Onboarding a New User

When a user first connects, backfill their history with the historical sync endpoint:

            POST /api/v1/providers/suunto/users/{user_id}/sync/historical
          

Run this once during onboarding, right after OAuth completes. After that, switch to incremental polling with a since timestamp.

Monitor sync status at:

            GET /api/v1/sync/events
          

Useful for confirming that a historical sync completed before you start displaying data to the user.

Reading the Data

Once synced, data is available through the standard read endpoints:

            GET /api/v1/users/{user_id}/events/workouts
GET /api/v1/users/{user_id}/events/sleep
GET /api/v1/users/{user_id}/timeseries
          

These return data in the canonical schema regardless of the source provider. If a user later connects a Garmin device alongside their Suunto, the same endpoint returns data from both, merged and normalized. Your read logic does not change.

Pagination

The sync endpoint returns up to 100 records per call. For users with long history, page through results using offset:

            offset = 0
limit = 100

while True:
    response = sync(user_id, since=last_sync_ts, limit=limit, offset=offset)
    records = response["data"]

    if not records:
        break

    store(records)
    offset += limit
          

For regular polling intervals, a single call with limit=100 is usually sufficient. Pagination is mainly relevant for historical sync and initial onboarding.

Quickstart

            # 1. Clone and start
git clone https://github.com/the-momentum/open-wearables.git
cd open-wearables
docker compose up -d

# 2. Connect a Suunto user (redirect them to this URL)
GET /api/v1/oauth/suunto/authorize?user_id=user_123&redirect_uri=https://yourapp.com/callback

# 3. Historical backfill on first connect
curl -X POST http://localhost:8000/api/v1/providers/suunto/users/user_123/sync/historical \
  -H "X-Open-Wearables-API-Key: your_api_key"

# 4. Incremental sync (run on a schedule)
curl -X POST "http://localhost:8000/api/v1/providers/suunto/users/user_123/sync" \
  -H "X-Open-Wearables-API-Key: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"since": 1748736000, "limit": 100, "data_type": "all"}'

# 5. Read normalized data
curl http://localhost:8000/api/v1/users/user_123/events/workouts -H "X-Open-Wearables-API-Key: your_api_key"
curl http://localhost:8000/api/v1/users/user_123/events/sleep -H "X-Open-Wearables-API-Key: your_api_key"
curl http://localhost:8000/api/v1/users/user_123/timeseries -H "X-Open-Wearables-API-Key: your_api_key"
          

See Related Articles

FAQ

Does Open Wearables support Suunto sleep stages, not just total sleep duration?

Yes. Suunto's 24/7 monitoring data includes sleep with stages, and Open Wearables surfaces that through the sleep events endpoint, normalized to the canonical schema.

How often should I poll the sync endpoint?

For most health apps, once per hour is sufficient for near-real-time data. For daily digests or recovery summaries, polling once per day is enough. Suunto's API is cloud-based, so data from the watch syncs to Suunto's servers periodically anyway.

What happens if a user's Suunto token expires?

Open Wearables manages token refresh automatically. As long as the user remains connected, OAuth tokens are refreshed behind the scenes and you do not need to handle this in your application code.

What is the difference between filter_by_modification_time=true and false?

When set to true (the default), the since parameter filters records by when they were last modified. This catches updated records, such as a workout that was corrected or a sleep entry that was reprocessed. When set to false, it filters by creation time only. The default is recommended for most use cases.

Does Suunto data normalize to the same schema as Garmin or Polar?

Yes. Workout data from Suunto and from Garmin comes out of the same endpoints in the same format with the same units. If a user connects both devices, you get their data through one API without any provider-specific handling on your end.

Never miss an update

Stay updated with the latest in open wearables, developer tools, and health data integration.

Join our Community. No spam ever.