Skip to main content

Services Catalog

Every connector you connect to Attlaz exposes a set of operations — typed commands you can invoke via the Services API or MCP.

This page is generated directly from the Services API at build time, so it always matches the deployed contract. The schemas below are the same ones validated server-side.

Calling an operation

POST https://api.attlaz.com/services/{adapterConnectionId}/{command}
Authorization: Bearer {TOKEN}
Content-Type: application/json

{
"spreadSheetId": "...",
"range": "Sheet1!A1:B10"
}

The discovery endpoint GET /services/{adapterConnectionId}/capabilities returns the same schemas scoped to a single connection.

Available services

Last updated: 05/08/2026, 23:05:32

garmin

getActivitiesInRange

Compact one-row-per-activity list across a date range. Use this for ANY multi-week, multi-month, or multi-year training analysis — way smaller than fanning out getRecentActivities, and date-bounded so the agent doesn't have to guess limit. Returns: { summaries: [{activityId, name, startTimeLocal, sport, distanceKm, durationMin, avgHr, maxHr, calories, elevationGainM, aerobicTrainingEffect, anaerobicTrainingEffect, trainingLoad, trainingEffectLabel}, ...], startDate, endDate }. Ordered oldest → newest. ~14 fields per activity, ~400 bytes each. 60 activities (a typical 3-month range for a regular trainer) ≈ 25 KB. 2,000 activities (decade-scale for an active user) ≈ 800 KB — still feasible but consider whether you actually need every activity in a 10-year window or just trends. Uses Garmin's native server-side date filter, so we ONLY fetch activities in your range — no waste, no 200-cap truncation. Paginates automatically up to ~10,000 activities (well past any realistic user). Range cap is 3650 days (~10 years). When you need full details for ONE activity from the list, call getActivity with the activityId. Don't loop getActivity for trend analysis — call this once instead. Example (last 3 months): getActivitiesInRange({startDate: "2026-02-25", endDate: "2026-05-25"}) → { summaries: [{activityId: 22914..., name: "Easy Run", sport: "running", distanceKm: 6.27, durationMin: 32, avgHr: 149, ...}, ...], startDate: "2026-02-25", endDate: "2026-05-25" }

Authentication: accessToken

Parameters

NameTypeRequiredDescription
startDatestringyesISO date YYYY-MM-DD. Inclusive.
endDatestringyesISO date YYYY-MM-DD. Inclusive. Must be ≥ startDate and ≤ 3650 days after.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"startDate": {
"type": "string",
"description": "ISO date YYYY-MM-DD. Inclusive."
},
"endDate": {
"type": "string",
"description": "ISO date YYYY-MM-DD. Inclusive. Must be ≥ startDate and ≤ 3650 days after."
}
},
"required": [
"startDate",
"endDate"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"summaries": {
"type": "array",
"items": {
"type": "object",
"properties": {
"activityId": {
"type": "number"
},
"name": {
"type": "string"
},
"startTimeLocal": {
"type": "string"
},
"sport": {
"type": "string"
},
"distanceKm": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"durationMin": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"avgHr": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"maxHr": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"calories": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"elevationGainM": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"aerobicTrainingEffect": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"anaerobicTrainingEffect": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"trainingLoad": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"trainingEffectLabel": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"activityId",
"name",
"startTimeLocal",
"sport",
"distanceKm",
"durationMin",
"avgHr",
"maxHr",
"calories",
"elevationGainM",
"aerobicTrainingEffect",
"anaerobicTrainingEffect",
"trainingLoad",
"trainingEffectLabel"
],
"additionalProperties": false
}
},
"startDate": {
"type": "string"
},
"endDate": {
"type": "string"
}
},
"required": [
"summaries",
"startDate",
"endDate"
],
"additionalProperties": false
}

getActivity

Get the full record for one activity by id (returned by getRecentActivities). Returns everything from the activity summary plus per-lap times, splits, training effect (aerobic + anaerobic), training load, device used, GPS-bounded location, and per-sport metrics (cadence/power on bikes, stride/ground-contact on runs, swim stroke type, etc.). include: 'summary' (DEFAULT): same noise-stripping as getRecentActivities. Keeps every field useful for analysis but drops owner avatars, social flags, dead sport-specific fields. include: 'full': Garmin's raw response unchanged. Does NOT return second-by-second sensor streams (HR/sec, GPS/sec, power/sec) — those live in a separate Garmin endpoint not wrapped in v1. Example: getActivity({activityId: 12345678901}) → { activityId: ..., activityName: "Morning Run", distance: 8420, duration: 2640, averageHR: 152, maxHR: 178, calories: 612, aerobicTrainingEffect: 3.4, splits: [...], ... }

Authentication: accessToken

Parameters

NameTypeRequiredDescription
activityIdunrecognised (anyOf, description)yesGarmin activity id as returned by getRecentActivities.
include"summary" | "full"no"summary" (default) = noise-stripped. "full" = Garmin raw record.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"activityId": {
"anyOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Garmin activity id as returned by getRecentActivities."
},
"include": {
"description": "\"summary\" (default) = noise-stripped. \"full\" = Garmin raw record.",
"type": "string",
"enum": [
"summary",
"full"
]
}
},
"required": [
"activityId"
],
"additionalProperties": false
}

getDailySteps

Total step count for a single day. Returns just the step number — no breakdown by hour. For multi-day trends use getDailyStepsRange (one call, one summary per day). Date defaults to today (in the Garmin account's configured timezone, usually local). Example: getDailySteps({date: "2026-05-22"}) → { date: "2026-05-22", steps: 8421 } Example (today): getDailySteps({}) → { date: "2026-05-23", steps: 5103 }

Authentication: accessToken

Parameters

NameTypeRequiredDescription
datestringnoISO date YYYY-MM-DD. Omit for today.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"date": {
"description": "ISO date YYYY-MM-DD. Omit for today.",
"type": "string"
}
},
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "ISO YYYY-MM-DD."
},
"steps": {
"type": "number",
"description": "Total steps for the day. 0 if no device synced."
}
},
"required": [
"date",
"steps"
],
"additionalProperties": false
}

getDailyStepsRange

Step totals across a date range, one row per day. Use this for any multi-day step question. Uses Garmin's native server-side range endpoint. Garmin caps each underlying request at 28 days, so we chunk any wider range into ≤28-day windows and fire them in parallel — the agent makes one logical call regardless. Outer range cap is 3650 days (~10 years), though very wide ranges will fan out into many parallel HTTP calls under the hood (~14 chunks for a year, ~130 for a decade). Returns: { summaries: [{date, steps, distanceMeters, stepGoal}, ...], startDate, endDate }. Ordered oldest → newest. Bonus fields (free from Garmin): `distanceMeters` (total walking/running distance covered, computed from step count + stride length) and `stepGoal` (the user's daily step target on that date — useful for "did I hit my goal?" questions). Example (last 7 days): getDailyStepsRange({startDate: "2026-05-17", endDate: "2026-05-23"}) → { summaries: [{date: "2026-05-17", steps: 8421, distanceMeters: 6234, stepGoal: 8017}, ...], startDate: "2026-05-17", endDate: "2026-05-23" }

Authentication: accessToken

Parameters

NameTypeRequiredDescription
startDatestringyesISO date YYYY-MM-DD. Inclusive.
endDatestringyesISO date YYYY-MM-DD. Inclusive. Must be ≥ startDate and ≤ 3650 days after.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"startDate": {
"type": "string",
"description": "ISO date YYYY-MM-DD. Inclusive."
},
"endDate": {
"type": "string",
"description": "ISO date YYYY-MM-DD. Inclusive. Must be ≥ startDate and ≤ 3650 days after."
}
},
"required": [
"startDate",
"endDate"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"summaries": {
"type": "array",
"items": {
"type": "object",
"properties": {
"date": {
"type": "string"
},
"steps": {
"type": "number",
"description": "Total steps for the day. 0 if no device synced."
},
"distanceMeters": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Distance walked/run in meters. Null on devices that don't compute it."
},
"stepGoal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "The user's daily step target for that date. Useful for goal-comparison."
}
},
"required": [
"date",
"steps",
"distanceMeters",
"stepGoal"
],
"additionalProperties": false
},
"description": "One row per day, oldest first."
},
"startDate": {
"type": "string"
},
"endDate": {
"type": "string"
}
},
"required": [
"summaries",
"startDate",
"endDate"
],
"additionalProperties": false
}

getHeartRate

HR data for one day. ⚠️ PAYLOAD SIZE — defaults to summary mode. Garmin's raw daily HR includes a ~700-point time series (`heartRateValues`, 2-minute sampling). That's the field that blows up context budgets when fanning out across many days. include: 'summary' (DEFAULT): restingHeartRate, minHeartRate, maxHeartRate, lastSevenDaysAvgRestingHeartRate, and timestamps. ~10 fields. Right answer for "what's my resting HR?", "max HR yesterday?", "resting HR trend?". include: 'full': adds the heartRateValues array (700ish [timestamp, bpm] pairs) and heartRateValueDescriptors. Use this for "show me my HR shape through the day" or to find a specific elevated period. For multi-day RHR trends use getHeartRateRange (one call, one summary per day). Resting HR is computed overnight and may be null for the current day until morning sync. Example: getHeartRate({date: "2026-05-22"}) → { calendarDate: "2026-05-22", restingHeartRate: 54, minHeartRate: 48, maxHeartRate: 162, lastSevenDaysAvgRestingHeartRate: 53, ... } Example with full: getHeartRate({date: "2026-05-22", include: "full"}) → above + heartRateValues: [[ts, bpm], ...] ~700 entries

Authentication: accessToken

Parameters

NameTypeRequiredDescription
datestringnoISO date YYYY-MM-DD. Omit for today. Resting HR for today is often null until next overnight sync.
include"summary" | "full"no"summary" (default) = resting/min/max/baseline only (~10 fields). "full" = adds the ~700-point time series; ~50× larger payload. Use full only for intra-day shape questions.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"date": {
"description": "ISO date YYYY-MM-DD. Omit for today. Resting HR for today is often null until next overnight sync.",
"type": "string"
},
"include": {
"description": "\"summary\" (default) = resting/min/max/baseline only (~10 fields). \"full\" = adds the ~700-point time series; ~50× larger payload. Use full only for intra-day shape questions.",
"type": "string",
"enum": [
"summary",
"full"
]
}
},
"additionalProperties": false
}

getHeartRateRange

