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¶
- Apply to the Garmin Connect Developer Program.
- Create an app in Garmin's developer portal.
- Copy the app's client id and client secret. Garmin's endpoint configuration calls them the consumer key and the consumer secret.
-
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¶
- Run
npm run status. It prints no secrets, and it says what is missing. - Open your authorize URL in a private window, with
integration=garmin_connectandprompt=login. - Sign in at Garmin, and grant every permission.
- Sync an activity from a Garmin device or from Garmin Connect.
- Run
npm run statusagain.Last fileshows the time of that activity's ping, andFailuresis empty. - 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.