Skip to content

Garmin Connect

Beta

Self-hosted integrations are in beta and are subject to breaking changes. If you plan to use them in production, email [email protected] before you integrate, so we can tell you about changes in advance and help you migrate.

This page describes how your app connects Garmin Connect, and what it needs from Garmin for that: keys, permissions and URLs.

Choose how your app connects Garmin Connect

Your app connects Garmin Connect on its own Garmin keys. You choose how on the Garmin card of your app's Data sources tab:

Way Who connects to Garmin Garmin's approval
Self-hosted integration (recommended) Your app, with the SweatStack bridge or your own code Your own Garmin developer access
Own keys SweatStack, on your Garmin keys Your own Garmin developer access, and Garmin's approval in writing before you switch it on

Hosted, on SweatStack's own Garmin keys, is not available to apps.

Use a self-hosted integration. With an approved Garmin app, deploying the SweatStack bridge takes about 10 minutes. The rest of this page applies to the bridge and to an integration you build yourself. The commands on this page are the bridge's. SweatStack holds none of these keys and no Garmin user id.

Get your keys

  1. Apply to the Garmin Connect Developer Program.
  2. Create an app in Garmin's developer portal.
  3. Copy the app's client id and client secret. Garmin's endpoint configuration calls them the consumer key and the consumer secret.
  4. Set both in the bridge:

    npx wrangler secret put GARMIN_CLIENT_ID
    npx wrangler secret put GARMIN_CLIENT_SECRET
    

Tell Garmin that SweatStack processes the data on your behalf, and send Garmin data only for users your app manages. See Use Garmin Connect data.

Garmin decides how long approval takes. Starter access is usually approved in about 1 hour. Enterprise access takes longer, and SweatStack knows no number for it. Apply before you deploy, because this is the one step whose duration you do not control.

Choose what your app offers

Two Garmin permissions decide what the bridge relays. Each athlete grants them on Garmin's consent screen, and can change them later in Garmin Connect.

Capability Garmin permission What the athlete gets
activities ACTIVITY_EXPORT New activities, from the moment they connect
activity_history HISTORICAL_DATA_EXPORT Earlier activities, up to BACKFILL_DAYS days back

Set GARMIN_APP_OFFERS in wrangler.jsonc to the capabilities your Garmin app is approved for, separated by commas. Then commit and push.

"GARMIN_APP_OFFERS": "activities,activity_history"

The value decides what athletes are asked to fix:

  • If your app offers a capability and the athlete did not grant its permission, the bridge declares it action_required.
  • If your app does not offer a capability, the bridge declares it unavailable, because reconnecting cannot give an athlete what your Garmin app cannot request.

If your Garmin app is not approved for HISTORICAL_DATA_EXPORT, leave activity_history out. Garmin refuses a request for history without that permission.

Paste the URLs

Run this in your copy of the bridge:

npm run urls

It prints each URL and where it goes, and checks that the bridge answers on it. The webhook URLs hold a secret: do not share the output.

Where Setting URL
Your app's settings in Garmin's developer portal OAuth 2 redirect URL https://your-bridge/callback/garmin_connect
Garmin's endpoint configuration Activity: Activity Files (ping) https://your-bridge/garmin_connect/{secret}/activity-files
Garmin's endpoint configuration Common: User Permissions Change https://your-bridge/garmin_connect/{secret}/user-permissions
Garmin's endpoint configuration Common: Deregistrations https://your-bridge/garmin_connect/{secret}/deregistrations
Your app's Data sources tab in SweatStack, Self-hosted on the Garmin card Issuer URL https://your-bridge/oidc/garmin_connect

Sign in to Garmin's endpoint configuration with your consumer key and consumer secret. Leave every other setting disabled. Garmin allows one URL per setting, so another server of yours stops receiving what you point at the bridge.

If you build your own integration, set the same three Garmin settings to your own URLs.

Garmin does not warn you about a setting that is missing, because a sign-in works without all three:

Missing What happens
Activity Files Your athletes' activities and history do not arrive
User Permissions Change If an athlete changes a permission, the bridge does not know. The Portal and the account status stay out of date
Deregistrations If an athlete removes your app in Garmin Connect, the connection stays until the bridge next fails to use its token

Check the setup

  1. Run npm run status. It prints no secrets, and it says what is missing.
  2. Open your authorize URL in a private window, with integration=garmin_connect and prompt=login.
  3. Sign in at Garmin, and grant every permission.
  4. Sync an activity from a Garmin device or from Garmin Connect.
  5. Run npm run status again. Last file shows the time of that activity's ping, and Failures is empty.
  6. Verify your app with Garmin, so that other athletes can use it too.

Without prompt=login, a browser that is already signed in to your app gets a code at once, and neither the bridge nor Garmin sees the request.

How history arrives

The bridge asks for history when the athlete connects, in windows of up to 30 days. BACKFILL_DAYS sets how far back it asks, and the default is 90.

Garmin does not answer with the files. It accepts the request, and later sends each file as an Activity Files ping, so history needs the same URL as new activities. Garmin has no signal for "finished". The bridge therefore declares activity_history as ready when it has an answer for every window and Garmin accepted at least one. Files can still arrive after that: usually for minutes to hours, and SweatStack promises no number.