One HR summary row per day across a range. Use this for any multi-day HR question — "how has my resting HR trended this month?", "any days with unusually high max HR?". Returns: { summaries: [{date, restingHeartRate, minHeartRate, maxHeartRate, lastSevenDaysAvgRestingHeartRate}, ...], startDate, endDate }. Ordered oldest → newest. Resting HR values can be null for very recent days (Garmin computes overnight). Per-day intra-day shape is NOT included — use getHeartRate with include: 'full' for that on a specific date. Range capped at 92 days. Both endpoints inclusive. Example (last 14 days): getHeartRateRange({startDate: "2026-05-10", endDate: "2026-05-23"}) → { summaries: [{date: "2026-05-10", restingHeartRate: 52, minHeartRate: 47, maxHeartRate: 158, lastSevenDaysAvgRestingHeartRate: 53}, ...], startDate: "2026-05-10", endDate: "2026-05-23" }

Authentication: accessToken

Parameters

NameTypeRequiredDescription
startDatestringyesISO date YYYY-MM-DD. Inclusive.
endDatestringyesISO date YYYY-MM-DD. Inclusive. Must be ≥ startDate and ≤ 92 days after.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"startDate": {
"type": "string",
"description": "ISO date YYYY-MM-DD. Inclusive."
},
"endDate": {
"type": "string",
"description": "ISO date YYYY-MM-DD. Inclusive. Must be ≥ startDate and ≤ 92 days after."
}
},
"required": [
"startDate",
"endDate"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"summaries": {
"type": "array",
"items": {
"type": "object",
"properties": {
"date": {
"type": "string"
},
"restingHeartRate": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"minHeartRate": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"maxHeartRate": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"lastSevenDaysAvgRestingHeartRate": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"date",
"restingHeartRate",
"minHeartRate",
"maxHeartRate",
"lastSevenDaysAvgRestingHeartRate"
],
"additionalProperties": false
}
},
"startDate": {
"type": "string"
},
"endDate": {
"type": "string"
}
},
"required": [
"summaries",
"startDate",
"endDate"
],
"additionalProperties": false
}

getRecentActivities

List the user's most recent activities recorded on Garmin Connect, newest first. ⚠️ PAYLOAD SIZE — defaults to summary mode. Garmin's raw activity record is ~85 fields per activity, including owner avatar URLs (5 variants), social/capability flags, and sport-specific dead fields (scuba fields on a run, golf on a walk). Summary mode strips all of that. include: 'summary' (DEFAULT): noise-stripped record. Keeps id, name, sport type, timing, distance, HR, power, cadence, training effect, calories, location — every field useful for fitness analysis. ~40 fields per activity, roughly half the raw size. include: 'full': Garmin's raw record unchanged. Use only when you specifically need a field we're stripping (avatar URLs, privacy settings, etc.) — for analysis, summary mode is strictly better. When to use: "how have I been training lately?", "what did I do this week?", "find my last run". Pagination: `start` is 0-indexed from newest. start=0 limit=20 returns the 20 newest; start=20 limit=20 returns the next 20. Example: getRecentActivities({start: 0, limit: 10}) → { activities: [10 most recent records, noise-stripped], start: 0, limit: 10 }

Authentication: accessToken

Parameters

NameTypeRequiredDescription
startintegerno0-indexed offset from newest. Default 0.
limitintegernoPage size. Default 20, max 100. limit=50 in summary mode is ~50KB; limit=50 in full mode is ~200KB.
include"summary" | "full"no"summary" (default) = ~40 useful fields per activity. "full" = Garmin's raw record (~85 fields, 2× larger, includes social/avatar metadata).
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"start": {
"description": "0-indexed offset from newest. Default 0.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"limit": {
"description": "Page size. Default 20, max 100. limit=50 in summary mode is ~50KB; limit=50 in full mode is ~200KB.",
"type": "integer",
"minimum": 1,
"maximum": 100
},
"include": {
"description": "\"summary\" (default) = ~40 useful fields per activity. \"full\" = Garmin's raw record (~85 fields, 2× larger, includes social/avatar metadata).",
"type": "string",
"enum": [
"summary",
"full"
]
}
},
"additionalProperties": false
}

getSleep

Sleep record for one night. A "date" here means the calendar date sleep BEGAN — i.e. asking for 2026-05-22 returns the sleep that started Sunday evening and ended Monday morning. Garmin's own UI uses the same convention. ⚠️ PAYLOAD SIZE — defaults to summary mode. Garmin's raw sleep record includes per-minute movement, HRV, respiration, body-battery, and stage-transition arrays. Tens of KB per night. include: 'summary' (DEFAULT): just dailySleepDTO with totals, stages, scores, respiration averages, restlessness counts. The 30 numbers anyone normally wants. include: 'full': adds sleepMovement (per-minute), sleepLevels (stage transitions), wellnessEpochRespirationDataDTOList (per-epoch breaths), sleepHeartRate, sleepBodyBattery. Opt into this only for "show me my sleep stages minute-by-minute"-style questions. For multi-night trends use getSleepRange — one call, one summary per night. Sleep-score thresholds (Garmin): 90+ excellent, 80-89 good, 60-79 fair, <60 poor. Empty/sparse object returned if no sleep was recorded. Example: getSleep({date: "2026-05-22"}) → { dailySleepDTO: { sleepTimeSeconds: 27840, deepSleepSeconds: 4980, lightSleepSeconds: 14820, remSleepSeconds: 6420, awakeSleepSeconds: 1620, sleepScores: { overall: { value: 84 }, ... }, ... } }

Authentication: accessToken

Parameters

NameTypeRequiredDescription
datestringnoISO date YYYY-MM-DD representing the calendar date sleep STARTED. Omit for the most recent night.
include"summary" | "full"no"summary" (default) = stage totals + scores only (~30 fields). "full" = adds per-minute arrays (movement, HRV, respiration, etc.); 10×+ larger payload. Use full only when you specifically need minute-level detail.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"date": {
"description": "ISO date YYYY-MM-DD representing the calendar date sleep STARTED. Omit for the most recent night.",
"type": "string"
},
"include": {
"description": "\"summary\" (default) = stage totals + scores only (~30 fields). \"full\" = adds per-minute arrays (movement, HRV, respiration, etc.); 10×+ larger payload. Use full only when you specifically need minute-level detail.",
"type": "string",
"enum": [
"summary",
"full"
]
}
},
"additionalProperties": false
}

getSleepRange

One sleep summary per night across a range. Use this for trend questions ("how has my sleep been this month?", "deep-sleep trend last 2 weeks"). Returns: { summaries: [{date, sleepTimeMin, deepSleepMin, lightSleepMin, remSleepMin, awakeMin, napMin, sleepScore, avgStress, avgRespiration, awakeCount}, ...], startDate, endDate }. Ordered oldest → newest. All durations are in MINUTES (converted from Garmin's seconds for legibility). `date` is the night's start date — see getSleep for the convention. A night with no sleep recorded shows up with nulls — gaps in the data, not omitted entries, so the caller can see when sync was missed. Range capped at 92 days. Both endpoints inclusive. Example (last 7 nights): getSleepRange({startDate: "2026-05-17", endDate: "2026-05-23"}) → { summaries: [{date: "2026-05-17", sleepTimeMin: 464, deepSleepMin: 83, lightSleepMin: 247, remSleepMin: 107, awakeMin: 27, napMin: 0, sleepScore: 84, avgStress: 18, avgRespiration: 13.4, awakeCount: 2}, ...], startDate: "2026-05-17", endDate: "2026-05-23" }

Authentication: accessToken

Parameters

NameTypeRequiredDescription
startDatestringyesISO date YYYY-MM-DD. Inclusive. Night-start date convention (see getSleep).
endDatestringyesISO date YYYY-MM-DD. Inclusive. Must be ≥ startDate and ≤ 92 days after.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"startDate": {
"type": "string",
"description": "ISO date YYYY-MM-DD. Inclusive. Night-start date convention (see getSleep)."
},
"endDate": {
"type": "string",
"description": "ISO date YYYY-MM-DD. Inclusive. Must be ≥ startDate and ≤ 92 days after."
}
},
"required": [
"startDate",
"endDate"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"summaries": {
"type": "array",
"items": {
"type": "object",
"properties": {
"date": {
"type": "string"
},
"sleepTimeMin": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"deepSleepMin": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"lightSleepMin": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"remSleepMin": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"awakeMin": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"napMin": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"sleepScore": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Garmin overall sleep score (0-100). Null on older devices."
},
"avgStress": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"avgRespiration": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"awakeCount": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"date",
"sleepTimeMin",
"deepSleepMin",
"lightSleepMin",
"remSleepMin",
"awakeMin",
"napMin",
"sleepScore",
"avgStress",
"avgRespiration",
"awakeCount"
],
"additionalProperties": false
}
},
"startDate": {
"type": "string"
},
"endDate": {
"type": "string"
}
},
"required": [
"summaries",
"startDate",
"endDate"
],
"additionalProperties": false
}

getWeight

Weight measurement(s) for a single day. Weight values are in GRAMS — divide by 1000 for kg, by 453.59 for lb. Source can be a Garmin Index smart scale, a connected third-party scale, or a manual entry; Garmin doesn't distinguish. Returns empty/sparse data when no measurement is recorded. For multi-day weight trends use getWeightRange. Example: getWeight({date: "2026-05-22"}) → { date: "2026-05-22", weight: { totalAverage: { weight: 78400, bmi: 23.1, ... }, dateWeightList: [...] } }

Authentication: accessToken

Parameters

NameTypeRequiredDescription
datestringnoISO date YYYY-MM-DD. Omit for today.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"date": {
"description": "ISO date YYYY-MM-DD. Omit for today.",
"type": "string"
}
},
"additionalProperties": false
}

getWeightRange

Daily weight summary across a date range, one row per day. Returns: { summaries: [{date, weightKg, bmi, bodyFatPct, bodyWaterPct, muscleMassKg, boneMassKg}, ...], startDate, endDate }. Ordered oldest → newest. Weight values are in KILOGRAMS (converted from Garmin's native grams for legibility). Body-fat / water are percentages; muscle / bone are kg. Days with no measurement show up with all-null fields, so the caller can see the gaps and only act on real entries. Many users don't weigh daily — expect lots of null rows in typical responses. Filter on the client side if needed. Range capped at 92 days. Both endpoints inclusive. Example (last 30 days): getWeightRange({startDate: "2026-04-24", endDate: "2026-05-23"}) → { summaries: [{date: "2026-04-24", weightKg: null, ...}, {date: "2026-04-25", weightKg: 78.4, bmi: 23.1, bodyFatPct: 18.2, ...}, ...], ... }

Authentication: accessToken

Parameters

NameTypeRequiredDescription
startDatestringyesISO date YYYY-MM-DD. Inclusive.
endDatestringyesISO date YYYY-MM-DD. Inclusive. Must be ≥ startDate and ≤ 92 days after.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"startDate": {
"type": "string",
"description": "ISO date YYYY-MM-DD. Inclusive."
},
"endDate": {
"type": "string",
"description": "ISO date YYYY-MM-DD. Inclusive. Must be ≥ startDate and ≤ 92 days after."
}
},
"required": [
"startDate",
"endDate"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"summaries": {
"type": "array",
"items": {
"type": "object",
"properties": {
"date": {
"type": "string"
},
"weightKg": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"bmi": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"bodyFatPct": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"bodyWaterPct": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"muscleMassKg": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"boneMassKg": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"date",
"weightKg",
"bmi",
"bodyFatPct",
"bodyWaterPct",
"muscleMassKg",
"boneMassKg"
],
"additionalProperties": false
}
},
"startDate": {
"type": "string"
},
"endDate": {
"type": "string"
}
},
"required": [
"summaries",
"startDate",
"endDate"
],
"additionalProperties": false
}

google-sheets

clearSheet

Clear cell values in a Google Sheet (preserves formatting, only removes values). ⚠️ Destructive within scope: deleted values cannot be recovered through this API. The user can still undo in the Sheets UI if they catch it quickly. Confirm with the user before clearing entire sheets. Formatting (colours, borders, formulas in adjacent cells) is preserved — only cell values within the range are wiped. Range options: - Sheet name only (e.g. "Sheet1") clears every value in that sheet - A1 range (e.g. "Sheet1!A1:B10") clears only that block Example: clearSheet({spreadSheetId: "1AbC...", range: "Sheet1!A1:B10"}) → { clearedRange: "Sheet1!A1:B10" }

Authentication: accessToken

Parameters

NameTypeRequiredDescription
spreadSheetIdstringyesSpreadsheet id from the Google Sheets URL (between /d/ and /edit).
rangestringyesSheet name to wipe entirely (e.g. "Sheet1") OR an A1-notation range for a specific block (e.g. "Sheet1!A1:B10").
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"spreadSheetId": {
"type": "string",
"description": "Spreadsheet id from the Google Sheets URL (between /d/ and /edit)."
},
"range": {
"type": "string",
"description": "Sheet name to wipe entirely (e.g. \"Sheet1\") OR an A1-notation range for a specific block (e.g. \"Sheet1!A1:B10\")."
}
},
"required": [
"spreadSheetId",
"range"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"clearedRange": {
"type": "string",
"description": "A1-notation range the API actually cleared. May differ from input (e.g. empty trailing rows trimmed)."
}
},
"required": [
"clearedRange"
],
"additionalProperties": false
}

getSheetValue

Read cell values from a Google Sheet as a 2D array (rows × columns). Returns the raw value matrix — empty trailing rows/columns are trimmed by the Sheets API. The first sub-array is the top-leftmost row of the requested range. Range syntax (A1 notation): - "Sheet1" → every non-empty cell in Sheet1 - "Sheet1!A1:C10" → exactly those cells, including empties as "" - "Sheet1!A:A" → entire column A Returns null if the range is empty (Google's API quirk — not an error). Example: getSheetValue({spreadSheetId: "1AbC...", range: "Sheet1!A1:B2"}) → [["Name", "Email"], ["Alice", "a@x"]]

Authentication: accessToken

Parameters

NameTypeRequiredDescription
spreadSheetIdstringyesSpreadsheet id from the Google Sheets URL: the alphanumeric segment between /d/ and /edit. NOT the sheet (tab) name.
rangestringyesA1-notation range. Examples: "Sheet1" (entire sheet), "Sheet1!A1:C10" (specific cells), "Sheet1!A:A" (whole column). Sheet name must be quoted with single quotes if it contains spaces: "'My Sheet'!A1:B2".
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"spreadSheetId": {
"type": "string",
"description": "Spreadsheet id from the Google Sheets URL: the alphanumeric segment between /d/ and /edit. NOT the sheet (tab) name."
},
"range": {
"type": "string",
"description": "A1-notation range. Examples: \"Sheet1\" (entire sheet), \"Sheet1!A1:C10\" (specific cells), \"Sheet1!A:A\" (whole column). Sheet name must be quoted with single quotes if it contains spaces: \"'My Sheet'!A1:B2\"."
}
},
"required": [
"spreadSheetId",
"range"
],
"additionalProperties": false
}

setSheetValue

Overwrite cells in a Google Sheet with a 2D array of values. Values are written as RAW (no formula parsing, no implicit type coercion — "=A1+B1" lands as the literal string, not a formula). If you need formulas evaluated, pre-compute them client-side. Cells outside the values array are left untouched. To clear before writing, call clearSheet first or include explicit "" in the values. The range determines the top-left anchor; the values array determines the extent. If the values array is larger than the range, the larger area is written. Example: setSheetValue({spreadSheetId: "1AbC...", range: "Sheet1!A1", values: [["Name", "Email"], ["Alice", "a@x"]]}) → { updatedCells: 4 }

Authentication: accessToken

Parameters

NameTypeRequiredDescription
spreadSheetIdstringyesSpreadsheet id from the Google Sheets URL (between /d/ and /edit).
rangestringyesA1-notation range. The top-left of this range is the write anchor. Example: "Sheet1!A1".
valuesarrayyes2D array of cell values: outer array = rows, inner array = columns. e.g. [["a","b"],["c","d"]] writes a 2×2 block. Values written as RAW — formulas land as literal strings.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"spreadSheetId": {
"type": "string",
"description": "Spreadsheet id from the Google Sheets URL (between /d/ and /edit)."
},
"range": {
"type": "string",
"description": "A1-notation range. The top-left of this range is the write anchor. Example: \"Sheet1!A1\"."
},
"values": {
"type": "array",
"items": {},
"description": "2D array of cell values: outer array = rows, inner array = columns. e.g. [[\"a\",\"b\"],[\"c\",\"d\"]] writes a 2×2 block. Values written as RAW — formulas land as literal strings."
}
},
"required": [
"spreadSheetId",
"range",
"values"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"updatedCells": {
"type": "number",
"description": "Total number of cells the API reports as updated."
}
},
"required": [
"updatedCells"
],
"additionalProperties": false
}

hue

getLightState

Get the current state of a single light by id. Use this only when you need fresh state for one specific light — for example, after a setLightState call, to confirm the change took effect, or when you have a stale lightId from earlier and want a re-read. For inventory or any "what lights are there" question, use listLights instead (one call vs N). Example: getLightState({lightId: "abc-123"}) → { id: "abc-123", on: true, brightness: 80, colorTemperature: 366, color: null, ... }

Authentication: hueCredentials

Parameters

NameTypeRequiredDescription
lightIdstringyesLight id as returned by listLights (e.g. "abc-123-def"). Not the device name.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"lightId": {
"type": "string",
"description": "Light id as returned by listLights (e.g. \"abc-123-def\"). Not the device name."
}
},
"required": [
"lightId"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Stable light id used by getLightState and setLightState."
},
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Human-set name from the Hue app (e.g. \"Desk lamp\"). null when unset."
},
"archetype": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Form-factor hint from the bridge (e.g. \"sultan_bulb\", \"ceiling_round\"). null when unknown."
},
"on": {
"type": "boolean",
"description": "Current power state. true = on, false = off."
},
"brightness": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Current brightness percentage 0-100. null on bulbs that do not report brightness."
},
"colorTemperature": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Current color temperature in mirek (153-500). null on bulbs without colour-temp support or when colour mode is RGB."
},
"color": {
"anyOf": [
{
"type": "object",
"properties": {
"x": {
"type": "number",
"description": "CIE 1931 x chromaticity, 0-1."
},
"y": {
"type": "number",
"description": "CIE 1931 y chromaticity, 0-1."
}
},
"required": [
"x",
"y"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "Current colour in CIE 1931 xy chromaticity (Hue's native representation). null on white-only bulbs. This {x, y} can be passed straight back into setLightState via the `colorXy` field for lossless round-trip."
},
"dimmable": {
"type": "boolean",
"description": "Whether this bulb supports brightness control. When false, setLightState's brightness field is silently ignored."
},
"supportsColor": {
"type": "boolean",
"description": "Whether this bulb supports colour input (RGB/xy/hex). When false, color and colorHex are silently ignored."
},
"supportsColorTemperature": {
"type": "boolean",
"description": "Whether this bulb supports colour-temperature input (mirek/Kelvin). When false, colorTemperature and colorTemperatureKelvin are silently ignored."
}
},
"required": [
"id",
"name",
"archetype",
"on",
"brightness",
"colorTemperature",
"color",
"dimmable",
"supportsColor",
"supportsColorTemperature"
],
"additionalProperties": false
}

listDevices

List all hardware connected to the Hue bridge: bridges, lights, sensors, accessories. Use this for hardware inventory questions ("what's connected to my bridge?", "which model is this light?"). For controllable lights, use listLights instead — it's filtered to only what can be commanded and includes current state. Example: listDevices() → { devices: [{ id: "abc", name: "Living room", productName: "Hue color lamp", manufacturer: "Signify", ... }, ...] }

Authentication: hueCredentials

Parameters

No parameters.

Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {},
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"devices": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Stable device id."
},
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Human-set name from the Hue app."
},
"archetype": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Form-factor hint from the bridge."
},
"productName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Manufacturer product name (e.g. \"Hue color lamp\")."
},
"modelId": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Manufacturer model id (e.g. \"LCT015\")."
},
"manufacturer": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Manufacturer name (typically \"Signify Netherlands B.V.\")."
},
"softwareVersion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Firmware version installed on the device."
}
},
"required": [
"id",
"name",
"archetype",
"productName",
"modelId",
"manufacturer",
"softwareVersion"
],
"additionalProperties": false
},
"description": "All hardware on the bridge — controllable and non-controllable."
}
},
"required": [
"devices"
],
"additionalProperties": false
}

listLights

List all Hue lights with their current state. Start any Hue workflow here. Returns id, name, on/off state, brightness, color temperature, and color for every controllable light on the bridge. The lightId returned by this call is what setLightState and getLightState expect — no other source of lightIds is needed for normal use. Prefer this over listDevices when working with controllable endpoints: listDevices returns ALL hardware on the bridge (sensors, accessories, the bridge itself), most of which can't be controlled. Example: listLights() → { lights: [{ id: "abc-123", name: "Desk lamp", on: true, brightness: 80, colorTemperature: 366, color: null }, ...] }

Authentication: hueCredentials

Parameters

