npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dofek/garmin-connect

v0.1.4

Published

Unofficial Garmin Connect API client using the internal SSO + OAuth authentication flow

Downloads

483

Readme

@dofek/garmin-connect

Unofficial TypeScript client for private Garmin Connect web/mobile endpoints. It supports the observed SSO-to-OAuth flow and reads activities, sleep, daily health data, and training metrics.

This package is not affiliated with, endorsed by, or supported by Garmin. Private endpoints and authentication pages may change without notice. For a supported commercial integration, Garmin offers the official Garmin Connect Developer Program; this package does not use that program.

Requirements and installation

Requires Node.js 22.14 or newer and its built-in fetch implementation.

npm install @dofek/garmin-connect

Quick start

Save this as example.mjs, set GARMIN_EMAIL and GARMIN_PASSWORD, then run node example.mjs.

import { GarminConnectClient } from "@dofek/garmin-connect";

const email = process.env.GARMIN_EMAIL;
const password = process.env.GARMIN_PASSWORD;
if (!email || !password) {
  throw new Error("Set GARMIN_EMAIL and GARMIN_PASSWORD");
}

const { client, tokens } = await GarminConnectClient.signIn(email, password);
const activities = await client.getActivities(0, 10);

console.log(
  activities.map(({ activityId, activityName }) => ({
    activityId,
    activityName,
  })),
);

// Store this value in encrypted storage, not in source control.
const credentialsToPersist = client.getTokens() ?? tokens;
console.log(`OAuth2 expires at ${credentialsToPersist.oauth2.expires_at}`);

Dates passed to daily and range methods use YYYY-MM-DD.

Public API

Authentication and profile:

  • GarminConnectClient.signIn(email, password, domain?, fetch?)
  • GarminConnectClient.fromTokens(tokens, domain?, fetch?)
  • client.getTokens(), client.getDisplayName(), and client.getUserSettings()

Activities and files:

  • getActivities, getActivityDetail, and downloadFitFile

Daily health:

  • getDailySummary, getSleepData, getDailyHeartRate, getDailyStress, getBodyBatteryDaily, getBodyBatteryEvents, getHrvSummary, getDailyRespiration, getDailySpO2, getDailyIntensityMinutes, getDailySteps, and getFloors

Training:

  • getTrainingStatus, getTrainingReadiness, getVo2Max, getRacePredictions, getHillScore, and getEnduranceScore

Supported deep imports:

  • @dofek/garmin-connect/client — client, throttle guidance constant, and Garmin-specific error classes.
  • @dofek/garmin-connect/parsing — normalized activity, sleep, daily metric, training, HRV, stress, heart-rate, and activity-stream parsers.
  • @dofek/garmin-connect/sports — sport mapping table and mapper.
  • @dofek/garmin-connect/types — raw API and token interfaces.
  • @dofek/garmin-connect/oauth1 — low-level OAuth 1.0 header construction.
import { parseConnectSleep } from "@dofek/garmin-connect/parsing";
import type { GarminTokens } from "@dofek/garmin-connect/types";

Authentication and persistence

The current implementation:

  1. Loads Garmin's SSO pages and extracts their CSRF token and cookies.
  2. Submits the user's email and password and extracts an SSO ticket.
  3. Exchanges that ticket for OAuth1 credentials.
  4. Exchanges OAuth1 credentials for an OAuth2 bearer token.

OAuth consumer credentials are not embedded in this package. The current implementation fetches them at runtime from the public thegarth.s3.amazonaws.com/oauth_consumer.json resource used by the observed flow. The package does embed observed Garmin mobile-app user-agent identifiers.

Persist the complete GarminTokens result in encrypted storage. fromTokens and authenticated API calls exchange the saved OAuth1 credential for a new OAuth2 token when expires_at has passed. Call getTokens() after requests and replace the stored value so refreshed OAuth2 metadata and displayName are retained. If the OAuth1 credential is no longer accepted, sign in again.

Interactive MFA is not implemented. Accounts whose SSO response requires MFA receive GarminMfaRequiredError; this client cannot complete that challenge. Never log or commit passwords or token objects.

Rate limits and errors

The client exports:

  • GarminMfaRequiredError and GarminAuthError for authentication failures.
  • GarminRateLimitError for 429 responses handled by the JSON API and OAuth exchange paths. It extends ProviderRateLimitError and exposes retryAfterSeconds, parsed from HTTP Retry-After when available.
  • GarminApiError for other API failures, with statusCode. The FIT download path reports all unsuccessful responses, including a possible 429, as GarminApiError.
import {
  GARMIN_CONNECT_THROTTLE_MS,
  GarminApiError,
  GarminRateLimitError,
} from "@dofek/garmin-connect";

try {
  await client.getDailySummary("2026-07-01");
} catch (error) {
  if (error instanceof GarminRateLimitError) {
    console.error(
      `Retry after ${error.retryAfterSeconds ?? "an unspecified delay"} seconds`,
    );
  } else if (error instanceof GarminApiError) {
    console.error(`Garmin request failed with ${error.statusCode}`);
  }
  throw error;
}

// The constant is guidance; the client does not queue requests for callers.
await new Promise((resolve) => setTimeout(resolve, GARMIN_CONNECT_THROTTLE_MS));

Parsing behavior

Activity durations and sleep durations use different upstream units; the exported parsers apply the conversions expected by the currently observed responses. Sleep-stage parsing overlays the dedicated REM series on Garmin's sleep-level series, and stress parsing omits negative sentinel values.

Project