Skip to content
Docs menu

Activities

Listing and searching activities, their detail (splits, weather, HR/power zones, exercise sets), manual creation, file import/upload and download, and the activity/gear association.

Every method below hangs off a Garmin instance. See Installation & setup for how to construct one:

import { GarminClient, Garmin, FileTokenStore } from "garminconnect-js";

const client = new GarminClient({ tokenStore: new FileTokenStore("./tokens") });
if (!(await client.loadTokens())) throw new Error("not connected to Garmin");
const garmin = new Garmin(client);

36 methods. The verification column says what has been confirmed against a live Garmin account, not merely unit-tested — AGENTS.md carries the full evidence per method.

MethodVerified
addGearToActivitylive-verified
countActivitieslive-verified
createManualActivitylive-verified
createManualActivityFromJsonlive-verified
deleteActivitylive-verified
downloadActivitylive-verified
downloadHealthSnapshotlive-verified
getActivitieslive-verified
getActivitiesByDatelive-verified
getActivitiesForDatelive-verified
getActivitylive-verified
getActivityDetailslive-verified
getActivityEventTypeslive-verified
getActivityExerciseSetslive-verified
getActivityGearlive-verified
getActivityHrInTimezoneslive-verified
getActivityPowerInTimezoneslive-verified
getActivitySplitslive-verified
getActivitySplitSummarieslive-verified
getActivityTypedSplitslive-verified
getActivityTypeslive-verified
getActivityWeatherlive-verified
getGearActivitieslive-verified
getLastActivitylive-verified
getPersonalRecordlive-verified
getProgressSummaryBetweenDateslive-verified
importActivitylive-verified
removeGearFromActivitylive-verified
setActivityDescriptionlive-verified
setActivityEventTypelive-verified
setActivityExerciseSetslive-verified
setActivityFeellive-verified
setActivityNamelive-verified
setActivityPerceivedEffortlive-verified
setActivityTypelive-verified
uploadActivitylive-verified

addGearToActivity

garmin.addGearToActivity(gearUUID: string, activityId: number | string): Promise<GearLinkResult | null>
const result = await garmin.addGearToActivity(activityId, activityId);

Returns

GearLinkResult — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

on 404 re-raises as GarminConnectionError ("gear not found (likely retired/removed)")

Verification: live-verified

countActivities

garmin.countActivities(): Promise<number>
const result = await garmin.countActivities();

Returns

number

returns the envelope's totalCount, not the whole response; throws GarminError if Garmin returns nothing or a non-numeric totalCount

Verification: live-verified

createManualActivity

garmin.createManualActivity(startDatetime: string, timeZone: string, typeKey: string, distanceKm: number, durationMin: number, activityName: string): Promise<unknown>
const result = await garmin.createManualActivity("startDatetime", "timeZone", "typeKey", 1, 1, "activityName");

Returns

unknown — Garmin's response is passed through unparsed. Cast it to whatever you need; this library does not model it.

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) — must include milliseconds, e.g. "2026-09-22T10:00:00.000"; omitting them produced a live HTTP 500 ValueInstantiationException from Garmin during verification. Live-verified: the converted summaryDTO.distance/summaryDTO.duration were read back via getActivity and matched the expected meters/seconds exactly (5.5km/30min → 5500m/1800s)

Verification: live-verified

createManualActivityFromJson

garmin.createManualActivityFromJson(payload: Record<string, unknown>): Promise<unknown>
const result = await garmin.createManualActivityFromJson("payload");

Returns

unknown — Garmin's response is passed through unparsed. Cast it to whatever you need; this library does not model it.

sends payload to Garmin verbatim, no shape validation

Verification: live-verified

deleteActivity

garmin.deleteActivity(activityId: number | string): Promise<unknown>
const result = await garmin.deleteActivity(activityId);

Returns

unknown — Garmin's response is passed through unparsed. Cast it to whatever you need; this library does not model it.