No parameters.

Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {},
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"lights": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Stable light id used by getLightState and setLightState."
},
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Human-set name from the Hue app (e.g. \"Desk lamp\"). null when unset."
},
"archetype": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Form-factor hint from the bridge (e.g. \"sultan_bulb\", \"ceiling_round\"). null when unknown."
},
"on": {
"type": "boolean",
"description": "Current power state. true = on, false = off."
},
"brightness": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Current brightness percentage 0-100. null on bulbs that do not report brightness."
},
"colorTemperature": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Current color temperature in mirek (153-500). null on bulbs without colour-temp support or when colour mode is RGB."
},
"color": {
"anyOf": [
{
"type": "object",
"properties": {
"x": {
"type": "number",
"description": "CIE 1931 x chromaticity, 0-1."
},
"y": {
"type": "number",
"description": "CIE 1931 y chromaticity, 0-1."
}
},
"required": [
"x",
"y"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "Current colour in CIE 1931 xy chromaticity (Hue's native representation). null on white-only bulbs. This {x, y} can be passed straight back into setLightState via the `colorXy` field for lossless round-trip."
},
"dimmable": {
"type": "boolean",
"description": "Whether this bulb supports brightness control. When false, setLightState's brightness field is silently ignored."
},
"supportsColor": {
"type": "boolean",
"description": "Whether this bulb supports colour input (RGB/xy/hex). When false, color and colorHex are silently ignored."
},
"supportsColorTemperature": {
"type": "boolean",
"description": "Whether this bulb supports colour-temperature input (mirek/Kelvin). When false, colorTemperature and colorTemperatureKelvin are silently ignored."
}
},
"required": [
"id",
"name",
"archetype",
"on",
"brightness",
"colorTemperature",
"color",
"dimmable",
"supportsColor",
"supportsColorTemperature"
],
"additionalProperties": false
},
"description": "All controllable lights on the bridge."
}
},
"required": [
"lights"
],
"additionalProperties": false
}

setLightState

Modify a light's physical state: power, brightness, color, or color temperature. ⚠️ This operation changes physical light state in the user's environment. Confirm with the user before firing unexpected calls (e.g. "turn all lights off" at night). Side-effect-bearing. All state fields are optional and INDEPENDENT — omitted fields are left unchanged on the bulb. Pass only what you want to change. Returns the updated light state after applying. Field interaction notes: - "on: false" turns the bulb off entirely. To dim a bulb without turning it off, use "brightness" (with on omitted or "on: true"). - "brightness: 0" is NOT the same as "on: false" — it sets the bulb on at minimum visible level. Use "on: false" to actually turn it off. - Colour fields ("color" / "colorHex" / "colorXy") and colour-temperature fields ("colorTemperature" / "colorTemperatureKelvin") are mutually exclusive on most Hue bulbs (last-write-wins on the bridge). Pass one or the other. - Pick the colour format that's natural in your context. If multiple are set, precedence is: colorXy > colorHex > color (RGB). Same for temperature: colorTemperatureKelvin > colorTemperature (mirek). - Colour fields only apply to colour-capable bulbs; check supportsColor / supportsColorTemperature from listLights first. - **Round-trip tip**: the colour shape returned by listLights/getLightState is xy chromaticity. To restore a light to its previously-observed colour, pass that {x, y} back via colorXy (lossless). RGB/hex are for "make it red"-style inputs where you don't have a prior xy. Example (set to warm white at 60%, fade in 2s): setLightState({lightId: "abc", on: true, brightness: 60, colorTemperatureKelvin: 2700, transitionMs: 2000}) Example (turn off): setLightState({lightId: "abc", on: false}) Example (red via hex): setLightState({lightId: "abc", colorHex: "#ff0000"}) Example (red via RGB): setLightState({lightId: "abc", color: {r: 255, g: 0, b: 0}}) Example (restore previously-read colour): setLightState({lightId: "abc", colorXy: {x: 0.6611, y: 0.3115}})

Authentication: hueCredentials

Parameters

NameTypeRequiredDescription
lightIdstringyesLight id as returned by listLights (e.g. "abc-123-def"). Not the device name.
onbooleannoPower state. true = on, false = off. Omit to leave power state unchanged. Note: "on: false" + brightness=N still turns the light off; the brightness is queued for next on.
brightnessnumbernoBrightness percentage, 0-100. 0 is minimum visible level, NOT off — use "on: false" to turn off. 100 is max. Omit to keep current brightness.
colorobjectnoTarget colour as 8-bit sRGB. Only honored by colour-capable bulbs (check supportsColor); silently ignored on white-only bulbs. The bridge maps to the bulb's gamut, so out-of-gamut colours are approximated. Use colorHex for a more natural hex notation. Mutually exclusive with colorTemperature/colorTemperatureKelvin.
colorHexstringnoTarget colour as hex string. Accepts "#ff0000", "ff0000", "#f00", or "f00" (3-digit shorthand expands like CSS). Same effect as color: {r,g,b}. Takes precedence over color (RGB) if both are set. Mutually exclusive with colorTemperature/colorTemperatureKelvin.
colorXyobjectnoTarget colour in CIE 1931 xy chromaticity — Hue's native format. Use this to round-trip a colour read from listLights/getLightState (same shape). Lossless (no gamma/matrix conversion). Takes precedence over colorHex and color when set. Mutually exclusive with colorTemperature/colorTemperatureKelvin.
colorTemperatureintegernoColor temperature in mirek (reciprocal megakelvin). Range 153-500. Counterintuitive: LOWER mirek = COOLER/bluer light. 153 ≈ 6500K, 250 ≈ 4000K, 366 ≈ 2700K, 500 ≈ 2000K. Prefer colorTemperatureKelvin for intuitive values. Mutually exclusive with color/colorHex.
colorTemperatureKelvinintegernoColor temperature in Kelvin (intuitive scale: higher K = cooler/bluer). Range 2000-6535 K (the inverse of Hue's 153-500 mirek range). 2700 ≈ warm white, 4000 ≈ neutral, 6500 ≈ cool daylight. Takes precedence over colorTemperature (mirek) if both are set. Mutually exclusive with color/colorHex.
transitionMsintegernoSmooth transition duration in milliseconds. Default 0 (instant). Max 60000 (60s) — longer values may be clamped by the bridge. Use ~400ms for gentle on/off, 2000-5000ms for ambience fades.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"lightId": {
"type": "string",
"description": "Light id as returned by listLights (e.g. \"abc-123-def\"). Not the device name."
},
"on": {
"description": "Power state. true = on, false = off. Omit to leave power state unchanged. Note: \"on: false\" + brightness=N still turns the light off; the brightness is queued for next on.",
"type": "boolean"
},
"brightness": {
"description": "Brightness percentage, 0-100. 0 is minimum visible level, NOT off — use \"on: false\" to turn off. 100 is max. Omit to keep current brightness.",
"type": "number",
"minimum": 0,
"maximum": 100
},
"color": {
"description": "Target colour as 8-bit sRGB. Only honored by colour-capable bulbs (check supportsColor); silently ignored on white-only bulbs. The bridge maps to the bulb's gamut, so out-of-gamut colours are approximated. Use colorHex for a more natural hex notation. Mutually exclusive with colorTemperature/colorTemperatureKelvin.",
"type": "object",
"properties": {
"r": {
"type": "integer",
"minimum": 0,
"maximum": 255,
"description": "Red component, 0-255."
},
"g": {
"type": "integer",
"minimum": 0,
"maximum": 255,
"description": "Green component, 0-255."
},
"b": {
"type": "integer",
"minimum": 0,
"maximum": 255,
"description": "Blue component, 0-255."
}
},
"required": [
"r",
"g",
"b"
],
"additionalProperties": false
},
"colorHex": {
"description": "Target colour as hex string. Accepts \"#ff0000\", \"ff0000\", \"#f00\", or \"f00\" (3-digit shorthand expands like CSS). Same effect as color: {r,g,b}. Takes precedence over color (RGB) if both are set. Mutually exclusive with colorTemperature/colorTemperatureKelvin.",
"type": "string"
},
"colorXy": {
"description": "Target colour in CIE 1931 xy chromaticity — Hue's native format. Use this to round-trip a colour read from listLights/getLightState (same shape). Lossless (no gamma/matrix conversion). Takes precedence over colorHex and color when set. Mutually exclusive with colorTemperature/colorTemperatureKelvin.",
"type": "object",
"properties": {
"x": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "CIE 1931 x chromaticity, 0-1."
},
"y": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "CIE 1931 y chromaticity, 0-1."
}
},
"required": [
"x",
"y"
],
"additionalProperties": false
},
"colorTemperature": {
"description": "Color temperature in mirek (reciprocal megakelvin). Range 153-500. Counterintuitive: LOWER mirek = COOLER/bluer light. 153 ≈ 6500K, 250 ≈ 4000K, 366 ≈ 2700K, 500 ≈ 2000K. Prefer colorTemperatureKelvin for intuitive values. Mutually exclusive with color/colorHex.",
"type": "integer",
"minimum": 153,
"maximum": 500
},
"colorTemperatureKelvin": {
"description": "Color temperature in Kelvin (intuitive scale: higher K = cooler/bluer). Range 2000-6535 K (the inverse of Hue's 153-500 mirek range). 2700 ≈ warm white, 4000 ≈ neutral, 6500 ≈ cool daylight. Takes precedence over colorTemperature (mirek) if both are set. Mutually exclusive with color/colorHex.",
"type": "integer",
"minimum": 2000,
"maximum": 6535
},
"transitionMs": {
"description": "Smooth transition duration in milliseconds. Default 0 (instant). Max 60000 (60s) — longer values may be clamped by the bridge. Use ~400ms for gentle on/off, 2000-5000ms for ambience fades.",
"type": "integer",
"minimum": 0,
"maximum": 60000
}
},
"required": [
"lightId"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Stable light id used by getLightState and setLightState."
},
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Human-set name from the Hue app (e.g. \"Desk lamp\"). null when unset."
},
"archetype": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Form-factor hint from the bridge (e.g. \"sultan_bulb\", \"ceiling_round\"). null when unknown."
},
"on": {
"type": "boolean",
"description": "Current power state. true = on, false = off."
},
"brightness": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Current brightness percentage 0-100. null on bulbs that do not report brightness."
},
"colorTemperature": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Current color temperature in mirek (153-500). null on bulbs without colour-temp support or when colour mode is RGB."
},
"color": {
"anyOf": [
{
"type": "object",
"properties": {
"x": {
"type": "number",
"description": "CIE 1931 x chromaticity, 0-1."
},
"y": {
"type": "number",
"description": "CIE 1931 y chromaticity, 0-1."
}
},
"required": [
"x",
"y"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "Current colour in CIE 1931 xy chromaticity (Hue's native representation). null on white-only bulbs. This {x, y} can be passed straight back into setLightState via the `colorXy` field for lossless round-trip."
},
"dimmable": {
"type": "boolean",
"description": "Whether this bulb supports brightness control. When false, setLightState's brightness field is silently ignored."
},
"supportsColor": {
"type": "boolean",
"description": "Whether this bulb supports colour input (RGB/xy/hex). When false, color and colorHex are silently ignored."
},
"supportsColorTemperature": {
"type": "boolean",
"description": "Whether this bulb supports colour-temperature input (mirek/Kelvin). When false, colorTemperature and colorTemperatureKelvin are silently ignored."
}
},
"required": [
"id",
"name",
"archetype",
"on",
"brightness",
"colorTemperature",
"color",
"dimmable",
"supportsColor",
"supportsColorTemperature"
],
"additionalProperties": false
}

openai

prompt

Send a single-turn prompt to an OpenAI chat-completion model and return the text response. ⚠️ Costs money per call (OpenAI billing on the connected API key). Caller should be deliberate about model choice — gpt-4 and gpt-4o are markedly more expensive than gpt-3.5-turbo. Single-turn only: no conversation history, no system prompt, no function calling. The prompt is sent as a single user message with stream=false. Output is plain text only; if you need structured output, parse it client-side or use the OpenAI API directly with response_format. Example: prompt({model: "gpt-4o", prompt: "Summarise this in one sentence: ..."}) → "Brief summary text"

Authentication: apiKey

Parameters

NameTypeRequiredDescription
modelstringnoModel id. Must be one of: "gpt-3.5-turbo" (cheap default), "gpt-4" (slower, higher quality), "gpt-4o" (recommended balance). Unknown models throw. Default: "gpt-3.5-turbo".
promptstringyesPrompt text sent as a user message to the model. No system prompt is prepended — fold any persona/instructions into this string.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"model": {
"description": "Model id. Must be one of: \"gpt-3.5-turbo\" (cheap default), \"gpt-4\" (slower, higher quality), \"gpt-4o\" (recommended balance). Unknown models throw. Default: \"gpt-3.5-turbo\".",
"type": "string"
},
"prompt": {
"type": "string",
"description": "Prompt text sent as a user message to the model. No system prompt is prepended — fold any persona/instructions into this string."
}
},
"required": [
"prompt"
],
"additionalProperties": false
}

