Skip to content

Valid DI token gets 403 on every connectapi call: API session uses plain requests, which Cloudflare rejects (curl_cffi works) #444

Open

Description

@cwm12345

Version: garminconnect 0.3.16, curl_cffi 0.15.0, Python 3.12, Windows 11

Symptom: Portal login + MFA succeeds and a DI token is issued (scopes include CONNECT_READ), but every connectapi call returns 403 {"message":"HTTP 403 Forbidden","error":"ForbiddenException"}: socialProfile, user-settings, devices, hrv, training readiness, body battery and stress. With the default verify_login=True, _verify_token() treats this as a rejected token and discards it, so the login looks like it failed ("token rejected by API tier after MFA"). This happened on 4 consecutive MFA logins.

Cause: same saved token, same URL (/userprofile-service/socialProfile), same native headers:

  • requests.get(...) → 403
  • curl_cffi.requests.get(..., impersonate="chrome") → 200 with full profile

Login strategies already use curl_cffi, but Client._api_session (used by _run_request) is a plain requests.Session, so all data calls carry the requests TLS fingerprint.

Workaround:

from curl_cffi import requests as cffi_requests
client = Garmin(email, password, prompt_mfa=..., verify_login=False)
client.client._api_session = cffi_requests.Session(impersonate="chrome")

After this, every endpoint returns 200 and token refresh works without re-login.

Suggested fix: when HAS_CFFI, build _api_session as a curl_cffi session (as the login strategies already do), so _verify_token() isn't testing through a different client than the one that got the token.

Related, smaller: _do_mobile_login reports every 429 as "IP rate limited by Garmin". With fake credentials, the same endpoint returned 429 to the library's fingerprints (iOS UA / safari_ios TLS) but a normal 401 invalid-password to other clients on the same IP, and the 429s also happened on IPs that had never been used. It seems to be fingerprint-based rather than per-IP, which sends users chasing IP cooldowns. Possibly the cause of the "follows me across IPs" reports in #369.

Activity

  1. cyberjunky commented on Sep 29, 2026

    @cyberjunky
    Owner

    Thanks for the detailed analysis. API calls have always used plain requests and work fine for most users (including from my own connection today), so this looks environment-specific — are you on a VPN, datacenter or corporate network? I'd rather not switch the default session: curl_cffi doesn't support files=, so all uploads (activities, weight/body composition) would break — note that your workaround has the same limitation. If this turns out to affect more people, I'll look at an opt-in impersonation setting for API calls. Good point on the 429 message, that's reworded in #449 so it no longer suggests it's always IP-based.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions