Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
GARMIN_EMAILNoOptional. Your Garmin Connect account email, for unattended sign-in. Without it, sign in with the sign_in_to_garmin tool.
GARMIN_PASSWORDNoOptional. Your Garmin Connect account password, for unattended sign-in. Without it, sign in with the sign_in_to_garmin tool.

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
sign_in_to_garminA

Sign in to Garmin Connect. Call this when another Garmin tool says you are not logged in or the session expired. It opens a sign-in page in the user's browser; the password and MFA code are entered there, never in the chat. Afterwards, ask the user to tell you when they have finished, then retry.

preview_workoutA

Check a workout and show it in readable form WITHOUT saving anything. Always call this first, show the user the result, and wait for them to confirm before create_workout. Paces are minutes as decimals (4:30 = 4.5), distances metres, times seconds.

create_workoutA

Save a workout to the user's Garmin Connect workout library. Only after the user has confirmed a preview_workout of the same spec. Look exercise names up with search_exercises first; an unknown name is rejected. Afterwards, schedule_workout (if available) puts it on the calendar and push_workout_to_device (if available) sends it to the watch. Paces are minutes as decimals (4:30 = 4.5), distances metres, times seconds.

update_workoutA

Replace an existing workout with a new spec. This OVERWRITES the whole workout; confirm with the user first, ideally after preview_workout. Paces are minutes as decimals (4:30 = 4.5), distances metres, times seconds.

search_exercisesA

Find Garmin exercise names for strength, HIIT, cardio, yoga, pilates and mobility steps. Returns CATEGORY / NAME lines; use them as exercise: { category, name }.

get_user_profileC

Cached per instance; every date-scoped endpoint needs the display name.

get_user_settingsC

Cached per instance, same eviction-on-failure pattern as getUserProfile().

display_nameA

The account's display name, which date-scoped endpoints put in their URLs. Cached.

full_nameA

The account holder's full name, from the cached user profile.

user_nameB

The account's user name, from the cached user profile.

unit_systemA

measurementSystem lives on /userprofile-service/userprofile/user-settings (under userData), not on the social profile. A convenience accessor, not a data endpoint: returns undefined rather than throwing if the payload shape is missing.

get_userprofile_settingsB

