| 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 | |
| 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 | |
| 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). |