Reproducible auth break against connectapi.garmin.com starting around 2026-06-01. Was working through 2026-05-30 on the same code/account/runner.
garminconnect: 0.3.3 (PyPI) — also confirmed against current master (= v0.3.4 tag, d206167)
Symptom: GarminConnectAuthenticationError: Failed to retrieve social profile, caused by API Error 401 on /userprofile-service/socialProfile during login().
Triangulation
Custom probe — identical auth headers (from client.get_api_headers(), including the freshly-refreshed Bearer) sent via three HTTP backends:
| Probe |
Result |
Unauth GET connect.garmin.com |
200 from any IP (edge reachable, no cf-mitigated) |
Stored-token resume + _refresh_session() against diauth.garmin.com |
succeeds, returns fresh DI bearer |
Authenticated GET /userprofile-service/socialProfile via curl_cffi (safari_ios) |
401, www-authenticate: Bearer error="invalid_token", error_description="Token is not active" |
Same call via curl_cffi (chrome120) |
401, identical www-authenticate |
Same call via plain requests |
401, identical www-authenticate |
Identical 401 + identical www-authenticate across all three transports rules out TLS fingerprint / Cloudflare interference. The DI tier issues the bearer; the API tier rejects it as not active.
What it isn't
- Not an IP block — reproduces from both a GitHub Actions runner (datacenter) and a residential IP.
- Not token-store corruption — a freshly-issued bearer via the full 5-strategy login path 401s the same way.
- Not TLS fingerprinting at the API tier — three transports with three different JA3/JA4 profiles all get the same answer.
- Not a token-rotation regression —
_refresh_session() against diauth.garmin.com succeeds; the new bearer is the one that's rejected.
Best guess
Garmin tightened API-tier bearer validation (new audience requirement, different grant flow, PKCE, mandatory client claim, etc.) in a way the current native auth engine doesn't satisfy. The error_description="Token is not active" is the standard RFC 6750 challenge — points at a server-side policy change rather than a client-side bug.
Happy to provide additional probe output (full request headers, alternate endpoints, web/JWT_WEB cookie fallback behavior) if useful.
Reproducible auth break against
connectapi.garmin.comstarting around 2026-06-01. Was working through 2026-05-30 on the same code/account/runner.garminconnect: 0.3.3 (PyPI) — also confirmed against current
master(= v0.3.4 tag,d206167)Symptom:
GarminConnectAuthenticationError: Failed to retrieve social profile, caused byAPI Error 401on/userprofile-service/socialProfileduringlogin().Triangulation
Custom probe — identical auth headers (from
client.get_api_headers(), including the freshly-refreshed Bearer) sent via three HTTP backends:connect.garmin.com_refresh_session()againstdiauth.garmin.com/userprofile-service/socialProfileviacurl_cffi(safari_ios)www-authenticate: Bearer error="invalid_token", error_description="Token is not active"curl_cffi(chrome120)www-authenticaterequestswww-authenticateIdentical 401 + identical
www-authenticateacross all three transports rules out TLS fingerprint / Cloudflare interference. The DI tier issues the bearer; the API tier rejects it as not active.What it isn't
_refresh_session()againstdiauth.garmin.comsucceeds; the new bearer is the one that's rejected.Best guess
Garmin tightened API-tier bearer validation (new audience requirement, different grant flow, PKCE, mandatory client claim, etc.) in a way the current native auth engine doesn't satisfy. The
error_description="Token is not active"is the standard RFC 6750 challenge — points at a server-side policy change rather than a client-side bug.Happy to provide additional probe output (full request headers, alternate endpoints, web/JWT_WEB cookie fallback behavior) if useful.