I Built Garmin Watch Integration While Waiting for API Access

The code is written, the matching algorithm is tested, and the personal best detector is ready. I am still waiting for Garmin to approve my API access.

What the Garmin Integration Does

When a runner finishes a workout, their Garmin watch records it. About thirty seconds later, I receive the data: distance, pace, and heart rate. I match that activity to the workout I recommended, mark it complete with the actual numbers, and check whether the runner set a personal best. The code for this is written. The OAuth flow works. The matching algorithm is tested. The one remaining problem is that Garmin has not approved my API access yet. That approval is the slow part of building an integration. I have to wait for it.

I Built the Integration Before Approval

Garmin's Connect API requires an approved developer account. You submit an application, describe your use case, and then wait for a decision. The status page shows "pending" until they respond. While waiting, I built the full integration. OAuth 2.0 with PKCE for connecting the account. A webhook endpoint that receives activity pushes. A background processor that syncs activities. A three-factor matching algorithm that pairs activities to workouts. Automatic personal best detection at six standard distances. All of it is in the codebase, implemented and tested. It needs Garmin's developer portal to approve access before it can run.

How the Three-Factor Match Works

The matching step decides which workout a Garmin activity belongs to. A runner might have several proposed workouts, or they might have done a workout a day later than scheduled. I score each candidate workout on three factors. Date proximity is weighted 40%: the same day scores a perfect match, one day off scores 0.7, two days off scores 0.3, and anything beyond that does not match. Type similarity is weighted 35%: a running activity against a running workout scores full marks, and a cycling activity against a running workout scores zero. Distance closeness is weighted 25%: within 10% of the target distance is a perfect score, within 30% is passable, and beyond 50% does not count. The best match above a 0.5 threshold wins. If no candidate clears the threshold, the activity stays unmatched, because a wrong match is worse than no match.

Automatic Personal Best Detection (Coming Soon)

This feature checks every synced activity against six standard distances: 1K, Mile, 5K, 10K, Half Marathon, and Marathon. If the activity distance is within 5% of one of those distances, I normalize the time and compare it against the runner's existing personal bests. If the activity is a new personal best, I update it automatically. The runner opens their dashboard and sees the new personal best without entering it by hand. For now, personal bests are entered manually, and that works. Automatic detection removes that step. Once Garmin approves my API access, I can turn it on.