resolves to null on success (204). Live-verified on synthetic fixtures created by the probe itself (never against pre-existing data) — create → delete → poll-confirm gone via getActivities

Verification: live-verified

downloadActivity

garmin.downloadActivity(activityId: number | string, format?: ActivityDownloadFormat): Promise<Buffer>
const result = await garmin.downloadActivity(activityId);

Returns

A Buffer of file bytes.

ActivityDownloadFormat is "ORIGINAL" | "TCX" | "GPX" | "KML" | "CSV", default "ORIGINAL"

Verification: live-verified

downloadHealthSnapshot

garmin.downloadHealthSnapshot(requestedDate: string | Date): Promise<Buffer>
const result = await garmin.downloadHealthSnapshot("2026-09-24");

Returns

A Buffer of file bytes.

routed through formatDate, routed through client.download

Verification: live-verified

getActivities

garmin.getActivities(start?: number, limit?: number): Promise<Activity[]>
const result = await garmin.getActivities();

Returns

An array of Activity:

FieldTypeAlways present
activityIdnumberyes
activityNamestringno
startTimeLocalstringno
distancenumberno
durationnumberno
activityTypeRecord<string, unknown>no

Plus every other field Garmin sends: this type carries an index signature because the real response is wider than the fields above, which are the ones this library relies on or has observed. Read an actual response before depending on a field that is not listed.

defaults start=0, limit=20

Verification: live-verified

getActivitiesByDate

garmin.getActivitiesByDate(startdate: string | Date, enddate?: string | Date, activitytype?: string, sortorder?: string): Promise<Activity[]>
const result = await garmin.getActivitiesByDate("2026-09-24");

Returns

An array of Activity:

FieldTypeAlways present
activityIdnumberyes
activityNamestringno
startTimeLocalstringno
distancenumberno
durationnumberno
activityTypeRecord<string, unknown>no

Plus every other field Garmin sends: this type carries an index signature because the real response is wider than the fields above, which are the ones this library relies on or has observed. Read an actual response before depending on a field that is not listed.

paginates internally: fetches fixed pages of 20, incrementing start by 20, until an empty page (normal end) or 2000 pages without one (throws GarminError)

Verification: live-verified

getActivitiesForDate

garmin.getActivitiesForDate(fordate: string | Date): Promise<ActivitiesForDateResponse | null>
const result = await garmin.getActivitiesForDate("2026-09-24");

Returns

ActivitiesForDateResponse — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

passes through unchecked; despite the name, the path is /mobile-gateway/heartRate/..., not an activities-service path

Verification: live-verified

getActivity

garmin.getActivity(activityId: number | string): Promise<Activity>
const result = await garmin.getActivity(activityId);

Returns

Activity:

FieldTypeAlways present
activityIdnumberyes
activityNamestringno
startTimeLocalstringno
distancenumberno
durationnumberno
activityTypeRecord<string, unknown>no

Plus every other field Garmin sends: this type carries an index signature because the real response is wider than the fields above, which are the ones this library relies on or has observed. Read an actual response before depending on a field that is not listed.

Verification: live-verified

getActivityDetails

garmin.getActivityDetails(activityId: number | string, maxchart?: number, maxpoly?: number): Promise<ActivityDetails | null>
const result = await garmin.getActivityDetails(activityId);

Returns

ActivityDetails — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

defaults maxchart=2000, maxpoly=4000, sent as maxChartSize/maxPolylineSize; passes through unchecked

Verification: live-verified

getActivityEventTypes

garmin.getActivityEventTypes(): Promise<ActivityEventType[] | null>
const result = await garmin.getActivityEventTypes();

Returns

An array of ActivityEventType:

FieldTypeAlways present
typeIdnumberyes
typeKeyActivityEventTypeKeyyes
sortOrdernumberyes

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

Verification: live-verified

getActivityExerciseSets

garmin.getActivityExerciseSets(activityId: number | string): Promise<ActivityExerciseSets | null>
const result = await garmin.getActivityExerciseSets(activityId);