sql

describeTable

Return column definitions for a table: name, data type, nullability, default, and primary-key membership. For mysql / mariadb: looks up `information_schema.columns` for the current database. For postgres: looks up `information_schema.columns` for the given schema (default `public`). Use this before composing a query to confirm column names/types — saves a guess-and-fail round-trip. Example: describeTable({table: "users"}) → { columns: [{name: "id", dataType: "int", nullable: false, ...}, ...] }

Authentication: sqlCredentials

Parameters

NameTypeRequiredDescription
tablestringyesTable name. Case-sensitivity follows the underlying database's rules.
schemastringnoPostgres only: schema (default "public"). Ignored for mysql/mariadb (uses current database).
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"table": {
"type": "string",
"description": "Table name. Case-sensitivity follows the underlying database's rules."
},
"schema": {
"description": "Postgres only: schema (default \"public\"). Ignored for mysql/mariadb (uses current database).",
"type": "string"
}
},
"required": [
"table"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"columns": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"dataType": {
"type": "string"
},
"nullable": {
"type": "boolean"
},
"defaultValue": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"isPrimaryKey": {
"type": "boolean"
}
},
"required": [
"name",
"dataType",
"nullable",
"defaultValue",
"isPrimaryKey"
],
"additionalProperties": false
},
"description": "Columns in ordinal_position order."
}
},
"required": [
"columns"
],
"additionalProperties": false
}

execute

Execute an arbitrary SQL statement, including writes (INSERT/UPDATE/DELETE/DDL). UNLIKE `query`, this does NOT enforce a read-only guard — anything the DB account is allowed to run will run. Use with care during debugging; prefer `query` for normal reads. Parameter placeholders are driver-native (`?` for mysql/mariadb, `$1, $2, …` for postgres). Returns affected-row count for DML and any rows the driver returns (e.g. `RETURNING` clauses on postgres). Example: execute({sql: "UPDATE users SET active = ? WHERE id = ?", params: [false, 42]}) → { affectedRows: 1, ... }

Authentication: sqlCredentials

Parameters

NameTypeRequiredDescription
sqlstringyesAny SQL statement. No guard — the DB account's grants are the only restriction.
paramsarraynoPositional parameters for placeholders.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "Any SQL statement. No guard — the DB account's grants are the only restriction."
},
"params": {
"description": "Positional parameters for placeholders.",
"type": "array",
"items": {}
}
},
"required": [
"sql"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"affectedRows": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Rows affected by DML. Null for SELECT-shaped results."
},
"rows": {
"type": "array",
"items": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"description": "Rows returned by the driver (e.g. RETURNING on postgres). Empty array for pure DML."
},
"columns": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"additionalProperties": false
},
"description": "Column metadata when rows are present."
}
},
"required": [
"affectedRows",
"rows",
"columns"
],
"additionalProperties": false
}

listTables

List tables in the connected database. For mysql / mariadb: queries `information_schema.tables` for the current database, base tables only. For postgres: queries `information_schema.tables` for the current database, schema = `public` by default. Pass `schema` to scan a different one. Returns table names sorted alphabetically. Example: listTables() → { tables: ["orders", "products", "users"] }

Authentication: sqlCredentials

Parameters

NameTypeRequiredDescription
schemastringnoPostgres only: schema to list (default "public"). Ignored for mysql/mariadb.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"schema": {
"description": "Postgres only: schema to list (default \"public\"). Ignored for mysql/mariadb.",
"type": "string"
}
},
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"tables": {
"type": "array",
"items": {
"type": "string"
},
"description": "Table names, sorted alphabetically."
}
},
"required": [
"tables"
],
"additionalProperties": false
}

query

Run a read-only SELECT statement and return the resulting rows. A statement-level guard parses the SQL and rejects anything that isn't a SELECT (no INSERT/UPDATE/DELETE/DDL/CALL). Use `execute` if you actually need to run a write. Parameter placeholders are driver-native: - mysql / mariadb → `?` - postgres → `$1, $2, …` Returns up to `rowLimit` rows (default 100, max 1000) — the underlying SQL is wrapped or truncated to avoid pulling huge result sets into the MCP response. Example (mysql): query({sql: "SELECT id, name FROM users WHERE active = ?", params: [true]}) Example (postgres): query({sql: "SELECT id, name FROM users WHERE active = $1", params: [true]})

Authentication: sqlCredentials

Parameters

NameTypeRequiredDescription
sqlstringyesA single SELECT statement. Non-SELECT statements (INSERT/UPDATE/DELETE/DDL/CALL) are rejected.
paramsarraynoPositional parameters for placeholders. Use `?` for mysql/mariadb, `$1, $2, …` for postgres.
rowLimitintegernoMaximum rows to return. Default 100. Hard cap 1000. Rows beyond the limit are silently dropped.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "A single SELECT statement. Non-SELECT statements (INSERT/UPDATE/DELETE/DDL/CALL) are rejected."
},
"params": {
"description": "Positional parameters for placeholders. Use `?` for mysql/mariadb, `$1, $2, …` for postgres.",
"type": "array",
"items": {}
},
"rowLimit": {
"description": "Maximum rows to return. Default 100. Hard cap 1000. Rows beyond the limit are silently dropped.",
"type": "integer",
"minimum": 1,
"maximum": 1000
}
},
"required": [
"sql"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"rows": {
"type": "array",
"items": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"description": "Result rows as objects keyed by column name."
},
"columns": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"additionalProperties": false
},
"description": "Column metadata in result order. Empty array when the driver omits it."
},
"rowCount": {
"type": "number",
"description": "Number of rows actually returned (after rowLimit truncation)."
},
"truncated": {
"type": "boolean",
"description": "True when the result was capped at rowLimit (the underlying query produced more rows)."
}
},
"required": [
"rows",
"columns",
"rowCount",
"truncated"
],
"additionalProperties": false
}

ssh

getFileInfo

Get file metadata for a single path: size, permissions, owner, group, modification time. Wraps the remote `stat` command. Path must point to an existing file or directory — errors if missing. For tree exploration use listFiles or listDirectories first. Example: getFileInfo({path: "/var/log/syslog"}) → { size: 1234567, permissions: "-rw-r--r--", owner: "root", group: "adm", modified: "2026-05-20 09:14:32.123456789 +0000" }

Authentication: sshCredentials

Parameters

NameTypeRequiredDescription
pathstringyesAbsolute path to a file or directory on the remote host.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute path to a file or directory on the remote host."
}
},
"required": [
"path"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"size": {
"type": "number",
"description": "Size in bytes. For directories: size of the directory entry itself, not contents."
},
"permissions": {
"type": "string",
"description": "Permissions in symbolic form (e.g. \"-rw-r--r--\", \"drwxr-xr-x\")."
},
"owner": {
"type": "string",
"description": "Username of the owner."
},
"group": {
"type": "string",
"description": "Group name."
},
"modified": {
"type": "string",
"description": "Modification time as reported by GNU stat (high precision, includes timezone). Not ISO-8601."
}
},
"required": [
"size",
"permissions",
"owner",
"group",
"modified"
],
"additionalProperties": false
}

listDirectories

List immediate subdirectories of a path on the remote host (one level deep, sorted). For files in a directory, use listFiles. For metadata about a single directory, use getFileInfo. Example: listDirectories({path: "/home"}) → { directories: ["/home", "/home/alice", "/home/bob"] } Note: the input path itself is included in the output (find -maxdepth 1 semantics).

Authentication: sshCredentials

Parameters

NameTypeRequiredDescription
pathstringyesAbsolute path to scan. Returns this directory plus its immediate subdirectories.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute path to scan. Returns this directory plus its immediate subdirectories."
}
},
"required": [
"path"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"directories": {
"type": "array",
"items": {
"type": "string"
},
"description": "Absolute paths of the directory and its immediate subdirectories, sorted lexicographically."
}
},
"required": [
"directories"
],
"additionalProperties": false
}

listFiles

List files in a directory on the remote host. Recursive scan and glob filtering are both supported. Returns plain filename strings (or full paths when recursive). Directories are excluded; for directories use listDirectories. When recursive=true and the tree is deep, prefer a tighter pattern — otherwise the output can be very large. Example (top level): listFiles({path: "/var/log"}) → { files: ["syslog", "auth.log", ...] } Example (recursive, filtered): listFiles({path: "/etc", pattern: "*.conf", recursive: true}) → { files: ["/etc/nginx/nginx.conf", ...] }

Authentication: sshCredentials

Parameters

NameTypeRequiredDescription
pathstringyesAbsolute path on the remote host (e.g. "/var/log"). Tilde expansion is NOT applied — use the full path.
patternstringnoGlob pattern applied to filenames (e.g. "*.log", "config-*.yaml"). When omitted, lists everything in the directory. Shell-style glob, not regex.
recursivebooleannoWhen true, walks subdirectories. Default false. Combine with a pattern to keep output bounded on large trees.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute path on the remote host (e.g. \"/var/log\"). Tilde expansion is NOT applied — use the full path."
},
"pattern": {
"description": "Glob pattern applied to filenames (e.g. \"*.log\", \"config-*.yaml\"). When omitted, lists everything in the directory. Shell-style glob, not regex.",
"type": "string"
},
"recursive": {
"description": "When true, walks subdirectories. Default false. Combine with a pattern to keep output bounded on large trees.",
"type": "boolean"
}
},
"required": [
"path"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"files": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filenames (non-recursive) or full paths (recursive). Empty array means no matches."
}
},
"required": [
"files"
],
"additionalProperties": false
}

