[![GitHub Release][releases-shield]][releases] [![GitHub Activity][commits-shield]][commits] ![License][license-shield] ![Project Maintenance][maintenance-shield]
The Garmin Connect API library comes with two examples:
example.py - Simple getting-started example showing authentication, token storage, and basic API callsdemo.py - Comprehensive demo providing access to 130+ API methods organized into 13 categories for easy navigation$ ./demo.py
🏃♂️ Full-blown Garmin Connect API Demo - Main Menu
==================================================
Select a category:
[1] 👤 User & Profile
[2] 📊 Daily Health & Activity
[3] 🔬 Advanced Health Metrics
[4] 📈 Historical Data & Trends
[5] 🏃 Activities & Workouts
[6] ⚖️ Body Composition & Weight
[7] 🏆 Goals & Achievements
[8] ⌚ Device & Technical
[9] 🎽 Gear & Equipment
[0] 💧 Hydration & Wellness
[a] 🔧 System & Export
[b] 📅 Training plans
[c] ⛳ Golf
[q] Exit program
Make your selection:
A comprehensive Python3 API wrapper for Garmin Connect, providing access to health, fitness, and device data.
This library enables developers to programmatically access Garmin Connect data including:
Compatible with all Garmin Connect accounts. See https://connect.garmin.com/
Install from PyPI:
pip install --upgrade garminconnect curl_cffi
Clone the repo, then:
python3 -m venv .venv --copies
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -e ".[example]"
python3 ./example.py # simple getting-started example
python3 ./demo.py # comprehensive demo (130+ API methods)
This project uses PDM for dependency management and task automation.
⚠️ Important: Create a virtual environment first on externally-managed Python installs (Debian/Ubuntu) to avoid system package conflicts.
python3 -m venv .venv --copies
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install pdm
python -m pdm install --group :all
pre-commit install --install-hooks # optional but recommended
Note: Using
python -m pdminstead ofpdmavoids PATH issues on some Windows setups wherepip install pdmplaces thepdmexecutable outside the directories onPATH. Oncepdm installhas run, subsequentpdm run ...commands work normally because the venv'sScripts/directory is onPATHwhile the venv is active.
Development commands:
pdm run format # Auto-format code (isort, black, ruff --fix)
pdm run lint # Check code quality (isort, ruff, black, mypy)
pdm run codespell # Check spelling
pdm run test # Run test suite
pdm run testcov # Run tests with coverage report
pdm run all # Run all checks (lint + codespell + pre-commit + test)
pdm run clean # Clean build artifacts and cache files
pdm run build # Build package for distribution
pdm run publish # Build and publish to PyPI
pdm run --list # Show all available commands
Run pdm run format && pdm run lint && pdm run test before submitting PRs.
Authentication uses the same mobile SSO flow as the official Garmin Connect Android app. No browser is needed.
How it works:
sso.garmin.com/mobile/api/login using the Android
app's client ID. If MFA is required, a callback (prompt_mfa) prompts for the one-time code.access_token + refresh_token) via diauth.garmin.com. Tokens are stored at
~/.garminconnect/garmin_tokens.json.Session lifetime: - DI tokens auto-refresh indefinitely as long as the refresh token remains valid. - A full re-login with credentials (and possibly MFA) is only needed if the refresh token itself expires or is revoked.
Token storage:
~/.garminconnect/garmin_tokens.json # saved automatically, mode 0600
Resilient login (multi-strategy + token validation):
login() tries several authentication strategies in order (mobile, SSO widget,
web portal — each with and without TLS impersonation) and only declares success
when the resulting token is actually accepted by the API. If a strategy obtains
a token the API later rejects (a region/account-specific condition — see
#369), the
library transparently falls through to the next strategy. Set
Garmin(..., verify_login=False) to restore the legacy "first token wins"
behavior.
Cached-token gotcha & self-healing: when a tokenstore is supplied,
login() loads those tokens before the strategy chain and short-circuits if
they load — so stale/poisoned cached tokens used to fail every run. The library
now detects this: if cached tokens are rejected by the API, it discards them and
performs a fresh credential login automatically. To force a clean slate yourself
(e.g. between a failed resume and a retry), call:
g.logout() # clears in-memory auth + cached tokens (uses GARMINTOKENS)
g.logout(tokenstore) # or pass an explicit path
Run example.py once first to create saved tokens in ~/.garminconnect, then:
pdm run test # Run all tests
pdm run testcov # Run tests with coverage report
Optional: keep test tokens isolated
export GARMINTOKENS="$(mktemp -d)"
python3 ./example.py # create a fresh token file for tests
pdm run test
Note: Tests use VCR cassettes to record/replay API responses. If tests fail with
authentication errors, ensure valid tokens exist in ~/.garminconnect (run
example.py first).
For package maintainers:
Setup PyPI credentials:
pip install twine
# Edit with your preferred editor, or create via here-doc:
# cat > ~/.pypirc <<'EOF'
# [pypi]
# username = __token__
# password = <PyPI_API_TOKEN>
# EOF
[pypi]
username = __token__
password = <PyPI_API_TOKEN>
Recommended: use environment variables and restrict file perms
chmod 600 ~/.pypirc
export TWINE_USERNAME="__token__"
export TWINE_PASSWORD="<PyPI_API_TOKEN>"
Publish new version:
pdm run publish # Build and publish to PyPI
Alternative publishing steps:
pdm run build # Build package only
pdm publish # Publish pre-built package
We welcome contributions! Here's how you can help:
Before contributing:
1. Set up your dev environment (see Development above)
2. Format and lint: pdm run format && pdm run lint
3. Run tests: pdm run test
4. Follow existing code style and patterns
Explore the API interactively with our reference notebook.
import os
from datetime import date
from garminconnect import Garmin
# First run: logs in and saves tokens to ~/.garminconnect
# Subsequent runs: loads saved tokens and auto-refreshes
client = Garmin(
os.getenv("EMAIL"),
os.getenv("PASSWORD"),
prompt_mfa=lambda: input("MFA code: "),
)
client.login("~/.garminconnect")
# Get today's stats
today = date.today().isoformat()
stats = client.get_stats(today)
# Get heart rate data
hr_data = client.get_heart_rates(today)
print(f"Resting HR: {hr_data.get('restingHeartRate', 'n/a')}")
The library includes optional typed workout models for creating type-safe workout definitions:
pip install garminconnect[workout]
from garminconnect.workout import (
RunningWorkout, WorkoutSegment,
create_warmup_step, create_interval_step, create_cooldown_step,
create_repeat_group,
)
# Create a structured running workout
workout = RunningWorkout(
workoutName="Easy Run",
estimatedDurationInSecs=1800,
workoutSegments=[
WorkoutSegment(
segmentOrder=1,
sportType={"sportTypeId": 1, "sportTypeKey": "running"},
workoutSteps=[create_warmup_step(300.0)]
)
]
)
# Upload and optionally schedule it
result = client.upload_running_workout(workout)
client.schedule_workout(result["workoutId"], "2026-03-20")
# Delete a workout or remove it from the calendar
client.delete_workout(workout_id)
client.unschedule_workout(scheduled_workout_id)
Available workout classes: RunningWorkout, CyclingWorkout, SwimmingWorkout, WalkingWorkout, HikingWorkout, MultiSportWorkout, FitnessEquipmentWorkout
Helper functions: create_warmup_step, create_interval_step, create_recovery_step, create_cooldown_step, create_repeat_group
tests/ directorySpecial thanks to all contributors who have helped improve this project:
This project thrives thanks to community involvement and feedback.
If you find this library useful for your projects, please consider supporting its continued development and maintenance:
Why Support? - Keeps the project actively maintained - Enables faster bug fixes and new features - Supports infrastructure costs (testing, AI, CI/CD) - Shows appreciation for h
$ claude mcp add python-garminconnect \
-- python -m otcore.mcp_server <graph>