Releases: cyberjunky/python-garminconnect
Release 0.3.5
What's Changed
This release includes a security fix (please upgrade), major login
resilience improvements, two new activity-editing methods, and bug fixes.
🔒 Security
- Token store hardening (GHSA-wjhr-76vg-2hvc, CWE-732, High).
Client.dump()
previously wrotegarmin_tokens.jsonunder the process umask, leaving it
world-readable (0o644) on the default Linux umask. The file holds the DI
refresh token, so any local user on a shared host could read it and gain
persistent access to the account. Tokens are now written0o600inside a
0o700directory (withO_NOFOLLOWand a defensivechmod), regardless of
umask. Affected: ≤ 0.3.4. Patched: 0.3.5. Upgrading is strongly advised
for anyone storing tokens on a multi-user system.
✨ Login resilience (fixes #369)
- In-chain token validation. Each login strategy's token is now verified
against the API before the chain accepts it. A token the API rejects
(401/403 — an account/region-specific condition) is discarded and the next
strategy is tried automatically. Only definitive auth rejections fall
through; transient 5xx/network errors never block a working login. - Self-healing from poisoned cached tokens. If cached tokens load but the
API rejects them, they're discarded and a fresh credential login runs
automatically — fixing the long-standing footgun where a stale token cache
silently short-circuited the login chain on every run. logout()is now functional (was a deprecated no-op): clears in-memory
auth state and removes cached tokens on disk. Callable asg.logout()or
g.logout(tokenstore).- New
verify_loginoption (Garmin(..., verify_login=False)) to restore
the legacy "first token wins" behavior. - New
skip_strategieson the client to force or skip specific login
strategies — useful for diagnosing which auth path works on your account. - Cleaner failure handling: explicit Cloudflare 403 / CAPTCHA detection,
child/family-account detection,429errors preserved through the login
wrapper, and no more stack-trace dumps for expected login failures.
🆕 New API methods
set_activity_description(activity_id, description)(#367)set_activity_exercise_sets(activity_id, payload)(#368)- CN accounts: domain-aware service URLs for authentication (#366)
Both new methods are available in the interactive demo under the new
✏️ Activity Editing menu.
🐛 Fixes
get_training_readinessnow correctly annotatedlist[dict[str, Any]]— the
endpoint returns a list of snapshots, not a single dict (#361). Downstream
tooling that validates against the return type (e.g. pydantic-based MCP
servers) no longer breaks.get_morning_training_readinesskeeps defensive handling for a single-dict
response.- Typed wrapper:
typed.get_training_readiness()normalizes an empty response
to[].
🧪 Internal / tooling
- New mocked test suites for login recovery and token-store permissions; no
network required. test_strategy.py— interactive diagnostic to run each login strategy in
isolation and tee output to a log.
Upgrade
pip install --upgrade garminconnectFull Changelog: 0.3.4...0.3.5
Release 0.3.4
What's Changed
Features
-
Typed response layer (
g.typedaccessor) — an optional Pydantic namespace that wraps high-value read endpoints with validated, IDE-friendly models. Requirespydantic>=2.0; zero impact on existing users.pip install garminconnect[typed]
g = Garmin(email, password) raw = g.get_stats(date) # dict[str, Any] — unchanged stats = g.typed.get_stats(date) # DailyStats (Pydantic model) print(stats.total_steps, stats.resting_heart_rate)
Supported methods in this release:
Method Typed return get_stats,get_user_summaryDailyStatsget_sleep_dataSleepDataget_hrv_dataHrvData | Noneget_body_batterylist[BodyBatteryEntry]get_training_readinesslist[TrainingReadiness]get_activities_by_datelist[Activity]Validation failures raise
GarminConnectResponseValidationErrorwith.rawpreserved. Extra/missing fields are tolerated so schema drift doesn't break running apps. Marked experimental — the surface may evolve based on feedback.
Bug Fixes
- CN account token refresh — DI OAuth2 token endpoint now routes to
diauth.garmin.cnforis_cn=Trueaccounts. Previously the.comendpoint was always used, causing refresh failures (invalid_grant) after the ~4-hour access token TTL expired. Cron-driven sync jobs and long-running sessions on CN accounts are now fixed. (PR #360 by @MidnightV1)
Contributors
- @sorlen008 — typed namespace implementation
- @MidnightV1 — CN DI token endpoint fix
Full Changelog: 0.3.3...0.3.4
Release 0.3.3
What's Changed
Performance
- Persistent HTTP session reuse across API calls (avoids repeated connection overhead)
- Retries enabled by default (retry_attempts=3 with exponential backoff for transient errors)
Fixes
- Correct sportTypeId for swimming workouts
- Ruff and mypy CI clean (0 errors)
Misc / Tooling
- Added -debug / -d flag on demo script for timing and HTTP visibility
- Python 3.12 minimum; tooling and workout models modernized
- Expanded mocked unit test coverage (~30 new tests)
Release 0.3.2
Release 0.3.1
What's new
Full Changelog: 0.3.0...0.3.1
Release 0.3.0
v0.3.0 — Native Authentication
This release replaces the garth dependency with a native authentication engine that uses the same mobile SSO flow as the official Garmin Connect app.
What's new
- Native DI OAuth authentication — Login, token exchange, and auto-refresh are handled internally.
- Cloudflare bypass — multiple TLS impersonation strategies (Safari, Chrome, Edge via
curl_cffi) to work around Garmin's Cloudflare blocking of programmatic logins. - Automatic token refresh — DI OAuth tokens refresh seamlessly in the background. Once logged in, sessions persist indefinitely.
- Random browser fingerprinting — login requests use randomized browser identities to avoid detection.
- New methods:
delete_workout(),unschedule_workout()(#341) - Golf API fix — golf endpoints now work correctly (#320, thanks @rcloran)
- Workout constant fix —
TargetType.HEART_RATEnow maps to the correct Garmin API value (#333) - Regression tests for workout constants to prevent future drift (thanks @ayazhankadessova, PR #340)
- Added descriptions to workouts by @miki134 in #339
Breaking changes
- Saved tokens from previous versions are not compatible. A fresh login is required after upgrading.
- New token format stored as
garmin_tokens.jsonin~/.garminconnect/. - New dependencies:
curl_cffi,ua-generator.
Install
pip install --upgrade garminconnect curl_cffi ua-generatorFull Changelog: https://github.com/cyberjunky/python-garminconnect/commits/0.3.0
Release 0.2.40
Release 0.2.39
What's Changed
- Update garth requirement from <0.6.0,>=0.5.17 to >=0.5.17,<0.7.0 by @dependabot[bot] in #319
- Bump actions/upload-artifact from 6 to 7 by @dependabot[bot] in #326
- Fixed linting in demo.py by @BernatNicolau in #325
- Add workout scheduling by @Skydler in #327
- Added nutrition service endpoints for daily food, meals, and settings by @KopyWasTaken in #323
- Added our first Golf related endpoints (see menu demo.py)
- Added import_activity method for uploads without Strava re-export
New Contributors
- @BernatNicolau made their first contribution in #325
- @Skydler made their first contribution in #327
- @KopyWasTaken made their first contribution in #323
Full Changelog: 0.2.38...0.2.29
Release 0.2.38
What's Changed
- Stricter linting and fixes
- Added get morning training readiness
- Fixed demo menu numbering issue
- Fixed get goals parameters not accepting '0'
- Added Weekly Steps, Weekly Stress and Weekly Intensity Minutes
Full Changelog: 0.2.37...0.2.38
Release 0.2.37
What's Changed
- Fixed possible MFA race condition
- Bump actions/upload-artifact from 5 to 6 by @dependabot[bot] in #314
- Fix maxpoly arg to get_activity_details() to allow 0 value by @puntonim in #313
- Added time in power zones for activities by @SebastianBurmester in #316
New Contributors
- @puntonim made their first contribution in #313
- @SebastianBurmester made their first contribution in #316
Full Changelog: 0.2.36...0.2.37