readFile

Read text content from a file on the remote host. Supports line-range slicing for large files. Default behaviour: returns the first 500 lines from the top. Override with startLine / maxLines for a specific window. Cap is 2000 lines per call to keep responses bounded — for larger reads, paginate by adjusting startLine. Designed for text. Binary files will be returned as bytes-as-text and likely garble the response. Example (head): readFile({path: "/var/log/syslog"}) → first 500 lines Example (window): readFile({path: "/var/log/syslog", startLine: 1000, maxLines: 100}) → lines 1000–1099

Authentication: sshCredentials

Parameters

NameTypeRequiredDescription
pathstringyesAbsolute path to the text file on the remote host.
startLineintegerno1-indexed line to start reading from. When omitted, reads from the top (line 1). Use with maxLines to paginate large files.
maxLinesintegernoMaximum number of lines to return. Default 500. Hard cap 2000. Use a tighter window for large logs.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute path to the text file on the remote host."
},
"startLine": {
"description": "1-indexed line to start reading from. When omitted, reads from the top (line 1). Use with maxLines to paginate large files.",
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991
},
"maxLines": {
"description": "Maximum number of lines to return. Default 500. Hard cap 2000. Use a tighter window for large logs.",
"type": "integer",
"minimum": 1,
"maximum": 2000
}
},
"required": [
"path"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"content": {
"type": "string",
"description": "Raw text content of the requested window. Newlines preserved."
},
"lineCount": {
"type": "number",
"description": "Number of lines in `content`. May exceed maxLines by 1 due to trailing newline."
},
"startLine": {
"type": "number",
"description": "Echoes the startLine that was used (1 if omitted in input)."
}
},
"required": [
"content",
"lineCount",
"startLine"
],
"additionalProperties": false
}

searchFile

Search for a pattern in a single file on the remote host (`grep -n`). Returns matching lines with line numbers. Pattern is BRE (Basic Regular Expression) — same as default grep, not PCRE. Escape special chars accordingly. For literal-string search, prefer simple alphanumeric terms. Output is capped at 100 matches by default (max 500). For multi-file search, call this once per file. Example: searchFile({path: "/var/log/syslog", pattern: "ERROR"}) → { matches: ["42:ERROR boot failed", "117:ERROR disk full", ...], matchCount: 2 }

Authentication: sshCredentials

Parameters

NameTypeRequiredDescription
pathstringyesAbsolute path to the text file to search.
patternstringyesgrep BRE pattern (e.g. "ERROR", "user_[0-9]+"). Escape regex metacharacters for literal search. NOT PCRE.
maxResultsintegernoMaximum number of matching lines to return. Default 100. Hard cap 500. First-match-wins order (matches the file order).
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute path to the text file to search."
},
"pattern": {
"type": "string",
"description": "grep BRE pattern (e.g. \"ERROR\", \"user_[0-9]+\"). Escape regex metacharacters for literal search. NOT PCRE."
},
"maxResults": {
"description": "Maximum number of matching lines to return. Default 100. Hard cap 500. First-match-wins order (matches the file order).",
"type": "integer",
"minimum": 1,
"maximum": 500
}
},
"required": [
"path",
"pattern"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"matches": {
"type": "array",
"items": {
"type": "string"
},
"description": "Matching lines, each prefixed with `<lineNumber>:` (grep -n format)."
},
"matchCount": {
"type": "number",
"description": "Length of `matches`. Equals maxResults when truncated."
}
},
"required": [
"matches",
"matchCount"
],
"additionalProperties": false
}

tailFile

Read the last N lines from a file on the remote host. Cheaper than readFile for "what's recent in this log" queries. Default 100 lines. Cap 2000. Equivalent to remote `tail -n`. Use this for log inspection (latest entries, errors that just happened). Use readFile for windowed reads anywhere in the file. Example: tailFile({path: "/var/log/nginx/access.log", lines: 50}) → last 50 access lines

Authentication: sshCredentials

Parameters

NameTypeRequiredDescription
pathstringyesAbsolute path to the text file on the remote host.
linesintegernoNumber of trailing lines to return. Default 100. Hard cap 2000.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute path to the text file on the remote host."
},
"lines": {
"description": "Number of trailing lines to return. Default 100. Hard cap 2000.",
"type": "integer",
"minimum": 1,
"maximum": 2000
}
},
"required": [
"path"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"content": {
"type": "string",
"description": "Trailing slice of the file as raw text. Newlines preserved."
},
"lineCount": {
"type": "number",
"description": "Number of non-empty trailing lines in `content`."
}
},
"required": [
"content",
"lineCount"
],
"additionalProperties": false
}

strava

getActivity

Get the full detail for a single activity by id. Returns DetailedActivity: everything from listActivities PLUS description, calories, splits (km / mile), laps summary, best efforts (PR distances), gear, device name, and segment efforts on KOM leaderboards. Use this when you need anything beyond the summary fields. For time-series sensor data (HR per second, power per second, GPS track, etc.), call getActivityStreams instead — that's where the second-by-second telemetry lives. For time-in-HR-zone or time-in-power-zone distributions, call getActivityZones — those are aggregated separately. Example: getActivity({activityId: 12345678901}) → { id: ..., name: "Threshold intervals", distance: 32100, splits_metric: [...], laps: [...], segment_efforts: [...], available_zones: ["heartrate", "power"] }

Authentication: accessToken

Parameters

NameTypeRequiredDescription
activityIdintegeryesStrava activity id as returned by listActivities.
includeAllEffortsbooleannoInclude all segment efforts (default false). When false, Strava only returns KOM-leaderboard efforts. Set true to get every segment the athlete crossed — heavier response.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"activityId": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Strava activity id as returned by listActivities."
},
"includeAllEfforts": {
"description": "Include all segment efforts (default false). When false, Strava only returns KOM-leaderboard efforts. Set true to get every segment the athlete crossed — heavier response.",
"type": "boolean"
}
},
"required": [
"activityId"
],
"additionalProperties": false
}

getActivityLaps

Get the lap (interval) breakdown for a single activity. Laps are device-recorded splits — typically auto-laps every km/mile, manually pressed laps during a workout, or structured-workout intervals from a Garmin/Wahoo. For interval training, this is the cleanest way to see "did each rep hit the target?" without parsing streams. Returns one entry per lap, in order. Each lap has its own distance/time/HR/power/speed averages. For an unstructured ride (no manual laps, no workout file), Strava typically returns a single lap covering the whole activity. For threshold/interval workouts uploaded from a structured trainer file, expect one lap per work and rest interval. Example: getActivityLaps({activityId: 12345}) → [{ lap_index: 1, name: "Lap 1", moving_time: 240, distance: 1050, average_heartrate: 168, average_watts: 295 }, { lap_index: 2, name: "Lap 2", moving_time: 120, distance: 350, average_heartrate: 142, average_watts: 140 }, ...]

Authentication: accessToken

Parameters

NameTypeRequiredDescription
activityIdintegeryesStrava activity id.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"activityId": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Strava activity id."
}
},
"required": [
"activityId"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"laps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "number",
"description": "Lap id (unique across Strava)."
},
"lap_index": {
"type": "number",
"description": "1-based ordinal of the lap within the activity."
},
"name": {
"type": "string",
"description": "Lap name. Strava auto-generates \"Lap 1\", \"Lap 2\", etc."
},
"start_date": {
"type": "string",
"description": "ISO-8601 start of the lap in UTC."
},
"elapsed_time": {
"type": "number",
"description": "Wall-clock duration of the lap in seconds."
},
"moving_time": {
"type": "number",
"description": "Moving-time duration of the lap in seconds (excludes auto-pause)."
},
"distance": {
"type": "number",
"description": "Lap distance in metres."
},
"total_elevation_gain": {
"type": "number",
"description": "Elevation gain over the lap in metres."
},
"average_speed": {
"type": "number",
"description": "Average speed in m/s."
},
"max_speed": {
"type": "number",
"description": "Max speed in m/s within this lap."
},
"average_cadence": {
"description": "Average cadence (rpm/spm). Sensor-dependent.",
"type": "number"
},
"average_watts": {
"description": "Average power in watts. Cycling power-meter only.",
"type": "number"
},
"average_heartrate": {
"description": "Average HR in bpm. Sensor-dependent.",
"type": "number"
},
"max_heartrate": {
"description": "Max HR in bpm within this lap.",
"type": "number"
},
"start_index": {
"type": "number",
"description": "Index into the streams arrays for the lap start (use with getActivityStreams to slice)."
},
"end_index": {
"type": "number",
"description": "Index into the streams arrays for the lap end (exclusive)."
}
},
"required": [
"id",
"lap_index",
"name",
"start_date",
"elapsed_time",
"moving_time",
"distance",
"total_elevation_gain",
"average_speed",
"max_speed",
"start_index",
"end_index"
],
"additionalProperties": false
},
"description": "Laps in order. Always at least 1 entry (the whole activity)."
}
},
"required": [
"laps"
],
"additionalProperties": false
}

getActivityStreams

Get second-by-second sensor data for an activity (HR, power, cadence, pace, GPS, altitude, etc.). This is THE dataset for training-quality analysis. listActivities/getActivity give you summary numbers (avg HR, normalised power); streams give you the underlying signal so you can compute HR drift, intensity factor, pacing strategy, time-above-threshold, etc. Response is keyed by stream type. Each stream has a "data" array aligned by index — index 0 across all streams refers to the same moment in the workout. Length = original_size. Available stream types (request only the ones you need to keep payloads small): - time: seconds elapsed from activity start (gaps mean auto-pause) - distance: cumulative metres - latlng: [[lat, lng], ...] GPS track. Absent on indoor/manual activities. - altitude: metres - velocity_smooth: m/s (smoothed) - heartrate: bpm. Absent when has_heartrate=false on the parent activity. - cadence: rpm (cycling) or spm (running) - watts: watts. Cycling power-meter only. - temp: °C - moving: boolean per sample (false during auto-pause) - grade_smooth: % grade (smoothed) Sampling: Strava typically delivers data at the recording device's native rate (often 1 Hz). For long activities or strict context limits, request fewer keys. Example: getActivityStreams({activityId: 12345, keys: ["time", "heartrate", "watts"]}) → { time: {data: [0, 1, 2, ...]}, heartrate: {data: [82, 84, ...]}, watts: {data: [180, 195, ...]} }

Authentication: accessToken

Parameters