Returns

ActivityExerciseSets:

FieldTypeAlways present
exerciseSetsunknown[]no

Plus every other field Garmin sends: this type carries an index signature because the real response is wider than the fields above, which are the ones this library relies on or has observed. Read an actual response before depending on a field that is not listed.

passes through unchecked

Verification: live-verified

getActivityGear

garmin.getActivityGear(activityId: number | string): Promise<ActivityGear[] | null>
const result = await garmin.getActivityGear(activityId);

Returns

An array of ActivityGear — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

passes through unchecked; returns an ARRAY, verified live

Verification: live-verified

getActivityHrInTimezones

garmin.getActivityHrInTimezones(activityId: number | string): Promise<ActivityHrInTimezones | null>
const result = await garmin.getActivityHrInTimezones(activityId);

Returns

ActivityHrInTimezones — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

passes through unchecked

Verification: live-verified

getActivityPowerInTimezones

garmin.getActivityPowerInTimezones(activityId: number | string): Promise<ActivityPowerInTimezones | null>
const result = await garmin.getActivityPowerInTimezones(activityId);

Returns

ActivityPowerInTimezones — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

passes through unchecked

Verification: live-verified

getActivitySplits

garmin.getActivitySplits(activityId: number | string): Promise<ActivitySplits | null>
const result = await garmin.getActivitySplits(activityId);

Returns

ActivitySplits — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

passes through unchecked

Verification: live-verified

getActivitySplitSummaries

garmin.getActivitySplitSummaries(activityId: number | string): Promise<ActivitySplitSummaries | null>
const result = await garmin.getActivitySplitSummaries(activityId);

Returns

ActivitySplitSummaries — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

passes through unchecked

Verification: live-verified

getActivityTypedSplits

garmin.getActivityTypedSplits(activityId: number | string): Promise<ActivityTypedSplits | null>
const result = await garmin.getActivityTypedSplits(activityId);

Returns

ActivityTypedSplits — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

passes through unchecked; richer detail than getActivitySplits for some activity types (e.g. Bouldering)

Verification: live-verified

getActivityTypes

garmin.getActivityTypes(): Promise<ActivityTypesResponse | null>
const result = await garmin.getActivityTypes();

Returns

ActivityTypesResponse = ActivityType[]

passes through unchecked; ActivityTypesResponse is ActivityType[] (154 entries observed live: {typeId, typeKey, parentTypeId, isHidden, restricted, trimmable}) — an ARRAY, verified live

Verification: live-verified

getActivityWeather

garmin.getActivityWeather(activityId: number | string): Promise<ActivityWeather | null>
const result = await garmin.getActivityWeather(activityId);

Returns

ActivityWeather — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

passes through unchecked

Verification: live-verified

getGearActivities

garmin.getGearActivities(gearUUID: string, limit?: number): Promise<GearActivity[]>
const result = await garmin.getGearActivities(activityId);

Returns

An array of GearActivity — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

limit clamped to 1000; returns [] on a 404 instead of throwing

Verification: live-verified

getLastActivity

garmin.getLastActivity(): Promise<Activity | null>
const result = await garmin.getLastActivity();

Returns

Activity:

FieldTypeAlways present
activityIdnumberyes
activityNamestringno
startTimeLocalstringno
distancenumberno
durationnumberno
activityTypeRecord<string, unknown>no

Plus every other field Garmin sends: this type carries an index signature because the real response is wider than the fields above, which are the ones this library relies on or has observed. Read an actual response before depending on a field that is not listed.

delegates to getActivities(0, 1), returns the last element or null

Verification: live-verified

getPersonalRecord

garmin.getPersonalRecord(): Promise<PersonalRecords | null>
const result = await garmin.getPersonalRecord();

Returns

PersonalRecords = PersonalRecord[]

GETs /personalrecord-service/personalrecord/prs/{displayName}; no args; passes through unchecked. 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

