An OpenClaw skill that syncs your daily health data from Garmin Connect into markdown files. OpenClaw can then reference your health and fitness data in conversation.
- Sleep β duration, stages (deep/light/REM/awake), sleep score
- Body β steps, calories, distance, floors
- Heart β resting HR, max HR, HRV
- Body Battery & SpO2
- Stress β average level
- Training Readiness β score and level
- Respiration β waking and sleeping breathing rate
- Fitness Age
- Intensity Minutes β weekly moderate/vigorous totals
- Weight β if recorded
- Activities β name, duration, distance, calories, HR, elevation, pace, cadence, power, training effect, VO2 max
# Health β January 26, 2026
## Sleep: 8h 39m (Good)
Deep: 1h 50m | Light: 4h 30m | REM: 2h 19m | Awake: 0h 54m
Sleep Score: 85
## Body: 9,720 steps | 2,317 cal
Distance: 8.0 km | Floors: 42
Resting HR: 37 bpm | Max HR: 111 bpm
HRV: 68 ms
SpO2: 94.0%
## Training Readiness: 100 (Prime) β Ready To Go
## Respiration: Waking: 12 brpm | Sleeping: 13 brpm | Range: 5β20
## Fitness Age: 33 (6 years younger)
## Intensity Minutes: 385 weekly
Moderate: 69 | Vigorous: 158 | Goal: 150
## Activities
- **5K Run** β 28:15, 5.0 km, 320 cal
Avg HR 155 / Max 172 | Elevation: +45m | Pace: 5:39/km | Cadence: 168 spm | Training Effect: 3.2 aerobic | VO2 Max: 50Sections are only included when data is available.
- Python 3.10+
- uv (no pip install needed β dependencies are inline)
- macOS:
brew install uv - Linux/WSL:
curl -LsSf https://astral.sh/uv/install.sh | sh - Windows:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
- macOS:
- A Garmin Connect account (two-factor authentication must be disabled β see Troubleshooting)
Authenticate and cache OAuth tokens. This only needs to happen once (~1 year token validity). The password is prompted interactively via getpass β never echoed to screen or stored in shell history.
uv run scripts/sync_garmin.py --setup --email you@example.comAfter setup succeeds, the password is no longer needed. All subsequent syncs use cached tokens only.
# Sync today (no credentials needed β uses cached tokens)
uv run scripts/sync_garmin.py
# Sync a specific date
uv run scripts/sync_garmin.py --date 2025-01-26
# Sync the last 7 days
uv run scripts/sync_garmin.py --days 7
# Custom output directory (default: health/)
uv run scripts/sync_garmin.py --output-dir my-dataMarkdown files are written to health/YYYY-MM-DD.md by default (relative to the skill's base directory).
ln -s /path/to/garminskill ~/.openclaw/skills/garmin-connectSchedule the sync to run every morning so your data stays up to date automatically. No credentials needed β the sync uses cached tokens from the one-time setup. OpenClaw's cron tool can handle this, or use a system crontab:
0 7 * * * uv run /path/to/garminskill/scripts/sync_garmin.pyThis is the most common setup error. It usually means Garmin's servers are temporarily rate-limiting or blocking the request (via Cloudflare). It does not necessarily mean your password is wrong.
- Wait a few minutes and try again. This resolves it most of the time.
- Double-check your password. The error can also appear for wrong credentials β Garmin doesn't always return a clear "wrong password" message.
- Check if Garmin Connect is down. Try logging in at connect.garmin.com in a browser.
If your Garmin account has 2FA enabled, authentication will fail. The garminconnect library does not support 2FA/MFA flows. You'll need to disable 2FA on your Garmin account to use this skill:
- Log in to connect.garmin.com
- Go to Account Settings β Security
- Disable two-step verification
- Re-run setup:
uv run scripts/sync_garmin.py --setup --email you@example.com
This skill uses cloudscraper to bypass Cloudflare protection on Garmin's SSO. Garmin periodically updates their anti-bot measures, which can cause temporary breakdowns. If authentication suddenly stops working after a period of stability:
- Update dependencies:
uv cache cleanthen re-run the sync (uv will fetch the latest versions automatically) - Wait and retry. Cloudflare blocks are often transient.
- Check the garminconnect issues page β others may be experiencing the same problem.
Cached tokens last about a year. When they expire, the sync will tell you to re-run setup. Just run the setup command again with your email β a new password prompt will appear and fresh tokens will be cached.
The script uses garminconnect with cloudscraper to bypass Cloudflare protection on Garmin's SSO. Authentication is split into two phases:
- Setup (
--setup): Run once in a terminal to authenticate.getpassprompts for the password (never echoed to screen or stored in shell history). OAuth tokens are cached in~/.garminconnect/(~1 year validity). The password is used once and then discarded. - Sync (default): Uses cached tokens only β no credentials needed. Token refresh is automatic (OAuth1 β OAuth2 exchange, no password required). If tokens expire or are revoked by Garmin, re-run setup.