NameTypeRequiredDescription
activityIdintegeryesStrava activity id.
keysarrayyesStream types to fetch. Request only what you need — power+HR streams can be tens of thousands of samples each on a long ride.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"activityId": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Strava activity id."
},
"keys": {
"minItems": 1,
"type": "array",
"items": {
"type": "string",
"enum": [
"time",
"distance",
"latlng",
"altitude",
"velocity_smooth",
"heartrate",
"cadence",
"watts",
"temp",
"moving",
"grade_smooth"
]
},
"description": "Stream types to fetch. Request only what you need — power+HR streams can be tens of thousands of samples each on a long ride."
}
},
"required": [
"activityId",
"keys"
],
"additionalProperties": false
}

getActivityZones

Get the time-in-zone breakdown for a single activity (heart rate and/or power). Returns an array — typically one entry for "heartrate" and (on power-meter rides) one for "power". Each entry has distribution_buckets: an ordered array of {min, max, time} where time is seconds spent in that zone. Zone boundaries come from the athlete's configured zones (see getAthleteZones). For HR this is the standard 5-zone polarised/threshold model by default; athletes can customise. Key for training analysis: - Polarised training quality: are most easy minutes actually in zone 1-2, or creeping into zone 3? - Threshold session validation: did the prescribed time at threshold actually land in zone 4? - Intensity distribution over a week/month: sum across many activities. Example: getActivityZones({activityId: 12345}) → [{ type: "heartrate", distribution_buckets: [{min: -1, max: 142, time: 1820}, {min: 142, max: 152, time: 940}, ...] }, { type: "power", distribution_buckets: [...] }]

Authentication: accessToken

Parameters

NameTypeRequiredDescription
activityIdintegeryesStrava activity id.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"activityId": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Strava activity id."
}
},
"required": [
"activityId"
],
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"zones": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"heartrate",
"power"
],
"description": "Which sensor this zone breakdown is for."
},
"sensor_based": {
"type": "boolean",
"description": "Whether buckets were computed from real sensor data (vs estimated)."
},
"points": {
"type": "number",
"description": "Strava-internal \"points\" score for time-in-zone. Higher = harder."
},
"custom_zones": {
"type": "boolean",
"description": "Whether the athlete uses custom zone boundaries vs Strava defaults."
},
"max": {
"type": "number",
"description": "Max HR (bpm) or max power (watts) reached in the activity."
},
"distribution_buckets": {
"type": "array",
"items": {
"type": "object",
"properties": {
"min": {
"type": "number",
"description": "Bucket lower bound (bpm or watts). -1 means \"no floor\" (zone 1)."
},
"max": {
"type": "number",
"description": "Bucket upper bound (bpm or watts). -1 means \"no ceiling\" (top zone)."
},
"time": {
"type": "number",
"description": "Seconds spent in this bucket."
}
},
"required": [
"min",
"max",
"time"
],
"additionalProperties": false
},
"description": "Ordered low-to-high zones. Sum of time across buckets = activity moving time."
},
"score": {
"description": "Strava effort score for this zone-set. Premium-influenced.",
"type": "number"
}
},
"required": [
"type",
"sensor_based",
"points",
"custom_zones",
"max",
"distribution_buckets"
],
"additionalProperties": false
},
"description": "One entry per available sensor type (typically heartrate, plus power on power-meter rides)."
}
},
"required": [
"zones"
],
"additionalProperties": false
}

getAthlete

Get the full profile for the authenticated athlete: identity, location, premium status, weight, FTP, and gear (bikes/shoes). Use this as the "who am I?" / "what's my baseline?" call. weight + FTP are the inputs for power-based training analysis. Premium gates the suffer_score / Fitness & Freshness features. For totals (YTD/all-time distance per sport), call getAthleteStats. For zone definitions, call getAthleteZones. This call gives you the athlete record itself. Example: getAthlete() → { id: 1234567, firstname: "Jane", lastname: "Doe", premium: true, weight: 62.5, ftp: 240, bikes: [...], shoes: [...] }

Authentication: accessToken

Parameters

No parameters.

Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {},
"additionalProperties": false
}

getAthleteStats

Get aggregate totals for the authenticated athlete: recent (last 4 weeks), year-to-date, and all-time, broken down by ride / run / swim. Quick way to answer "how much have I run this year?", "what's my all-time bike distance?", or "have I been training more this month than last?". Buckets: - recent_*_totals: last 4 weeks. Useful for "current load" comparisons. - ytd_*_totals: calendar year to date. - all_*_totals: lifetime on Strava. Each total: count, distance (metres), moving_time (seconds), elapsed_time (seconds), elevation_gain (metres). Recent buckets also include achievement_count. Note: only ride / run / swim are bucketed; walks, hikes, e-bikes, etc. are not split out in this endpoint. Use listActivities + client-side aggregation for finer breakdowns. Example: getAthleteStats() → { recent_run_totals: { count: 8, distance: 64000, moving_time: 18900, ... }, ytd_ride_totals: {...}, all_ride_totals: {...}, ... }

Authentication: accessToken

Parameters

No parameters.

Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {},
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"biggest_ride_distance": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Longest single ride in metres. null if athlete has no rides."
},
"biggest_climb_elevation_gain": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Largest climb elevation gain in metres. null if unknown."
},
"recent_ride_totals": {
"type": "object",
"properties": {
"count": {
"type": "number",
"description": "Number of activities in this bucket."
},
"distance": {
"type": "number",
"description": "Total distance in metres."
},
"moving_time": {
"type": "number",
"description": "Total moving time in seconds (excludes auto-pause)."
},
"elapsed_time": {
"type": "number",
"description": "Total elapsed wall-clock time in seconds (includes pauses)."
},
"elevation_gain": {
"type": "number",
"description": "Total elevation gain in metres."
},
"achievement_count": {
"description": "Total achievements (KOMs, PRs, etc.). Only present on recent buckets.",
"type": "number"
}
},
"required": [
"count",
"distance",
"moving_time",
"elapsed_time",
"elevation_gain"
],
"additionalProperties": false,
"description": "Totals over the last 4 weeks for rides."
},
"recent_run_totals": {
"type": "object",
"properties": {
"count": {
"type": "number",
"description": "Number of activities in this bucket."
},
"distance": {
"type": "number",
"description": "Total distance in metres."
},
"moving_time": {
"type": "number",
"description": "Total moving time in seconds (excludes auto-pause)."
},
"elapsed_time": {
"type": "number",
"description": "Total elapsed wall-clock time in seconds (includes pauses)."
},
"elevation_gain": {
"type": "number",
"description": "Total elevation gain in metres."
},
"achievement_count": {
"description": "Total achievements (KOMs, PRs, etc.). Only present on recent buckets.",
"type": "number"
}
},
"required": [
"count",
"distance",
"moving_time",
"elapsed_time",
"elevation_gain"
],
"additionalProperties": false,
"description": "Totals over the last 4 weeks for runs."
},
"recent_swim_totals": {
"type": "object",
"properties": {
"count": {
"type": "number",
"description": "Number of activities in this bucket."
},
"distance": {
"type": "number",
"description": "Total distance in metres."
},
"moving_time": {
"type": "number",
"description": "Total moving time in seconds (excludes auto-pause)."
},
"elapsed_time": {
"type": "number",
"description": "Total elapsed wall-clock time in seconds (includes pauses)."
},
"elevation_gain": {
"type": "number",
"description": "Total elevation gain in metres."
},
"achievement_count": {
"description": "Total achievements (KOMs, PRs, etc.). Only present on recent buckets.",
"type": "number"
}
},
"required": [
"count",
"distance",
"moving_time",
"elapsed_time",
"elevation_gain"
],
"additionalProperties": false,
"description": "Totals over the last 4 weeks for swims."
},
"ytd_ride_totals": {
"type": "object",
"properties": {
"count": {
"type": "number",
"description": "Number of activities in this bucket."
},
"distance": {
"type": "number",
"description": "Total distance in metres."
},
"moving_time": {
"type": "number",
"description": "Total moving time in seconds (excludes auto-pause)."
},
"elapsed_time": {
"type": "number",
"description": "Total elapsed wall-clock time in seconds (includes pauses)."
},
"elevation_gain": {
"type": "number",
"description": "Total elevation gain in metres."
},
"achievement_count": {
"description": "Total achievements (KOMs, PRs, etc.). Only present on recent buckets.",
"type": "number"
}
},
"required": [
"count",
"distance",
"moving_time",
"elapsed_time",
"elevation_gain"
],
"additionalProperties": false,
"description": "Year-to-date totals for rides."
},
"ytd_run_totals": {
"type": "object",
"properties": {
"count": {
"type": "number",
"description": "Number of activities in this bucket."
},
"distance": {
"type": "number",
"description": "Total distance in metres."
},
"moving_time": {
"type": "number",
"description": "Total moving time in seconds (excludes auto-pause)."
},
"elapsed_time": {
"type": "number",
"description": "Total elapsed wall-clock time in seconds (includes pauses)."
},
"elevation_gain": {
"type": "number",
"description": "Total elevation gain in metres."
},
"achievement_count": {
"description": "Total achievements (KOMs, PRs, etc.). Only present on recent buckets.",
"type": "number"
}
},
"required": [
"count",
"distance",
"moving_time",
"elapsed_time",
"elevation_gain"
],
"additionalProperties": false,
"description": "Year-to-date totals for runs."
},
"ytd_swim_totals": {
"type": "object",
"properties": {
"count": {
"type": "number",
"description": "Number of activities in this bucket."
},
"distance": {
"type": "number",
"description": "Total distance in metres."
},
"moving_time": {
"type": "number",
"description": "Total moving time in seconds (excludes auto-pause)."
},
"elapsed_time": {
"type": "number",
"description": "Total elapsed wall-clock time in seconds (includes pauses)."
},
"elevation_gain": {
"type": "number",
"description": "Total elevation gain in metres."
},
"achievement_count": {
"description": "Total achievements (KOMs, PRs, etc.). Only present on recent buckets.",
"type": "number"
}
},
"required": [
"count",
"distance",
"moving_time",
"elapsed_time",
"elevation_gain"
],
"additionalProperties": false,
"description": "Year-to-date totals for swims."
},
"all_ride_totals": {
"type": "object",
"properties": {
"count": {
"type": "number",
"description": "Number of activities in this bucket."
},
"distance": {
"type": "number",
"description": "Total distance in metres."
},
"moving_time": {
"type": "number",
"description": "Total moving time in seconds (excludes auto-pause)."
},
"elapsed_time": {
"type": "number",
"description": "Total elapsed wall-clock time in seconds (includes pauses)."
},
"elevation_gain": {
"type": "number",
"description": "Total elevation gain in metres."
},
"achievement_count": {
"description": "Total achievements (KOMs, PRs, etc.). Only present on recent buckets.",
"type": "number"
}
},
"required": [
"count",
"distance",
"moving_time",
"elapsed_time",
"elevation_gain"
],
"additionalProperties": false,
"description": "All-time totals for rides."
},
"all_run_totals": {
"type": "object",
"properties": {
"count": {
"type": "number",
"description": "Number of activities in this bucket."
},
"distance": {
"type": "number",
"description": "Total distance in metres."
},
"moving_time": {
"type": "number",
"description": "Total moving time in seconds (excludes auto-pause)."
},
"elapsed_time": {
"type": "number",
"description": "Total elapsed wall-clock time in seconds (includes pauses)."
},
"elevation_gain": {
"type": "number",
"description": "Total elevation gain in metres."
},
"achievement_count": {
"description": "Total achievements (KOMs, PRs, etc.). Only present on recent buckets.",
"type": "number"
}
},
"required": [
"count",
"distance",
"moving_time",
"elapsed_time",
"elevation_gain"
],
"additionalProperties": false,
"description": "All-time totals for runs."
},
"all_swim_totals": {
"type": "object",
"properties": {
"count": {
"type": "number",
"description": "Number of activities in this bucket."
},
"distance": {
"type": "number",
"description": "Total distance in metres."
},
"moving_time": {
"type": "number",
"description": "Total moving time in seconds (excludes auto-pause)."
},
"elapsed_time": {
"type": "number",
"description": "Total elapsed wall-clock time in seconds (includes pauses)."
},
"elevation_gain": {
"type": "number",
"description": "Total elevation gain in metres."
},
"achievement_count": {
"description": "Total achievements (KOMs, PRs, etc.). Only present on recent buckets.",
"type": "number"
}
},
"required": [
"count",
"distance",
"moving_time",
"elapsed_time",
"elevation_gain"
],
"additionalProperties": false,
"description": "All-time totals for swims."
}
},
"required": [
"biggest_ride_distance",
"biggest_climb_elevation_gain",
"recent_ride_totals",
"recent_run_totals",
"recent_swim_totals",
"ytd_ride_totals",
"ytd_run_totals",
"ytd_swim_totals",
"all_ride_totals",
"all_run_totals",
"all_swim_totals"
],
"additionalProperties": false
}