The account's profile settings record, from /userprofile-service/userprofile/settings. Distinct from getUserSettings() ("user-settings"): the two paths are one character apart. GETs /userprofile-service/userprofile/settings (SINGULAR "settings", distinct from getUserSettings's "user-settings"; the two paths are one character apart and easy to transpose) Three profile endpoints, three methods: getUserProfile() reads /userprofile-service/socialProfile (and backs displayName/fullName/userName), getUserSettings() reads /userprofile-service/userprofile/user-settings (and back…

has_connect_plusA

Whether the account has a Garmin Connect+ subscription, from the cached user profile. The methods in CONNECT_PLUS_METHODS need it and throw GarminConnectPlusRequiredError without. Whether the account has a Garmin Connect+ subscription, read from the cached user profile (/userprofile-service/socialProfile, so usually no extra request): true when hasPremiumSocialIcon is true or userRoles holds any ROLE_SP_FEATURE_n entry. The methods in CONNECT_PLUS_METHODS (searchFoods, getCustomFoods, getCustomFoodServingUnits, createCustomFood, updateCustomFood, deleteCustomFood…

get_user_summaryB

The daily summary for one date: steps, distance, calories and the rest of the day's totals.

get_statsC

Alias of getUserSummary. Alias of getUserSummary.

get_steps_dataA

Step counts for one date, as a series of time intervals; [] if there are none.

get_heart_ratesB

Heart-rate samples plus resting, min and max heart rate for one date.

get_sleep_dataB

Sleep data for one date: the night's summary and its sleep levels.

get_hrv_dataB

Heart-rate variability (HRV) readings and status for one date.

get_body_batteryA

Daily Body Battery reports for a date or a date range (enddate defaults to startdate).

get_body_battery_eventsC

Body Battery events for one date.

get_floorsB

Floors climbed and descended for one date. Throws GarminError if Garmin returns nothing.

get_daily_stepsB

Daily step totals over a date range; ranges over 28 days are fetched in chunks. Auto-chunks ranges over Garmin's 28-day-per-request limit into ≤28-day windows and concatenates; a single request within the limit.

get_daily_statsA

Per-day calorie or step totals; ranges over 28 days are chunked. GETs /usersummary-service/stats/daily/{start}/{end}?statsType=CALORIES|STEPS; statsType is exactly "CALORIES" or "STEPS" (any other value, lower-case included, is a Garmin 404). Garmin 400s when end - start > 27, so longer ranges are fetched in 28-day windows and the rows concatenated; Garmin's per-request aggregations (averages) are DROPPED rather than returned wrong for a chunked range. A range with no data returns [] (Garmin answers null). Overlaps getDailySteps/getCaloriesDaily; this one returns total/act…

get_weekly_stepsB

Weekly step totals for the weeks weeks (default 52) ending on end. weeks defaults to 52, must be a positive integer.

get_weekly_stressB

Weekly stress averages for the weeks weeks (default 52) ending on end. Same weeks default/validation as getWeeklySteps.

get_weekly_intensity_minutesB

Weekly intensity-minute totals between two dates.

get_stats_and_bodyC

getUserSummary merged with the day's body-composition averages. Merges getUserSummary with the body-composition totalAverage block, delegating to getBodyComposition (bodyComposition service) for the latter.

set_blood_pressureA

Records a blood-pressure reading (systolic 70-260, diastolic 40-150, pulse 20-250). when defaults to now. Validates systolic 70-260, diastolic 40-150, pulse (if given) 20-250, all integers; when defaults to new Date().

get_blood_pressureA

Blood-pressure readings for a date or a date range (enddate defaults to startdate).

delete_blood_pressureA

Deletes one blood-pressure reading by its version, taken from getBloodPressure. Deletes one reading by its version, taken from getBloodPressure. IRREVERSIBLE: confirm with the user before calling.

add_hydration_dataA

Logs water intake in milliliters (negative values subtract). There is no delete endpoint. Raw milliliters, magnitude capped at 10000, negative values allowed; no delete endpoint exists, so not safely round-trippable.

get_hydration_dataC

The hydration log for one date.

get_respiration_dataC

Breathing-rate data for one date.

get_spo2_dataB

Pulse-oximeter (SpO2) readings for one date. Coerces a string lastSevenDaysAvgSpO2 to a number.

get_intensity_minutes_dataC

Moderate and vigorous intensity minutes for one date.

get_all_day_stressB

All-day stress levels for one date.

get_stress_dataB

Alias of getAllDayStress. Identical URL to getAllDayStress; an alias.

get_all_day_eventsB

Daily events for one date (Garmin's dailyEvents feed).

get_sleep_dailyA

Per-night sleep stats over a date range; ranges over 28 days are fetched in chunks. Garmin's endpoint has a documented 28-day-per-request limit; ranges beyond that are auto-chunked, de-duplicated by calendarDate, and sorted.

get_rhr_dayC

Resting heart rate for one date.

get_rhr_dailyA

Resting heart rate per day over a date range, as { calendarDate, value } rows. Reshapes allMetrics.metricsMap into [{calendarDate, value}], dropping null values.

get_calories_dailyA

Active, resting and total calories per day over a date range. Merges active (metricId 22) and resting/BMR (metricId 23) series into [{calendarDate, active, resting, total}].

get_hrv_data_rangeB

Daily HRV summaries over a date range.

get_daily_summariesA

One whole-day summary per day: steps, calories, heart rate, stress, Body Battery, SpO2 and more. Ranges over a year are chunked. GETs /usersummary-service/v2/dailysummaries?startDate&endDate and unwraps data: one object per day grouped by topic (movement, calories, heartRate, intensityMinutes, stress, bodyBattery, hydration, respiration, pulseOx, jetLag), typed from PersonalApi.UserDailySummaryV2DTO. Garmin caps one request at a year ("must be P1Y or less": a 365-day span passes, 366 is a 400), so longer ranges go in 365-day windows. Days with no data are ABSENT, not…

get_daily_stressA

Daily stress level and time at each stress level over a date range; ranges over 28 days are chunked. GETs /usersummary-service/stats/stress/daily/{start}/{end}: {calendarDate, values: {overallStressLevel, restStressDuration, lowStressDuration, mediumStressDuration, highStressDuration}} per day, durations in seconds. Garmin 400s end - start > 27 ("cannot be more than 28 days"), so longer ranges go in 28-day windows; a reversed range throws GarminError before any request.

get_daily_hydrationA

Daily hydration in milliliters, with the goal, over a date range; ranges over 31 days are chunked. GETs /usersummary-service/stats/hydration/daily/{start}/{end}: {calendarDate, valueInML, goalInML} per day that has an entry (the spec's sweatLossInML/activityIntakeInML were not seen). Garmin 400s end - start > 30 ("cannot be more than 31 days"), so longer ranges go in 31-day windows.

get_spo2_acclimationA

One day's SpO2 readings with altitude acclimation. GETs /wellness-service/wellness/daily/spo2acclimation/{cdate} (Garmin's Personal API spec): the day's SpO2 averages, sleep and hourly series and the monitoring environment, typed from PersonalApi.DailySpO2AndAcclimationDTO. Distinct from getSpo2Data's path. A day with no data (even 2030-01-01) still returns the object, fields null.

count_activitiesA

The total number of activities on the account. Returns the envelope's totalCount, not the whole response; throws GarminError if Garmin returns nothing or a non-numeric totalCount.

get_activitiesC

A page of activities, newest first (start defaults to 0, limit to 20). Defaults start=0, limit=20.

get_activities_for_dateB

The activities recorded on one date. Despite the name, the path is /mobile-gateway/heartRate/..., not an activities-service path.

get_activities_by_dateB

Every activity between two dates, optionally filtered by activity type. Pages through the results internally. Paginates internally: fetches fixed pages of 20, incrementing start by 20, until an empty page (normal end) or 2000 pages without one (throws GarminError).

get_last_activityA

The most recent activity, or null if there are none. Delegates to getActivities(0, 1), returns the last element or null.

get_activityB

One activity by id; measurements are under summaryDTO. Throws GarminError if Garmin returns nothing. ActivitySummary extends PersonalApi.ActivitySummaryDTO, typed from Garmin's Personal API spec (AGENTS.md section 3).

get_activity_typesB

Every activity type Garmin knows: { typeId, typeKey, parentTypeId, ... }.

search_activitiesB

One page of activities filtered by text, type, dates, distance, duration, power, location and more. One page from /activitylist-service/activities/search/activities (the getActivities endpoint) with the filters Garmin's Personal API spec documents: search, activityType (a PARENT key, includes its subtypes; a subtype there is a 400), activitySubType, excludedActivitySubTypes, eventType, startDate/endDate, min/max distance (m), duration and elapsed duration (s), elevation gain (m), minAvgPower (W), favorite, excludeChildren, sortBy/sortOrder, requiredFields, `bound…

get_activity_workoutsB

The workouts done during an activity, with the steps the device ran. GETs /activity-service/activity/{id}/workouts: the workouts done during the activity, {index, workoutName, timeCreated, sport, manufacturer, multisportSet, steps, workoutPhrase} with the steps the device ran (PersonalApi.ActivityWorkoutDTO, matched key for key, steps included). [] for an activity done without one.

delete_activityA

Deletes an activity. IRREVERSIBLE. Resolves to null on success. IRREVERSIBLE: confirm with the user before calling.

set_activity_nameB

Renames an activity. Resolves to null on success.

set_activity_typeC

Changes an activity's type. Resolves to null on success.

set_activity_descriptionB

Sets an activity's description. Resolves to null on success.

get_activity_event_typesA

The event types setActivityEventType accepts. GETs /activity-service/activity/eventTypes: nine {typeId, typeKey, sortOrder} entries (race, recreation, specialEvent, training, transportation, touring, geocaching, fitness, uncategorized).

set_activity_event_typeA

Files an activity under an event type, e.g. "race" or "training". Partial PUT /activity-service/activity/{id} with {activityId, eventTypeDTO: {typeKey}}. The typeKey alone is enough; Garmin fills in typeId/sortOrder. Throws GarminError before any request for a key outside the nine.

set_activity_perceived_effortA

Perceived effort (RPE) 1-10, or null to clear it. Partial PUT of summaryDTO.directWorkoutRpe, which Garmin stores TIMES TEN: pass RPE 1-10 (integer), it sends 10-100; null clears it. Garmin itself rejects 150 with a 400 MEASUREMENT_NOT_VALID. Never read-modify-write the whole summaryDTO instead: PUTting its stored start-coordinate pair back is a 400.

set_activity_feelA

"How did you feel?": 0, 25, 50, 75 or 100, or null to clear it. Partial PUT of summaryDTO.directWorkoutFeel, one of 0/25/50/75/100 ("How did you feel?", very weak to very strong); null clears it. Garmin stores any number (30 read back as 30) that its apps cannot display, so the method refuses anything else.

set_activity_distanceA

Sets an activity's total distance in meters; Garmin recomputes the average speeds. Partial PUT of summaryDTO.distance in METERS (no conversion; createManualActivity is the one that takes km), the edit Garmin Connect offers for treadmill runs. Garmin recomputes averageSpeed/averageMovingSpeed from it; duration, calories and laps stay as recorded, so the laps no longer sum to the total. getActivities lags the change by a few seconds while getActivity shows it at once. Throws GarminError before any request for a non-positive or non-finite value.

create_manual_activity_from_jsonA

Creates a manual activity from a payload sent to Garmin verbatim. Sends payload to Garmin verbatim, no shape validation.

create_manual_activityB

Creates a manual activity from a distance in km and a duration in minutes. startDatetime is a local timestamp with milliseconds, e.g. "2026-09-22T10:00:00.000". converts units: distanceKm * 1000 → meters, durationMin * 60 → seconds, before building the request body; startDatetime is NOT routed through formatDate (it's a full local timestamp, not a bare calendar date) and must include milliseconds, e.g. "2026-09-22T10:00:00.000"; omitting them produced a live HTTP 500 ValueInstantiationException from Garmin during verification.

import_activityB

Imports a .fit, .gpx or .tcx file as an activity. A duplicate raises GarminConnectionError. Multipart upload to /upload-service/upload/{ext} (extension from filename, must be fit/gpx/tcx) with the load-bearing NK/origin/custom User-Agent headers that make Garmin treat it as an import rather than a device sync; a 409 is re-raised as GarminConnectionError ("Activity already exists (duplicate):...").

upload_activityA

Uploads an activity file through Garmin's plain device-sync upload path. Multipart upload to the PLAIN /upload-service/upload path, no extension suffix and none of importActivity's load-bearing import headers (ordinary device-sync-shaped upload, distinct from importActivity's spoofed-client import).

get_personal_recordA

The account's personal records, one row per record type. GETs /personalrecord-service/personalrecord/prs/{displayName}; no args PersonalRecords is PersonalRecord[], an ARRAY, verified live (the test account returned array[0]). Note the plural type name: the method name is singular but the payload is a list.

download_activityB

Downloads an activity file: "ORIGINAL" (the default), TCX, GPX, KML or CSV. ActivityDownloadFormat is "ORIGINAL" | "TCX" | "GPX" | "KML" | "CSV", default "ORIGINAL".

get_activity_splitsC

An activity's laps.

get_activity_typed_splitsC

An activity's typed splits, richer than getActivitySplits for some sports. Richer detail than getActivitySplits for some activity types (e.g. Bouldering).

get_activity_split_summariesC

Summaries of an activity's splits.

get_activity_weatherC

The weather recorded for an activity.

get_activity_hr_in_timezonesB

Time spent in each heart-rate zone during an activity.

get_activity_power_in_timezonesB

Time spent in each power zone during an activity.

get_activity_detailsB

An activity's time series and GPS track, capped at maxchart/maxpoly points. Defaults maxchart=2000, maxpoly=4000, sent as maxChartSize/maxPolylineSize.

get_activity_exercise_setsC

A strength activity's exercise sets.

set_activity_exercise_setsA

Replaces ALL of a strength activity's exercise sets with payload. replace-all semantics, payload sent verbatim. IRREVERSIBLE: confirm with the user before calling.

get_activity_gearB

The gear linked to an activity. Returns an ARRAY, verified live.

get_gear_activitiesB

Activities that used a piece of gear (limit capped at 1000); [] if it is unknown. limit clamped to 1000; returns [] on a 404 instead of throwing.

add_gear_to_activityB

Links a piece of gear to an activity. On 404 re-raises as GarminConnectionError ("gear not found (likely retired/removed)").

remove_gear_from_activityA

Unlinks a piece of gear from an activity. PUT, not DELETE; same 404-handling pattern as addGearToActivity.

get_progress_summary_between_datesB

Totals of one metric (default distance) between two dates, grouped by activity type by default. Defaults metric="distance", groupbyactivities=true.

download_health_snapshotB

Downloads the Health Snapshot recorded on a date, as raw bytes.

get_gearA

Every piece of gear on the account. userProfileNumber is the profile's numeric id. Hits /gear-service/gear/filterGear?userProfilePk=...; returns an ARRAY of gear entries, verified live. This is the dedicated gear-CRUD service, distinct from getActivityGear (activities service, reuses the same base URL with activityId instead).

create_gearA

Creates a piece of gear (shoes, bike and so on). Usage limits are given in km and minutes. POSTs /gear-service/gear/v2; defaults usageType="DISTANCE", notes=""; gearType/usageType upper-cased via validateSportKey; converts maxUsageDistanceKm -> maxUsageDistanceMeters (round(km*1000), floor 1) and maxUsageDurationMin -> maxUsageDurationSeconds (round(min*60), floor 1), the opposite direction from addWeighIn's "send raw" rule; returns the response unchecked.

get_gear_statsA

Usage totals for one piece of gear; {} if it is unknown. GETs /gear-service/gear/stats/{gearUUID}; gearUUID validated via validateUuid (hex, hyphens optional); returns {} on a 404 instead of throwing; other errors re-raised.

delete_gearA

Deletes a piece of gear and its activity history. IRREVERSIBLE. DELETE /gear-service/gear/v2/{gearUUID}, resolves null on success (204). The UUID must be HYPHENATED: getGear returns them WITHOUT hyphens and that form 404s, so this method re-inserts them for you when handed the bare 32-char form; only hand-rolled URLs hit the 404. IRREVERSIBLE: confirm with the user before calling.

set_gear_activity_defaultsA

Sets which activity types default to this gear. Uses the v2 full-record PUT Garmin's own web client uses (the older dedicated default-gear endpoint is dead). The working way to set gear defaults (the old dedicated default-gear endpoint is dead; there is no setGearDefault). Read-modify-writes the v2 record: GETs /gear-service/gear/v2/{uuid}, replaces associatedActivityTypes with [{activityTypeKey, defaultGear: true, preferredGear: false}] per key, PUTs the whole record back. Keys are lowercase ("running"), matching createGear, NOT setGearDefault's upper-cased form. [] clear… IRREVERSIBLE: confirm with the user before calling.

get_gear_defaultsA

Which gear is the default for which activity types. GETs /gear-service/gear/user/{userProfileNumber}/activityTypes; returns an ARRAY of {uuid, activityTypePk, defaultGear} entries, verified live.

list_coursesA

Every saved course, as { coursesForUser: CourseSummary[] }. GETs /web-gateway/course/owner/ (trailing slash included); returns an ENVELOPE { coursesForUser: CourseSummary[] }, not a bare array.

get_courseA

One saved course by id. GETs /course-service/course/{courseId}; a missing id is a 404 GarminHttpError ("Course not found : {id}").

import_course_gpxA

Parses a GPX file into a course WITHOUT saving it (courseId: null). Multipart POST (field file) to /course-service/course/import. Parses only, saves NOTHING: the result has courseId: null and a RESAMPLED track (13 GPX points came back as 37) with null elevations/timestamps; courseName is the GPX <name>. Only .gpx accepted (only .gpx verified); throws GarminError on an empty response.

create_courseA

Saves a course from a name and at least two geo points. Garmin computes the distance. POSTs /course-service/course with the template Garmin's web client sends. CourseInput = { name, geoPoints, coursePoints?, activityTypeId? = 1 (running), privacy? = "private" }; privacy maps to rulePK public 1 / private 2. Garmin computes distanceMeter and elevation. Throws GarminError before any request on an empty name or fewer than two geoPoints.

create_course_from_gpxB

importCourseGpx then createCourse, in one call. Name defaults to the GPX <name>, then the filename without .gpx.

update_courseA

Rename and/or change privacy. Read-modify-write of the whole record. READ-MODIFY-WRITE: GETs the record, overlays the changes, PUTs the whole record back to /course-service/course/{courseId}, the same request Garmin's own web editor sends on Save (observed 2026-09-24). Concurrent callers can clobber each other. Throws before any request if neither field is given or the name is blank. A just-created course may 429 "not yet ready".

delete_courseA

IRREVERSIBLE. A course created seconds ago may 429 "not yet ready"; retry shortly. DELETE /course-service/course/{courseId}, resolves null (204). May 429 "not yet ready" for a few seconds after creation. IRREVERSIBLE: confirm with the user before calling.

download_course_gpxB

Downloads a saved course as GPX bytes. GETs /course-service/course/gpx/{courseId} through client.download.

get_devicesA

Every device registered to the account. Undocumented per-device shape, deviceId is the field the other device methods key off of.

get_device_settingsA

One device's settings, by the deviceId from getDevices. deviceId coerced to an int, validated positive, re-stringified before being placed in the path Two-call sequence: get a deviceId from a getDevices() entry first, then pass it here.

get_primary_training_deviceC

The account's primary training device.

get_device_solar_dataA

A solar device's charging input for a date or a date range (enddate defaults to startdate). The only raising method in this group: throws GarminConnectionError if the response is falsy or missing the deviceSolarInput key; returns resp.deviceSolarInput, NOT the envelope. enddate defaults to startdate, and singleDayView is sent "true" exactly when enddate was omitted.

get_device_alarmsA

Every alarm configured on every registered device. No HTTP path of its own: calls getDevices() once, then getDeviceSettings(device.deviceId) once per device (N+1 fan-out, sequential by design; do not parallelize), concatenating each device's alarms; a device with no alarms contributes nothing, never throws for that case.

get_device_last_usedB

The device used most recently. Also used internally by pushWorkoutToDevice to resolve a missing deviceId.

get_earned_badgesA

Badges the account has earned, each with badgeImageUrls added. GETs /badge-service/badge/earned; each badge gains badgeImageUrls: { small, large }, added by this library (Garmin sends no image URL): https://connect.garmin.com/images/badges/xxhdpi/badge_<badgeUuid ?? badgeId>_sml.png (_lrg.png for large), the URL Garmin Connect's web app builds; public, no session. Every other field passes through unchanged; stays nullable (does NOT coalesce to []).

get_available_badgesA

Badges the account can still earn, each with badgeImageUrls added. GETs /badge-service/badge/available?showExclusiveBadge=true; same badgeImageUrls as getEarnedBadges; stays nullable.

get_in_progress_badgesA

Badges with progress towards them, from the earned and available lists. No HTTP path of its own: calls getEarnedBadges() and getAvailableBadges(), filters each with an in-progress predicate (progress truthy; if progress === target, only "in progress" when badgeLimitCount is set and badgeEarnedNumber < badgeLimitCount), then merges both filtered lists into a Map keyed by badgeId (available overwrites earned on collision, keeping the earned entry's position); never raises: a null from either call is treated as []. Badges carry badgeImageUrls (inherited from the two calls).

get_badge_detailA

One badge with its related series and what earned it. Works for unearned badges. GETs /badge-service/badge/detail/v3/{badgeId}, the request Garmin Connect's web app makes when a badge is opened; badgeId validated as a positive integer. Returns an OBJECT: the getEarnedBadges fields plus relatedBadges (the rest of the series, each with earnedByMe), badgeAssocType/badgeAssocDataId/badgeAssocDataName (for "activityId", the activity that earned it) and followings. An unknown id is a 400 GarminHttpError, not a 404. The badge and each relatedBadges entry gain `badgeImageU…

get_adhoc_challengesB

Ad-hoc challenges the account took part in. GETs /adhocchallenge-service/adHocChallenge/historical; start validated non-negative, limit validated positive (throws GarminError otherwise); returns an ARRAY, verified live.

get_badge_challengesA

Completed badge challenges. Garmin rejects start=0; start at 1. GETs /badgechallenge-service/badgeChallenge/completed; same start/limit validation as getAdhocChallenges; returns an ARRAY, verified live. Live discovery: Garmin's server rejects start=0 with a 400 ("start should > 0.") on this endpoint even though the client-side check allows it (non-negative); the 400 is Garmin's server, not a wrong URL; call with start>=1 in practice.

get_available_badge_challengesB

Badge challenges the account can join. Garmin rejects start=0; start at 1. GETs /badgechallenge-service/badgeChallenge/available; same start/limit validation; same array-not-dict correction and same live start=0 -> 400 discovery as getBadgeChallenges.

get_non_completed_badge_challengesA

Badge challenges joined but not completed. Garmin rejects start=0. GETs /badgechallenge-service/badgeChallenge/non-completed; same start/limit validation; same array-not-dict correction and same live start=0 -> 400 discovery as getBadgeChallenges.

get_inprogress_virtual_challengesA

Virtual challenges in progress. start must be at least 1. GETs /badgechallenge-service/virtualChallenge/inProgress; asymmetric validation: start validated POSITIVE here (rejects start=0), unlike the non-negative start on the four challenge methods above; limit validated positive; same array-not-dict correction.

get_weigh_insC

Weigh-ins between two dates.

get_weight_goalsA

The weight goals in effect between two dates, in grams. GETs /goal-service/goal/user/effective/weightgoal/{start}/{end}: {userGoals, userGoalRanges}, weights in GRAMS (PersonalApi.UserGoalsRangeDTO). No range limit found (1000 days answered); a reversed range throws before any request although Garmin itself accepts one.

add_weigh_inB

Records a weigh-in in "kg" (the default) or "lbs". when defaults to now. Defaults unitKey="kg", when=new Date().

delete_weigh_inA

Deletes one weigh-in, by date and its weightPk. IRREVERSIBLE: confirm with the user before calling.

add_weigh_in_with_timestampsA

addWeighIn with explicit local and GMT timestamps. Same raw-value, no-conversion rule as addWeighIn; the two timestamps are COUPLED: the local instant resolves from dateTimestamp if given (naive strings read as LOCAL time) else from when (defaults new Date()), and gmtTimestamp, when omitted, is derived from THAT resolved instant, never independently from when. Both supplied strings are re-formatted rather than forwarded verbatim (a naive gmtTimestamp is read as UTC).

get_daily_weigh_insB

Every weigh-in recorded on one date. GETs /weight-service/weight/dayview/{cdate}?includeAll=true.

delete_weigh_insA

Deletes the weigh-ins recorded on one date and returns how many went. IRREVERSIBLE. No HTTP path of its own: calls getDailyWeighIns, then loops deleteWeighIn per entry; returns null (deletes nothing) if there are zero entries, or more than one entry and deleteAll is not true; otherwise deletes every entry that day and returns the count. IRREVERSIBLE: confirm with the user before calling.

get_body_compositionA

Body-composition data for a date or a date range (enddate defaults to startdate). GETs /weight-service/weight/dateRange; throws GarminError if startdate > enddate getStatsAndBody (wellness service) now delegates to this instead of inlining its own copy of the same call.

add_body_compositionB

Records a weight (kg) with optional body-composition fields such as body fat and muscle mass, uploaded as a .fit weight-scale file built in memory. Builds a .fit binary in memory and uploads it via client.upload to /upload-service/upload; weight validated positive/finite, throws GarminError otherwise.

get_max_metricsB

Max metrics (VO2 max and related values) for one date. The date is repeated twice in the path (start=end=cdate).

get_max_metrics_rangeA

Max metrics (VO2 max and related values) over a date range. Throws GarminError if start > end.

get_functional_threshold_power_rangeA

Functional threshold power (FTP) history for a sport over a date range. Defaults sport="RUNNING", aggregation="daily"; sport upper-cased and validated (^[A-Z_]+$); aggregation restricted to {daily,weekly,monthly,yearly}.

get_lactate_thresholdA

Lactate threshold: the latest values (default), or history over a date range when latest is false. Defaults latest=true, aggregation="daily". TWO DIFFERENT branches: latest=true returns {speed_and_heart_rate, power} from two GETs; latest=false (requires startDate, throws otherwise) returns {speed, heart_rate, power} from three GETs.

get_training_readinessB

Training readiness entries for one date.

get_morning_training_readinessA

The training readiness score computed after waking up on a date. Delegates to getTrainingReadiness, no HTTP call of its own; filters for inputContext === "AFTER_WAKEUP_RESET", falls back to the first entry; null for a falsy or empty result.

get_endurance_scoreA

Endurance score for a date, or weekly aggregates over a date range. TWO branches by presence of enddate: no enddate hits the single-day endpoint; with enddate hits .../stats with hard-coded aggregation="weekly".

get_running_toleranceB

Running tolerance over a date range, aggregated weekly (the default) or daily. Defaults aggregation="weekly"; restricted to {daily,weekly} (narrower than the FTP/lactate methods).

get_race_predictionsA

Predicted race times: the latest, or daily/monthly history when all three args are given. TWO branches, all-or-nothing params (throws on a partial combination): no params hits .../latest/{displayName}; all three hit .../{type}/{displayName}, capped at a 366-day span.

get_training_statusC

Training status for one date.

get_fitness_age_dataC

Fitness age for one date.

get_hill_scoreB

Hill score for a date, or daily values over a date range. TWO branches by presence of enddate, same shape as getEnduranceScore but the range branch hard-codes aggregation="daily" (NOT "weekly"; do not conflate the two).

get_cycling_ftpA

The latest cycling functional threshold power (FTP). Latest value only; use getFunctionalThresholdPowerRange for history.

get_heart_rate_zonesB

Heart-rate zone profiles: DEFAULT plus any per-sport ones.

set_heart_rate_zonesA

Changes one heart-rate zone profile and returns it as stored. READ-MODIFY-WRITE of one profile: GETs /biometric-service/heartRateZones, overlays {sport? = "DEFAULT", trainingMethod?, maxHeartRate?, restingHeartRate?, lactateThresholdHeartRate?, zoneFloors?: [5 bpm]} with changeState: "CHANGED", PUTs [profile] (204), then returns the profile READ BACK. A sport with no profile starts from DEFAULT's. Setting restingHeartRate also turns off restingHrAutoUpdateUsed. Garmin does NOT recompute floors when the method or a heart rate changes. Send zoneFloors too. Floors must be stric…

delete_heart_rate_zonesA

Removes a sport's own zone profile; it falls back to DEFAULT. PUTs the stored profile back with changeState: "DELETED", which removes it so the sport falls back to DEFAULT. Refuses DEFAULT; throws for a sport with no profile instead of doing nothing. IRREVERSIBLE: confirm with the user before calling.

get_power_zonesC

Power zone profiles.

get_power_zones_for_sportB

Power zones for one sport, e.g. "CYCLING". sport upper-cased and validated the same way as getFunctionalThresholdPowerRange.

get_workoutsC

A page of the workout library (start defaults to 0, limit to 100). Defaults start=0, limit=100, stays nullable (does NOT coalesce to []).

get_workout_by_idC

One workout from the library by id.

delete_workoutA

Deletes a workout from the library. IRREVERSIBLE. IRREVERSIBLE: confirm with the user before calling.

download_workoutB

Downloads a workout as FIT-file bytes.

push_workout_to_deviceA

Sends a workout to a device. Defaults to the newest workout and the most recently used device. Multi-call: resolves a missing deviceId via /device-service/deviceservice/mylastused's userDeviceId, a missing workoutId via getWorkouts(0,1)'s first result (throws if none), then reads getWorkoutById(workoutId).workoutName for the push message on the final POST.

get_scheduled_workoutsA

The calendar for one month (month is 1-12): scheduled workouts, events and more. month is 1-12 on the way in, converted to 0-indexed on the wire; validates year>=2000, month 1-12.

get_scheduled_workout_by_idB

One scheduled workout by its scheduled-workout id. Uses a DIFFERENT base (/workout-service/schedule) than getScheduledWorkouts (/calendar-service).

get_next_scheduled_workoutA

The next scheduled workout from this month or next, or {} if there is none. Computed from two getScheduledWorkouts calls (this month + next, handling Dec->Jan rollover); returns {} if nothing matches, never throws.

schedule_workoutC

Schedules a library workout on a date.

unschedule_workoutA

Removes a workout from the calendar; the workout stays in the library. Removes the calendar entry without deleting the workout template; irreversible. IRREVERSIBLE: confirm with the user before calling.

get_adaptive_workoutA

A Garmin Coach workout by its workoutUuid (getWorkoutById cannot). GETs /workout-service/fbt-adaptive/{uuid}: a Garmin Coach workout with segments, steps and estimatedTrainingEffect. Coach workouts have no workoutId, and getWorkoutById cannot fetch them: /workout-service/workout/{uuid} is a 404. Take the uuid from getTrainingPlanWorkouts or getScheduledWorkoutSummaries.

get_scheduled_workout_summariesA

Every scheduled workout between two dates, as compact summaries. POSTs the fixed GraphQL query workoutScheduleSummariesScalar(startDate, endDate): every scheduled workout in the range, plan or self-scheduled (tpType: null), as compact rows whose scheduledWorkoutId is what unscheduleWorkout takes, or null for a Garmin Coach workout, which also has tpType: null like a self-scheduled one (tell them apart by fbtAdaptivePlanId). Dates are validated before they are spliced into the query. A GraphQL error arrives as HTTP 200 with errors, turned into a GarminError. *It LAGS writes…

get_training_plan_workoutsA

Enrolled training plans' workouts around a date, grouped by plan. POSTs GraphQL trainingPlanScalar(calendarDate, lang, firstDayOfWeek) (all three arguments are REQUIRED by Garmin) and unwraps trainingPlanWorkoutScheduleDTOS: one entry per enrolled plan, {trainingPlanId, planName, trainingPlanClassification, trainingPlanDetailsDTO, workoutScheduleSummaries}. Garmin picks the window (18 workouts across several weeks for an ITP plan); [] with no plan.

list_calendar_eventsA

Every event between two dates, inclusive, or every event on the account with no dates. Events also appear in getScheduledWorkouts' month feed as itemType event; this GETs /calendar-service/events, with startDate/endDate when both dates are given (inclusive) or no params for every event on the account; one date alone throws.

get_calendar_eventA

One calendar event by id. GETs /calendar-service/event/{id}; a missing id is a 404 GarminHttpError.

get_shared_calendar_eventA

A race from Garmin's events catalogue by its shareableEventUuid; no subscription needed. GETs /calendar-service/event/{uuid}/shareable: an event from Garmin's public events catalogue (Races & Events -> Find an Event), with locationStartPoint, subscribersCount, provider and subscribed. Works WITHOUT subscribing (subscribed: false). The plain /event/{uuid} is a 404. Events you create never get a shareable uuid (Garmin forces them private).

create_calendar_eventA

Adds a race or other event to the calendar. Always stored PRIVATE. POSTs /calendar-service/event. CalendarEventInput = {name, date, startTime?: "HH:MM", timeZoneId?, eventType?, race?, primary?, distance?: {value, unit: meter|kilometer|yard|mile}, goalTimeSeconds?, location?, url?, note?}; only name/date are required by Garmin ('addEvent.arg3.eventName' must not be blank otherwise). Garmin forces every user-created event PRIVATE and unshareable whatever is sent, sets isTrainingEvent whenever isPrimaryEvent is, and answers an unknown eventType with a 500. It also rewrote …

update_calendar_eventA

Read-modify-write of the stored event; resolves to null. READ-MODIFY-WRITE: GETs the event, overlays {name?, date?, location?, url?, note?, race?, goalTimeSeconds?} (null clears an optional one), PUTs the whole record back to /calendar-service/event/{id}; Concurrent callers can clobber each other.

delete_calendar_eventA

IRREVERSIBLE. DELETE /calendar-service/event/{id}, resolves null (204). IRREVERSIBLE: confirm with the user before calling.

get_menstrual_data_for_dateB

The menstrual-cycle day view for one date.

get_menstrual_calendar_dataA

Menstrual-cycle calendar data between two dates (Garmin allows up to 91 days). Garmin rejects windows of 92+ inclusive days; not enforced here (caller's responsibility).

get_menstrual_last_confirmedC

The last confirmed period as of a date.

get_menstrual_cycle_summaryC

A summary of the menstrual cycle containing a date.

get_menstrual_reportsA

Menstrual-cycle reports over the last 1, 6 (the default) or 12 cycles. Defaults numberOfCycles=6, nextReport=false, reportType="CYCLE", todayCalendarDate defaults to today; numberOfCycles restricted to {1,6,12} or throws GarminError.

get_pregnancy_summaryC

The pregnancy-tracking summary.

update_menstrual_daily_logA

Logs a day's cycle-tracking entry (symptoms, moods, flow, notes). Replaces whatever that day already holds; there is no undo. Needs cycle tracking set up in Garmin Connect first. full-day replace, not a merge; at least one optional field required or throws GarminError; notes: undefined preserves the existing note, notes: "" clears it (distinct, load-bearing); discharge rejects combining "NO_DISCHARGE" with any other value. IRREVERSIBLE: confirm with the user before calling.

update_menstrual_calendarA

Rewrites the period days in a date range. Replaces what the range holds; there is no undo. Needs cycle tracking set up in Garmin Connect first. full replace, not a merge; each cycleDatesLists group must be non-empty, consecutive calendar dates, and fall within [startdate, enddate], else throws GarminError. IRREVERSIBLE: confirm with the user before calling.

init_menstrual_cycle_setupA

Sets up cycle tracking from a first period start date and average period and cycle lengths. Account-level and not undoable; not meant for an account that is already set up. Does NOT also update tracking-preference settings (call updateMenstrualSettings separately for that); not intended for an already-configured account.

confirm_menstrual_period_startA

Confirms that a period started on a date, with its period and cycle lengths. There is no undo. Needs cycle tracking set up in Garmin Connect first. POSTs /periodichealth-service/menstrualcycle/{periodStartDate} directly, NOT the dayview/calendar/lastconfirmed/summary sub-paths.

update_menstrual_settingsA

Changes cycle-tracking preferences (merged into the current ones). Has no effect until cycle tracking is set up in Garmin Connect. settings must be non-empty or throws GarminError; multi-step: GETs /userprofile-service/userprofile/user-settings, overlays settings onto the current userMenstrualCycleSettings, then PUTs the same endpoint, including id if resolvable.

get_goalsA

The account's goals with a given status (active, future or past), every page fetched. Sends the Sec-Fetch-Site: same-origin header Garmin needs to return newer goal types. Defaults status="active", **start=1**, limit=30. start defaults to 1, NOT 0, because goal-service is 1-INDEXED and start=0 silently returns []; throws GarminError before any request for an invalid status. Paginated, multi-call: starting at start, fetches successive pages of limit entries (incrementing start by limit each call) until a page comes back empty/falsy, same fixed-page-size pattern as `ge…

get_golf_summaryA

A page of golf scorecard summaries. Defaults start=0, limit=100; start validated non-negative, limit validated positive (throws GarminError otherwise); query params are literally hyphenated (per-page, start), matching Garmin's own naming. An OBJECT, not an array, verified live: the test account (0 rounds recorded) returned a single pagination-envelope OBJECT {pageNumber, rowsPerPage, totalRows}, not an array.

get_golf_scorecardB

One golf scorecard in full. GETs /gcs-golfcommunity/api/v2/scorecard/detail; hyphenated query params scorecard-ids and include-longest-shot-distance (sent as the literal string "true").

get_golf_shot_dataA

Shot data for a scorecard, optionally for some holes only (e.g. "1,2,3"). GETs /gcs-golfcommunity/api/v2/shot/scorecard/{scorecardId}/hole; holeNumbers accepts commas or hyphens as separators (spaces stripped), re-joined with - before sending as the hyphenated hole-numbers param; if any requested hole number is >9, the filter is silently dropped and all 18 holes are requested instead (Garmin's endpoint drops double-digit hole numbers from a filtered query); omitting holeNumbers also fetches all 18.

get_golf_club_statsA

Per-club golf stats. Defaults limit=1000; validated positive; GETs /gcs-golfcommunity/api/v2/club/player; hyphenated query params per-page and include-stats (literal "true"). An ARRAY, verified live: the test account returned a JSON ARRAY of 17 club entries ({id, clubTypeId, shaftLength, flexTypeId, averageDistance, adviceDistance, retired, deleted, lastModifiedTime}), not a single object.

get_golf_user_statsB

Golf handicap and strokes-gained overview. GETs /gcs-golfcommunity/api/v2/player/stats; handicap and strokes-gained overview, no params.

get_nutrition_daily_food_logC

The food log for one date. GETs /nutrition-service/food/logs/{cdate}.

get_nutrition_daily_mealsC

Meals logged on one date. GETs /nutrition-service/meals/{cdate}.

get_nutrition_daily_settingsA

Nutrition settings and goals in effect on one date. GETs /nutrition-service/settings/{cdate}.

get_nutrition_food_log_rangeA

Daily food logs over a range; works without Connect+. GETs /nutrition-service/food/logs/range?startDate&endDate: {dailyNutritionSummaries}, one day-log per day that has anything logged.

search_foodsA

Requires a Garmin Connect+ subscription (check with has_connect_plus). Food catalogue search. Needs Garmin Connect+. GETs /nutrition-service/food/search?searchExpression&start&limit (defaults 0/20): {results: Food[], moreDataAvailable}; catalogue foods carry source: "FATSECRET" and several servings each.

get_custom_foodsA

Requires a Garmin Connect+ subscription (check with has_connect_plus). Your custom foods, optionally filtered by name (limit <= 20). Needs Connect+. GETs /nutrition-service/customFood with includeContent=true: {customFoods, moreDataAvailable}. limit is capped at 20 (Garmin 400s above it). There is NO get-by-id: GET /customFood/{id} is a 405, so search by name to read one back.

get_custom_food_serving_unitsA

Requires a Garmin Connect+ subscription (check with has_connect_plus). Serving units a custom food can use. Needs Connect+. GETs /nutrition-service/metadata/customFoodServingUnits (13 units).

create_custom_foodA

Requires a Garmin Connect+ subscription (check with has_connect_plus). Creates a custom food (per serving). Needs Connect+. A PUT, not a POST, to /nutrition-service/customFood; CustomFoodInput = {name, calories, servingUnit? = "G", servingSize? = 100, brand?, carbs?, protein?, fat?, fiber?, sugar?, saturatedFat?, transFat?, sodium?, cholesterol?, potassium?, calcium?, iron?, vitaminD?} per ONE serving, absolute amounts (not %DV); numbers are sent as strings, as Garmin's own client does. Returns the stored food with the foodId/servingId logFood takes.

update_custom_foodC

Requires a Garmin Connect+ subscription (check with has_connect_plus). FULL REPLACE of a custom food's definition. Needs Connect+. The same PUT carrying both ids. FULL REPLACE: a nutrient or brand left out is removed (verified: the brand was dropped).

delete_custom_foodA

Requires a Garmin Connect+ subscription (check with has_connect_plus). IRREVERSIBLE. Needs Connect+. DELETE /nutrition-service/customFood/{foodId}, resolves null. IRREVERSIBLE: confirm with the user before calling.

log_foodA

Requires a Garmin Connect+ subscription (check with has_connect_plus). Logs a catalogue or custom food into a meal. Needs Connect+. PUTs /nutrition-service/food/logs with one REGULAR_LOG item and returns the whole day's log. FoodLogInput = {date, foodId, servingId, servings? = 1, time?, meal?, source? = "GARMIN", regionCode?, languageCode?}. Pass the catalogue food's source/regionCode/languageCode for a search result. Needs a mealId, which only exists after Garmin's nutrition setup in the app (400 mealId must not be null otherwise; this method throws a clearer error first). The meal is the named one, else the one whose window holds time, …

quick_add_foodA

Requires a Garmin Connect+ subscription (check with has_connect_plus). Logs an entry by name and macros ("Quick Add"). Needs Connect+. PUTs /nutrition-service/food/logs/quickAdd: an entry by name + calories/carbs/protein/fat with no food behind it (QUICK_ADD). Same meal rules as logFood.

delete_food_logsA

Requires a Garmin Connect+ subscription (check with has_connect_plus). Deletes log entries from one day, any number at once. IRREVERSIBLE. DELETE /nutrition-service/food/logs/{date} with {logIds} as the body: any number in ONE call, regular and quick-add alike. logIds are on the entries of getNutritionDailyFoodLog. Needs Connect+. IRREVERSIBLE: confirm with the user before calling.

get_training_plansB

The account's training plans. GETs /trainingplan-service/trainingplan/plans; no params.

get_training_plan_by_idC

One phased training plan by id. GETs /trainingplan-service/trainingplan/phased/{planId}.

get_adaptive_training_plan_by_idA

One adaptive (Garmin Coach) training plan by id. GETs /trainingplan-service/trainingplan/fbt-adaptive/{planId}, a distinct sub-path from getTrainingPlanById's phased path.

delete_training_planA

Quits an enrolled plan ("Quit Plan"); its workouts leave the calendar. IRREVERSIBLE. DELETE /trainingplan-service/trainingplan/trainingplan/{planId} (the trainingplan segment really is doubled): the request Garmin Connect's "Quit Plan" sends, observed in the web client on 2026-10-06. Resolves null (204); the plan's scheduled workouts leave the calendar, completed activities stay. A second call is a 404 "Training plan not found with ID: …". IRREVERSIBLE: confirm with the user before calling.

get_lifestyle_logging_dataB

Lifestyle logging entries for one date. GETs /lifestylelogging-service/dailyLog/{cdate} Grouped under misc per the plan's explicit instruction, even though it superficially resembles a wellness-daily endpoint.

request_reloadA

Asks Garmin to reload a day's wellness data that it has offloaded. POSTs /wellness-service/wellness/epoch/request/{cdate} with no JSON body; asks Garmin to reload/recompute a day's data (Garmin offloads older data, so this forces it back).

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

TDQS

C2.9/5.0

Scored across 198 tools

Disambiguation2/5

Many tools target closely related Garmin data, and several are explicit aliases or near-duplicates, such as get_stats for get_user_summary and get_stress_data for get_all_day_stress. The activity, daily-stats, weight, workout/scheduling, and gear families contain multiple overlapping read methods, so tool selection remains error-prone despite detailed descriptions.

Naming Consistency4/5

Most names use a predictable snake_case verb_noun pattern with get_/set_/create_/delete_/add_/remove_ prefixes. Deviations such as bare accessors (display_name, full_name, user_name, unit_system) and get_userprofile_settings vs get_user_settings are minor but noticeable.

Tool Count1/5

198 tools is far beyond a coherent single-server surface and forces an agent to navigate enormous, partially redundant families. Even for a broad Garmin Connect domain, exposing nearly every low-level endpoint and alias as a separate tool is an extreme mismatch.

Completeness4/5

The surface covers an unusually wide range of Garmin Connect domains: activities, wellness metrics, body composition, gear, workouts, courses, calendar, badges, nutrition, menstrual health, training plans, and devices. Some lifecycle gaps remain, such as limited write support for profile/settings and power zones, but most core workflows are present.

Maintenance

ActivityActive
ResponsivenessNo recent community issues