Skip to content

get_goals() returns empty results for goals visible in current Garmin Connect UI #431

Description

@skweeker

get_goals() returns an empty list even though I have an active goal
visible in Garmin Connect.

I originally encountered this through Taxuspt/garmin_mcp:

Taxuspt/garmin_mcp#317

However, I have reproduced the behavior directly with
python-garminconnect, outside of the MCP server.

The active goal is a cycling distance accumulation goal created through
the current Garmin Connect Goals UI. It has a custom date range and a
target of 100 miles.

Testing

I initially reproduced this using garminconnect==0.3.2, which is the
version currently pinned by Taxuspt/garmin_mcp.

goals = api.get_goals("active")
print(goals)

Result:

[]

I then tested the underlying endpoint directly:

api.connectapi(
    "/goal-service/goal/goals",
    params={
        "status": "active",
        "start": "0",
        "limit": "30",
        "sortOrder": "asc",
    },
)

Result:

[]

I also tested past and future, which both return empty lists.

Calling the endpoint without a status parameter:

api.connectapi(
    "/goal-service/goal/goals",
    params={
        "start": "0",
        "limit": "30",
        "sortOrder": "asc",
    },
)

returns:

API Error 400 - userId and status cant be null

Because of that error, I also retrieved the ID from get_user_profile()
and explicitly supplied it as userId:

api.connectapi(
    "/goal-service/goal/goals",
    params={
        "userId": str(user_id),
        "status": "active",
        "start": "0",
        "limit": "30",
        "sortOrder": "asc",
    },
)

This still returns:

[]

The same is true for past and future when userId is explicitly
supplied.

Current version

Before filing this issue, I upgraded to garminconnect==0.3.15 and
repeated the test.

The problem is still reproducible with 0.3.15. get_goals("active")
continues to return an empty list despite the goal being present and
active in the Garmin Connect UI.

Related investigation

There is also a proposed fix in Taxuspt/garmin_mcp:

Taxuspt/garmin_mcp#325

That PR proposes querying /goal-service/goal/goals without the legacy
status parameter and parsing the newer Connect UI goal format
separately.

On my account, however, querying that endpoint without status results
in:

API Error 400 - userId and status cant be null

so that approach does not appear to work against my live account as
currently implemented.

Expected behavior

get_goals("active") should return the active cycling distance goal
shown in Garmin Connect, including its target, date range, and current
progress.

Actual behavior

api.get_goals("active")

returns:

[]

despite the goal being visible and active in Garmin Connect.

Additional notes

This appears to be related specifically to goals created/displayed
through Garmin Connect's current Goals UI rather than the older goal API
behavior.

I'm happy to run additional API calls or provide sanitized response data
from my account if that would help identify the endpoint or parameters
now used by Garmin Connect.

Activity

  1. cyberjunky commented on Sep 17, 2026

    @cyberjunky
    Owner

    Really solid diagnostic work here — confirmed the current get_goals() implementation (__init__.py) does exactly what you tested manually against /goal-service/goal/goals, with the same params, same pagination. So this isn't a code bug on our side — the endpoint is being called correctly, it just doesn't have this goal.

    Conclusion: Garmin's newer Goals UI (custom accumulation goals, custom date ranges) appears to be backed by a different endpoint than the legacy /goal-service/goal/goals this library queries. The Taxuspt/garmin_mcp PR's "drop the status param" workaround doesn't apply here either — you already showed that 400s on your account, so it's not just a missing/extra param on the same endpoint; the legacy endpoint likely doesn't carry these newer goal types at all.

    I don't have a live account with one of these newer goals to discover the real endpoint against, so I can't chase this further blind. You offered to help — the most useful next step:

    1. Open the Garmin Connect website (connect.garmin.com), not the mobile app.
    2. Open browser DevTools → Network tab, filter by Fetch/XHR.
    3. Navigate to the page showing this cycling distance goal.
    4. Find the request that loads it — endpoint path, method, query params, and (sanitized, no personal values needed) response shape.

    That'll tell us the real endpoint and field names to add support for. Happy to help wire it up once we know what it actually is.

  2. skweeker commented on Sep 17, 2026

    @skweeker
    ContributorAuthor

    Looks like it does a GET to https://connect.garmin.com/gc-api/goal-service/goal/goals?status=active

    Response:
    [{"id":41139679,"name":"GCC 2026","type":"distance_accumulation","distanceInMeters":160934.0,"durationInSeconds":null,"caloriesInKiloCalories":null,"numberOfActivities":null,"startDate":"2026-09-01","activityType":"cycling","period":"one_month","privacy":"public","progress":{"percent":17,"days":17,"distanceInMeters":28089.0,"durationInSeconds":null,"caloriesInKiloCalories":null,"numberOfActivities":null},"remaining":{"percent":83,"days":13,"distanceInMeters":132845.0,"durationInSeconds":null,"caloriesInKiloCalories":null,"numberOfActivities":null},"overage":{"percent":0,"days":0,"distanceInMeters":0.0,"durationInSeconds":null,"caloriesInKiloCalories":null,"numberOfActivities":null},"endDate":"2026-09-30","active":true,"completed":false}]

    It also does a GET to https://connect.garmin.com/gc-api/goal-service/goal/goals?status=future and https://connect.garmin.com/gc-api/goal-service/goal/goals?status=past&start=1&limit=100&sortOrder=desc

  3. skweeker commented on Sep 17, 2026

    @skweeker
    ContributorAuthor

    I narrowed this down further. The Sec-Fetch-Site: same-origin request header appears to be significant.

    Using the request copied from Garmin Connect as cURL returns my active goal as expected. If I remove only:

    -H 'Sec-Fetch-Site: same-origin'

    the request no longer returns my active goal.

    Restoring that header causes the goal to be returned again.

    This may explain why the Garmin Connect web UI returns goals while python-garminconnect receives an empty response from the same /goal-service/goal/goals?status=active endpoint.

  4. cyberjunky commented on Sep 18, 2026

    @cyberjunky
    Owner

    That's the root cause — great isolation work with the header-by-header curl test. Confirmed: `get_goals()` now sends `Sec-Fetch-Site: same-origin` explicitly. PR: #434

    Browsers send that header automatically on any same-origin fetch; this client doesn't send any `Sec-Fetch-*` headers otherwise since it presents as a native/API client, not a browser page — so it was silently missing exactly the one header the newer goal-type response apparently gates on.

  5. skweeker commented on Sep 18, 2026

    @skweeker
    ContributorAuthor

    Confirmed the Sec-Fetch-Site: same-origin change in 0.3.16 is necessary and works, but I found a second issue preventing get_goals() from returning goals.

    The start parameter appears to be 1-based rather than 0-based.

    Testing through api.connectapi() with the new header:
    status=active -> goal returned
    status=active&start=0 -> []
    status=active&start=1 -> goal returned
    status=active&limit=30 -> goal returned
    status=active&sortOrder=asc -> goal returned
    status=active&start=0&limit=30 -> []
    status=active&start=1&limit=30 -> goal returned
    status=active&start=0&limit=30&sortOrder=asc -> []
    status=active&start=1&limit=30&sortOrder=asc -> goal returned

    api.get_goals("active") still returns [] because it currently starts pagination at 0.

    I also tested the exact past-goals request observed in Garmin Connect:
    status=past&start=1&limit=100&sortOrder=desc
    and it successfully returned my historical goals.

    It therefore looks like get_goals() needs to start pagination at 1 instead of 0.

  6. added a commit that references this issue on Sep 20, 2026
    1184e34
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