Connecting Garmin Devices to OpenClaw: AI Agents Meet Fitness Data
Fitness tracking generates valuable data. Heart rate, sleep patterns, GPS routes, activity metrics: all useful information that AI agents can leverage for coaching, analysis, and automation. Garmin devices are among the most popular fitness trackers, and connecting them to OpenClaw opens up powerful possibilities.
This guide shows you how to integrate Garmin devices with OpenClaw and build agents that work with your health and fitness data.
Why Connect Garmin to OpenClaw?
Garmin devices track a wealth of data:
- Activity metrics: Steps, distance, calories, active minutes
- Heart rate: Resting HR, max HR, HR zones
- Sleep: Duration, sleep stages, sleep score
- GPS data: Routes, pace, elevation
- Stress and recovery: Stress levels, body battery, training load
- Workouts: Exercise type, duration, performance metrics
By connecting this data to OpenClaw, you can:
- Build a personal health coach that analyzes trends
- Automate workout logging and analysis
- Create alerts based on recovery metrics
- Sync fitness data with other tools and services
- Generate insights and recommendations
Integration Methods
There are three main approaches to connecting Garmin to OpenClaw:
1. Garmin Connect API
The official Garmin Connect API provides programmatic access to your data. This is the most reliable method.
Setup:
Pros:
- Official, supported method
- Access to all Garmin data types
- Real-time sync
Cons:
- Requires developer approval
- OAuth setup is complex
- Rate limits apply
2. Garmin Connect IQ Apps
Connect IQ is Garmin's platform for custom watch apps. You can build a Connect IQ app that sends data directly to your OpenClaw instance.
Setup:
Pros:
- Direct device integration
- Custom data collection
- No API approval needed for personal use
Cons:
- Requires learning Monkey C (Connect IQ language)
- App must run on device
- Battery impact
3. Garmin Connect Web Scraping
As a fallback, you can scrape data from the Garmin Connect website using OpenClaw's browser automation.
Setup:
Pros:
- No API approval needed
- Works immediately
- Access to all visible data
Cons:
- Fragile (breaks when UI changes)
- Slower than API
- Against Garmin TOS (use at your own risk)
Using the Garmin Connect API
Let's implement the official API method. This is the recommended approach.
Step 1: Get API Credentials
Step 2: Implement OAuth Authentication
Garmin uses OAuth 1.0a. Here's a basic implementation:
import OAuth from "oauth-1.0a";
import crypto from "crypto";
const oauth = OAuth({
consumer: {
key: process.env.GARMIN_CONSUMER_KEY,
secret: process.env.GARMIN_CONSUMER_SECRET
},
signature_method: "HMAC-SHA1",
hash_function(base_string, key) {
return crypto
.createHmac("sha1", key)
.update(base_string)
.digest("base64");
}
});
const requestToken = {
key: process.env.GARMIN_TOKEN,
secret: process.env.GARMIN_TOKEN_SECRET
};
Step 3: Fetch Activity Data
Once authenticated, fetch activities:
async function getRecentActivities() {
const requestData = {
url: "https://apis.garmin.com/wellness-api/rest/activities",
method: "GET"
};
const headers = oauth.toHeader(
oauth.authorize(requestData, requestToken)
);
const response = await fetch(requestData.url, { headers });
const activities = await response.json();
return activities;
}
Step 4: Parse and Store Data
Activities come in Garmin's format. Parse them into something useful:
async function processActivities(activities) {
for (const activity of activities) {
const processed = {
id: activity.activityId,
type: activity.activityType.typeKey,
startTime: new Date(activity.startTimeGMT),
duration: activity.duration,
distance: activity.distance,
calories: activity.calories,
averageHR: activity.averageHR,
maxHR: activity.maxHR
};
await storeActivity(processed);
}
}
Building an OpenClaw Garmin Skill
Let's create a reusable OpenClaw skill for Garmin integration.
File Structure
skills/garmin/
├── SKILL.md
├── config.json
├── scripts/
│ ├── sync.ts
│ ├── auth.ts
│ └── analyze.ts
└── data/
└── activities.json
SKILL.md
# Garmin Integration
Sync and analyze data from Garmin devices.
## Commands
- `garmin sync` - Fetch latest activities
- `garmin analyze` - Analyze recent trends
- `garmin activity <id>` - Get details for specific activity
## Setup
1. Get Garmin API credentials
2. Run `garmin auth` to authenticate
3. Run `garmin sync` to fetch initial data
## Configuration
Set these environment variables:
- `GARMIN_CONSUMER_KEY`
- `GARMIN_CONSUMER_SECRET`
- `GARMIN_TOKEN` (obtained via auth flow)
- `GARMIN_TOKEN_SECRET` (obtained via auth flow)
Sync Script
// scripts/sync.ts
import { getRecentActivities, processActivities } from "./api";
import fs from "fs/promises";
async function sync() {
console.log("Fetching activities from Garmin...");
const activities = await getRecentActivities();
console.log(`Found ${activities.length} activities`);
await processActivities(activities);
// Save to local cache
await fs.writeFile(
"./data/activities.json",
JSON.stringify(activities, null, 2)
);
console.log("Sync complete");
}
sync().catch(console.error);
Real-World Use Cases
Use Case 1: Recovery Coach
Build an agent that monitors recovery metrics and adjusts training recommendations:
async function checkRecovery() {
const latest = await getLatestActivity();
const sleep = await getLastNightSleep();
const hrv = await getHRV();
const recoveryScore = calculateRecoveryScore(sleep, hrv, latest);
if (recoveryScore < 50) {
return {
recommendation: "Easy day or rest",
reason: "Low recovery score",
details: {
sleep: sleep.score,
hrv: hrv.average,
previousWorkload: latest.trainingLoad
}
};
} else if (recoveryScore > 80) {
return {
recommendation: "High intensity workout",
reason: "Excellent recovery",
details: { recoveryScore }
};
} else {
return {
recommendation: "Moderate workout",
reason: "Average recovery"
};
}
}
Use Case 2: Automated Workout Logging
Sync workouts to a training log automatically:
async function logWorkouts() {
const activities = await getRecentActivities();
for (const activity of activities) {
const existing = await checkIfLogged(activity.id);
if (!existing) {
await logToNotion({
date: activity.startTime,
type: activity.type,
duration: formatDuration(activity.duration),
distance: `${activity.distance / 1000} km`,
notes: `Avg HR: ${activity.averageHR}, Calories: ${activity.calories}`
});
console.log(`Logged: ${activity.type} on ${activity.startTime}`);
}
}
}
Use Case 3: Performance Trends
Analyze trends over time:
async function analyzeRunningTrends() {
const runs = await getActivitiesByType("running");
const last30Days = runs.filter(r =>
isWithinDays(r.startTime, 30)
);
const avgPace = calculateAveragePace(last30Days);
const avgHR = calculateAverageHR(last30Days);
const totalDistance = sumDistance(last30Days);
const previousMonth = runs.filter(r =>
isWithinDays(r.startTime, 60) && !isWithinDays(r.startTime, 30)
);
const paceChange = avgPace - calculateAveragePace(previousMonth);
return {
summary: `30-day running stats`,
totalRuns: last30Days.length,
totalDistance: `${totalDistance / 1000} km`,
averagePace: formatPace(avgPace),
averageHR: Math.round(avgHR),
trend: paceChange < 0 ? "improving" : "declining",
paceChange: formatPace(Math.abs(paceChange))
};
}
Use Case 4: GPS Route Sharing
Extract GPS data and share routes:
async function exportRoute(activityId: string) {
const activity = await getActivity(activityId);
const gpsData = await getActivityGPS(activityId);
// Convert to GPX format
const gpx = convertToGPX(gpsData, activity);
// Save locally
await fs.writeFile(`./routes/${activityId}.gpx`, gpx);
// Or upload to Strava, Komoot, etc.
await uploadToStrava(gpx);
return {
distance: activity.distance,
elevation: activity.totalElevation,
gpxFile: `${activityId}.gpx`
};
}
Advanced Patterns
Real-Time Data Sync
Set up a webhook to receive data in real-time:
// Express endpoint
app.post("/webhook/garmin", async (req, res) => {
const event = req.body;
if (event.activitySummary) {
await processActivity(event.activitySummary);
}
if (event.dailySummary) {
await processDailySummary(event.dailySummary);
}
res.sendStatus(200);
});
Combining Multiple Data Sources
Merge Garmin data with other sources:
async function generateHealthReport() {
const garminData = await getGarminWeeklySummary();
const nutritionData = await getMyFitnessPalData();
const weightData = await getScaleData();
return {
week: getCurrentWeek(),
activity: {
steps: garminData.totalSteps,
activeMinutes: garminData.activeMinutes,
calories: garminData.totalCalories
},
nutrition: {
avgCalories: nutritionData.avgDailyCalories,
protein: nutritionData.avgProtein
},
weight: {
current: weightData.latest,
change: weightData.weekChange
},
insights: generateInsights(garminData, nutritionData, weightData)
};
}
Machine Learning on Fitness Data
Predict performance or detect patterns:
import * as tf from "@tensorflow/tfjs-node";
async function predictRaceTime(distance: number) {
// Load historical race/workout data
const trainingData = await getHistoricalWorkouts();
// Features: recent pace, volume, HR trends
const features = extractFeatures(trainingData);
// Simple model (in practice, train this properly)
const model = await loadOrTrainModel(features);
// Predict finish time for target distance
const prediction = model.predict(tf.tensor2d([[distance]]));
return {
distance,
predictedTime: prediction.dataSync()[0],
confidence: "based on last 90 days of training"
};
}
Privacy and Security
Store Credentials Securely
Never hardcode API credentials:
// Good: use environment variables
const apiKey = process.env.GARMIN_CONSUMER_KEY;
// Better: use a secrets manager
const apiKey = await getSecret("garmin/consumer-key");
Limit Data Retention
Only keep what you need:
async function pruneOldData() {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - 365); // Keep 1 year
await deleteActivitiesOlderThan(cutoff);
}
Anonymize When Sharing
Remove identifying info before sharing:
function anonymizeActivity(activity) {
return {
type: activity.type,
duration: activity.duration,
distance: activity.distance,
// Remove: GPS data, start location, user ID
};
}
Debugging Tips
Check API Quota
Garmin has rate limits. Monitor your usage:
const rateLimitRemaining = response.headers.get("X-RateLimit-Remaining");
const rateLimitReset = response.headers.get("X-RateLimit-Reset");
console.log(`API calls remaining: ${rateLimitRemaining}`);
console.log(`Resets at: ${new Date(rateLimitReset * 1000)}`);
Validate Data
Sometimes Garmin data has anomalies:
function validateActivity(activity) {
// Check for realistic values
if (activity.maxHR > 250 || activity.maxHR < 60) {
console.warn("Suspicious max HR:", activity.maxHR);
}
if (activity.distance === 0 && activity.duration > 0) {
console.warn("Zero distance with duration");
}
// GPS data sanity check
if (activity.gps && !isValidGPSTrack(activity.gps)) {
console.warn("Invalid GPS data");
}
}
Test with Sample Data
During development, use mock data:
const MOCK_ACTIVITY = {
activityId: "test-123",
activityType: { typeKey: "running" },
startTimeGMT: new Date().toISOString(),
duration: 1800,
distance: 5000,
calories: 350,
averageHR: 145,
maxHR: 165
};
if (process.env.NODE_ENV === "development") {
return MOCK_ACTIVITY;
}
Alternatives to Garmin
Similar integrations can be built for:
- Fitbit: Fitbit Web API
- Apple Watch: HealthKit export to OpenClaw
- Polar: Polar Open AccessLink API
- Wahoo: Wahoo Cloud API
- Strava: Strava API (aggregates data from many devices)
Wrapping Up
Connecting Garmin devices to OpenClaw unlocks powerful health and fitness automation. Whether you're building a personal coach, automating workout logs, or analyzing performance trends, the integration opens up possibilities that weren't feasible before.
Start with simple data sync, verify it works reliably, then build more sophisticated analysis and automation on top. Your agent can become your most knowledgeable training partner.
The key is treating fitness data with the same care you'd treat any personal information. Secure storage, minimal retention, and thoughtful use cases go a long way.
Now your OpenClaw agents can help you get fitter, recover smarter, and perform better.