getAthleteZones

Get the athlete's configured heart-rate and (where set) power zone boundaries. These are the zone definitions used by getActivityZones — to interpret time-in-zone meaningfully you need the boundary values. HR zones are present for any athlete with a max-HR set; power zones are typically only set by athletes with a power meter who've configured FTP zones. Requires profile:read_all scope. Free athletes typically get HR zones only; premium athletes get richer power zones. Each zone: {min, max} in bpm (HR) or watts (power). -1 means "unbounded" on that side (zone 1 has min=-1; top zone has max=-1). Example: getAthleteZones() → { heart_rate: { custom_zones: false, zones: [{min: -1, max: 142}, {min: 142, max: 152}, {min: 152, max: 162}, {min: 162, max: 172}, {min: 172, max: -1}] }, power: { zones: [...] } }

Authentication: accessToken

Parameters

No parameters.

Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {},
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"heart_rate": {
"description": "Heart-rate zones. Absent if athlete has no max HR configured.",
"type": "object",
"properties": {
"custom_zones": {
"type": "boolean",
"description": "true if athlete has set custom HR zone boundaries; false for Strava defaults."
},
"zones": {
"type": "array",
"items": {
"type": "object",
"properties": {
"min": {
"type": "number",
"description": "Lower bound of the zone (bpm for HR, watts for power). -1 means \"no lower bound\" (zone 1)."
},
"max": {
"type": "number",
"description": "Upper bound of the zone (bpm for HR, watts for power). -1 means \"no upper bound\" (top zone)."
}
},
"required": [
"min",
"max"
],
"additionalProperties": false
},
"description": "Ordered low-to-high. Typically 5 zones for runners/cyclists. min=-1 on zone 1, max=-1 on top zone."
}
},
"required": [
"custom_zones",
"zones"
],
"additionalProperties": false
},
"power": {
"description": "Power zones. Typically only present for premium athletes with FTP configured.",
"type": "object",
"properties": {
"zones": {
"type": "array",
"items": {
"type": "object",
"properties": {
"min": {
"type": "number",
"description": "Lower bound of the zone (bpm for HR, watts for power). -1 means \"no lower bound\" (zone 1)."
},
"max": {
"type": "number",
"description": "Upper bound of the zone (bpm for HR, watts for power). -1 means \"no upper bound\" (top zone)."
}
},
"required": [
"min",
"max"
],
"additionalProperties": false
},
"description": "Ordered low-to-high. Typically 7 zones (Coggan model). min=-1 on zone 1, max=-1 on top zone."
}
},
"required": [
"zones"
],
"additionalProperties": false
}
},
"additionalProperties": false
}

listActivities

List the authenticated athlete's activities, newest first. The trunk of every Strava workflow. Returns SUMMARY activities (lightweight: distance, time, avg HR/power, etc.) — not the full detail. Use getActivity for splits/laps/segment efforts/description. Pagination: - Default 30 per page; max per_page=200. Use 200 when scanning history. - Strava rate-limits 100 requests / 15 min per athlete. A full multi-year scan can be ~5-20 pages; pace requests if querying years of history. Date filtering: - before / after are Unix timestamps in SECONDS (not ms). Omit both for newest first. - Half-open: after=T returns activities AFTER T; before=T returns activities BEFORE T. Use both to bound a range. Example: listActivities({per_page: 200, after: 1704067200}) → first 200 activities since 2024-01-01. Example: listActivities({page: 2, per_page: 200}) → activities 201-400 (newest first).

Authentication: accessToken

Parameters

NameTypeRequiredDescription
beforeintegernoUnix timestamp in SECONDS. Only activities with start_date < before are returned. Use Math.floor(Date.now()/1000) for "now".
afterintegernoUnix timestamp in SECONDS. Only activities with start_date > after are returned. Useful for incremental sync ("everything since last fetch").
pageintegerno1-based page number. Default 1.
perPageintegernoActivities per page. Default 30, max 200. Use 200 for bulk history scans.
Full input schema (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"before": {
"description": "Unix timestamp in SECONDS. Only activities with start_date < before are returned. Use Math.floor(Date.now()/1000) for \"now\".",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"after": {
"description": "Unix timestamp in SECONDS. Only activities with start_date > after are returned. Useful for incremental sync (\"everything since last fetch\").",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"page": {
"description": "1-based page number. Default 1.",
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991
},
"perPage": {
"description": "Activities per page. Default 30, max 200. Use 200 for bulk history scans.",
"type": "integer",
"minimum": 1,
"maximum": 200
}
},
"additionalProperties": false
}
Response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"activities": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "number",
"description": "Strava activity id. Pass to getActivity / getActivityStreams / getActivityZones / getActivityLaps."
},
"name": {
"type": "string",
"description": "Activity title as set by the athlete (e.g. \"Morning Run\")."
},
"type": {
"type": "string",
"description": "Legacy activity type (e.g. \"Run\", \"Ride\", \"Swim\"). Use sport_type for new code."
},
"sport_type": {
"type": "string",
"description": "Modern sport classification (e.g. \"Run\", \"TrailRun\", \"Ride\", \"VirtualRide\", \"Walk\")."
},
"start_date": {
"type": "string",
"description": "ISO-8601 timestamp in UTC."
},
"start_date_local": {
"type": "string",
"description": "ISO-8601 timestamp in the athlete's local timezone (no tz suffix)."
},
"timezone": {
"type": "string",
"description": "IANA timezone (e.g. \"(GMT+01:00) Europe/Brussels\")."
},
"distance": {
"type": "number",
"description": "Distance in metres."
},
"moving_time": {
"type": "number",
"description": "Moving time in seconds (excludes auto-pause)."
},
"elapsed_time": {
"type": "number",
"description": "Elapsed wall-clock time in seconds."
},
"total_elevation_gain": {
"type": "number",
"description": "Cumulative elevation gain in metres."
},
"average_speed": {
"type": "number",
"description": "Average speed in m/s."
},
"max_speed": {
"type": "number",
"description": "Max instantaneous speed in m/s."
},
"has_heartrate": {
"type": "boolean",
"description": "Whether HR data was recorded. If false, average_heartrate/max_heartrate are absent."
},
"average_heartrate": {
"description": "Average HR in bpm. Only present when has_heartrate=true.",
"type": "number"
},
"max_heartrate": {
"description": "Max HR in bpm. Only present when has_heartrate=true.",
"type": "number"
},
"average_cadence": {
"description": "Average cadence (rpm for cycling, spm for running). Sensor-dependent.",
"type": "number"
},
"average_watts": {
"description": "Average power in watts. Cycling only, sensor-dependent.",
"type": "number"
},
"weighted_average_watts": {
"description": "Normalised power in watts (Strava's NP equivalent). Cycling only.",
"type": "number"
},
"kilojoules": {
"description": "Total work done in kJ. Cycling only.",
"type": "number"
},
"suffer_score": {
"description": "Strava Relative Effort score. Premium-only; null/missing for free athletes or HR-less rides.",
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"trainer": {
"type": "boolean",
"description": "Indoor trainer ride."
},
"commute": {
"type": "boolean",
"description": "Tagged as a commute."
},
"manual": {
"type": "boolean",
"description": "Manually entered (no GPS file)."
},
"private": {
"type": "boolean",
"description": "Visible only to the athlete."
},
"workout_type": {
"description": "Workout subtype: Run (0=default, 1=race, 2=long run, 3=workout), Ride (10=default, 11=race, 12=workout). Useful for filtering structured sessions.",
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"pr_count": {
"description": "Number of personal records set on this activity.",
"type": "number"
},
"achievement_count": {
"type": "number",
"description": "Total achievements on this activity (KOMs + PRs)."
},
"kudos_count": {
"type": "number",
"description": "Kudos received."
},
"start_latlng": {
"anyOf": [
{
"type": "array",
"prefixItems": [
{
"type": "number"
},
{
"type": "number"
}
]
},
{
"type": "null"
}
],
"description": "[lat, lng] of activity start. null for indoor/manual."
},
"end_latlng": {
"anyOf": [
{
"type": "array",
"prefixItems": [
{
"type": "number"
},
{
"type": "number"
}
]
},
{
"type": "null"
}
],
"description": "[lat, lng] of activity end. null for indoor/manual."
}
},
"required": [
"id",
"name",
"type",
"sport_type",
"start_date",
"start_date_local",
"timezone",
"distance",
"moving_time",
"elapsed_time",
"total_elevation_gain",
"average_speed",
"max_speed",
"has_heartrate",
"trainer",
"commute",
"manual",
"private",
"achievement_count",
"kudos_count",
"start_latlng",
"end_latlng"
],
"additionalProperties": {}
},
"description": "Activities on this page, newest first."
},
"page": {
"type": "number",
"description": "1-based page that was returned. Echoes the input (or 1 if omitted)."
},
"perPage": {
"type": "number",
"description": "Page size that was applied."
}
},
"required": [
"activities",
"page",
"perPage"
],
"additionalProperties": false
}