Verification: live-verified

getProgressSummaryBetweenDates

garmin.getProgressSummaryBetweenDates(startdate: string | Date, enddate: string | Date, metric?: string, groupbyactivities?: boolean): Promise<ProgressSummary | null>
const result = await garmin.getProgressSummaryBetweenDates("2026-09-24", "2026-09-24");

Returns

ProgressSummary — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

defaults metric="distance", groupbyactivities=true; both dates routed through formatDate; passes through unchecked

Verification: live-verified

importActivity

garmin.importActivity(file: Blob, filename: string): Promise<ImportActivityResult>
const result = await garmin.importActivity(file, "filename");

Returns

ImportActivityResult — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

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

Verification: live-verified

removeGearFromActivity

garmin.removeGearFromActivity(gearUUID: string, activityId: number | string): Promise<GearLinkResult | null>
const result = await garmin.removeGearFromActivity(activityId, activityId);

Returns

GearLinkResult — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

PUT, not DELETE; same 404-handling pattern as addGearToActivity

Verification: live-verified

setActivityDescription

garmin.setActivityDescription(activityId: number | string, description: string): Promise<unknown>
const result = await garmin.setActivityDescription(activityId, "description");

Returns

unknown — Garmin's response is passed through unparsed. Cast it to whatever you need; this library does not model it.

resolves to null on success. Live-verified: description read back via getActivity after the call

Verification: live-verified

setActivityEventType

garmin.setActivityEventType(activityId: number | string, eventType: ActivityEventTypeKey): Promise<unknown>
const result = await garmin.setActivityEventType(activityId, eventType);

Returns

unknown — Garmin's response is passed through unparsed. Cast it to whatever you need; this library does not model it.

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

Verification: live-verified

setActivityExerciseSets

garmin.setActivityExerciseSets(activityId: number | string, payload: ActivityExerciseSets): Promise<unknown>
const result = await garmin.setActivityExerciseSets(activityId, payload);

Returns

unknown — Garmin's response is passed through unparsed. Cast it to whatever you need; this library does not model it.

replace-all semantics, payload sent verbatim. See gotchas for the payload shape Garmin actually requires (undocumented by Garmin)

Verification: live-verified

setActivityFeel

garmin.setActivityFeel(activityId: number | string, feel: ActivityFeel | null): Promise<unknown>
const result = await garmin.setActivityFeel(activityId, feel);

Returns

unknown — Garmin's response is passed through unparsed. Cast it to whatever you need; this library does not model 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

Verification: live-verified

setActivityName

garmin.setActivityName(activityId: number | string, activityName: string): Promise<unknown>
const result = await garmin.setActivityName(activityId, "activityName");

Returns

unknown — Garmin's response is passed through unparsed. Cast it to whatever you need; this library does not model it.

resolves to null on success. Live-verified: value read back via getActivity after the call, not just that the request was accepted

Verification: live-verified

setActivityPerceivedEffort

garmin.setActivityPerceivedEffort(activityId: number | string, rpe: number | null): Promise<unknown>
const result = await garmin.setActivityPerceivedEffort(activityId, 1);

Returns

unknown — Garmin's response is passed through unparsed. Cast it to whatever you need; this library does not model 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

Verification: live-verified

setActivityType

garmin.setActivityType(activityId: number | string, typeId: number, typeKey: string, parentTypeId: number): Promise<unknown>
const result = await garmin.setActivityType(activityId, activityId, "typeKey", activityId);

Returns

unknown — Garmin's response is passed through unparsed. Cast it to whatever you need; this library does not model it.

resolves to null on success. Live-verified: activityTypeDTO read back via getActivity after the call

Verification: live-verified

uploadActivity

garmin.uploadActivity(file: Blob, filename: string): Promise<UploadActivityResult | null>
const result = await garmin.uploadActivity(file, "filename");

Returns

UploadActivityResult — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

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)

Verification: live-verified

Methods in this category