google-health-cli
Skill by ara.so — Devtools Skills collection.
CLI for the Google Health API v4 built for AI agents and developers. Supports 40+ verified health data types including steps, heart rate, exercise, sleep, weight, SpO2, HRV, ECG, blood glucose, and nutrition. Outputs deterministic JSON with agent-friendly exit codes.
Installation
git clone https://github.com/Google-Health-API/google-health-cli.git
cd google-health-cli
go build -o ghealth .
Move the binary to your PATH:
sudo mv ghealth /usr/local/bin/
Authentication Setup
Interactive Setup
ghealth setup
Walks through:
- GCP project ID
- OAuth credentials (download from Console as Desktop application)
- Health API enablement
- Scope selection
- Browser-based OAuth login
Credentials stored in ~/.config/ghealth/ (override with GHEALTH_CONFIG_DIR):
client_secret.json— OAuth client (mode 0600)credentials.json— access + refresh tokens (mode 0600)config.toml— active profile
Non-Interactive Setup (CI/Agents)
ghealth setup \
--project-id $GCP_PROJECT_ID \
--client-secret ~/Downloads/client_secret.json \
--scopes-preset readonly \
--skip-enable-api \
--no-prompt
Headless OAuth Flow
# 1. Start non-interactive login
ghealth auth login --non-interactive --scopes-preset readonly
# Returns JSON with auth_url and complete_command
# 2. Open auth_url in browser, copy the redirect URL or code parameter
# 3. Complete authentication
ghealth auth login --complete 'http://localhost/?code=4/0AX4XfWh...&state=cQq...'
# OR just the code:
ghealth auth login --complete 4/0AX4XfWh...
Environment Variables
# Use existing access token
export GHEALTH_ACCESS_TOKEN=ya29...
# Use custom credentials file
export GHEALTH_CREDENTIALS_FILE=/path/to/creds.json
# Custom config directory
export GHEALTH_CONFIG_DIR=/custom/config/path
Precedence: GHEALTH_ACCESS_TOKEN > GHEALTH_CREDENTIALS_FILE > stored credentials > Application Default Credentials.
Moving Tokens Between Machines
# Export on source machine
ghealth auth export > /tmp/ghealth-creds.json
# Import on target machine (requires client_secret.json)
ghealth auth import --file /tmp/ghealth-creds.json
Key Data Types
| Type | Key Values | Operations |
|---|---|---|
steps |
countSum (daily rollup) |
list, rollup, daily-rollup, reconcile |
heart-rate |
beatsPerMinute |
list, rollup, daily-rollup, reconcile |
exercise |
type, duration, calories, avgHeartRate | list, get, create, update, delete, export-tcx |
sleep |
minutesAsleep, minutesAwake, stageMinutes | list, get, create, update, delete, reconcile |
weight |
weightGrams |
list, get, create, update, delete, rollup, daily-rollup |
distance |
millimetersSum (daily rollup) |
list, rollup, daily-rollup, reconcile |
oxygen-saturation |
percentage (SpO2) |
list, reconcile |
heart-rate-variability |
RMSSD | list, reconcile |
blood-glucose |
mg/dL, mealType, timing | list, get, rollup, daily-rollup, reconcile |
nutrition-log |
nutrients, energy, mealType | list, get, rollup, daily-rollup, reconcile |
Run ghealth schema types for all 40+ types.
Reading Data
Sample Data (Individual Readings)
# Recent heart rate readings
ghealth data heart-rate list --from today --limit 10
# Heart rate for specific date range
ghealth data heart-rate list --from 2026-03-22 --to 2026-03-29 --limit 100
# Oxygen saturation readings
ghealth data oxygen-saturation list --from 2026-06-01 --limit 20
# Recent weight measurements
ghealth data weight list --limit 10
Rollup Data (Aggregated)
Critical: For steps, distance, and other interval types, use daily-rollup to get actual values. list returns time intervals without totals.
# Daily step totals (CORRECT way)
ghealth data steps daily-rollup --from 2026-03-22 --to 2026-03-29
# Returns: {"dataPoints": [{"date": "2026-03-28", "countSum": "9037"}, ...]}
# Daily distance totals
ghealth data distance daily-rollup --from 2026-03-01 --to 2026-03-31
# Weekly average heart rate
ghealth data heart-rate rollup --from 2026-03-01 --to 2026-03-31 --duration 7d
# Hourly step counts for today
ghealth data steps rollup --from today --duration 1h
Exercise Data
# List recent exercises
ghealth data exercise list --from 2026-03-01 --limit 20
# Get specific exercise
ghealth data exercise get --id <exercise-id>
# Export exercise track as TCX
ghealth data exercise export-tcx --id <id> --output ride.tcx
# Export as CSV for data analysis
ghealth data exercise export-tcx --id <id> --output ride.csv --as csv
# CSV columns: time, activity, lap, sport, latitude_deg, longitude_deg, altitude_m,
# distance_m, heart_rate_bpm, cadence_rpm, speed_mps, watts
# Stream to stdout
ghealth data exercise export-tcx --id <id> --output - --as csv | head
Sleep Data
# Recent sleep sessions (summary)
ghealth data sleep list --limit 5
# Sleep with stage-by-stage breakdown
ghealth data sleep list --limit 5 --detail
# Specific sleep session
ghealth data sleep get --id <sleep-id>
Pagination
All list commands support pagination:
# First page (default limit 500)
ghealth data heart-rate list --from 2026-06-15 --limit 500
# Returns: {"dataPoints": [...], "nextPageToken": "ABC"}
# Next page
ghealth data heart-rate list --from 2026-06-15 --limit 500 --page-token ABC
Writing Data
Create Exercise
ghealth data exercise create \
--start-time 2026-03-28T14:00:00Z \
--end-time 2026-03-28T15:30:00Z \
--type running \
--calories 450 \
--distance-meters 8000 \
--avg-heart-rate 152 \
--notes "Morning run in the park"
Update Exercise
ghealth data exercise update \
--id <exercise-id> \
--notes "Evening run - felt great" \
--calories 475
Create Sleep Session
ghealth data sleep create \
--start-time 2026-03-27T23:00:00Z \
--end-time 2026-03-28T07:00:00Z \
--minutes-asleep 450 \
--minutes-awake 30
Log Weight
ghealth data weight create \
--timestamp 2026-03-28T08:00:00Z \
--weight-grams 75000
Delete Data
ghealth data exercise delete --id <exercise-id>
ghealth data sleep delete --id <sleep-id>
ghealth data weight delete --id <weight-id>
Agent-Friendly Patterns
Deterministic JSON Output
Every read command returns:
{
"dataPoints": [
{"date": "2026-03-28", "countSum": "9037"},
{"date": "2026-03-27", "countSum": "8245"}
],
"_hints": {
"type": "steps",
"rollupDuration": "1d"
},
"nextPageToken": "optional-token-if-more-data"
}
Parse with jq:
# Extract step counts
ghealth data steps daily-rollup --from 2026-03-22 --to 2026-03-29 | jq -r '.dataPoints[].countSum'
# Get total steps for week
ghealth data steps daily-rollup --from 2026-03-22 --to 2026-03-29 | jq '[.dataPoints[].countSum | tonumber] | add'
# Average heart rate
ghealth data heart-rate list --from today --limit 100 | jq '[.dataPoints[].beatsPerMinute | tonumber] | add / length'
Dry Run Mode
# Preview API request without executing
ghealth data steps daily-rollup --from today --dry-run
Raw API Response
# Get unprocessed API response
ghealth data heart-rate list --from today --limit 10 --raw
Exit Codes
0— Success1— General error2— Authentication/validation error3— API error4— Data not found5— Configuration error
Critical Data Handling Rules
Missing Days vs. Zero Values
For presence-aware types (altitude, distance, floors, steps, total-calories):
- Missing date → device not worn/synced — render as "no data", NOT zero
countSum: "0"→ true zero (device worn, no activity)
# Example output:
# {"dataPoints": [
# {"date": "2026-03-28", "countSum": "9037"}, # Active day
# {"date": "2026-03-27", "countSum": "0"}, # Worn but sedentary
# # 2026-03-26 missing entirely # Not worn - DON'T treat as 0
# ]}
Never average over absent days as if they were zeros — this silently deflates statistics.
Time Formats
- Use ISO 8601:
2026-03-28T14:00:00Z - Shortcut:
today,yesterday - Date-only:
2026-03-28(interprets as start of day in user's timezone)
ghealth data steps daily-rollup --from 2026-03-22 --to today
ghealth data heart-rate list --from yesterday --limit 50
Common Workflows
Weekly Step Summary
#!/bin/bash
# Get last 7 days of step data
WEEK_AGO=$(date -d '7 days ago' +%Y-%m-%d)
TODAY=$(date +%Y-%m-%d)
ghealth data steps daily-rollup --from $WEEK_AGO --to $TODAY | \
jq -r '.dataPoints[] | "\(.date): \(.countSum) steps"'
Export All Exercise Data
#!/bin/bash
# Export all exercises from a month
ghealth data exercise list --from 2026-03-01 --to 2026-03-31 | \
jq -r '.dataPoints[].id' | \
while read id; do
ghealth data exercise export-tcx --id "$id" --output "exercise_${id}.csv" --as csv
done
Daily Health Dashboard
#!/bin/bash
# Fetch today's key metrics
echo "=== Health Metrics for $(date +%Y-%m-%d) ==="
echo -n "Steps: "
ghealth data steps daily-rollup --from today --to today | jq -r '.dataPoints[0].countSum // "no data"'
echo -n "Average Heart Rate: "
ghealth data heart-rate list --from today --limit 500 | \
jq '[.dataPoints[].beatsPerMinute | tonumber] | add / length | round'
echo -n "Weight: "
ghealth data weight list --limit 1 | jq -r '.dataPoints[0].weightGrams // "no data"'
Data Reconciliation
Check for data conflicts and resolve:
# Reconcile heart rate data
ghealth data heart-rate reconcile --from 2026-03-01 --to 2026-03-31
# Reconcile with conflict resolution strategy
ghealth data exercise reconcile --from 2026-03-01 --strategy newest
Troubleshooting
No OAuth Credentials
If you see exit code 5 with JSON error:
{
"error": {
"type": "config",
"code": 5,
"message": "No OAuth client_secret.json configured",
"next_steps": [
"Open https://console.cloud.google.com/apis/credentials",
"Create or select a Google Cloud project",
"Enable the Google Health API",
"Create OAuth client ID with Application type: Desktop app",
"Download the client_secret JSON",
"Run: ghealth setup --client-secret /path/to/client_secret.json"
]
}
}
Get instructions without error:
ghealth setup --instructions
Token Refresh Issues
# Check auth status
ghealth auth status
# Force refresh
ghealth auth refresh
# Re-authenticate
ghealth auth login
Empty Data Results
Check:
- Date range is correct (use
--fromand--to) - Using correct command (
daily-rollupvslist) - OAuth scopes include necessary permissions
- Device has synced data
# Verify scopes
ghealth auth status | jq -r '.scopes'
# Check available types
ghealth schema types
Rate Limiting
The API has rate limits. Space out requests:
# Add delays between bulk operations
for id in $(ghealth data exercise list | jq -r '.dataPoints[].id'); do
ghealth data exercise get --id "$id"
sleep 1
done
Schema Exploration
# List all available data types
ghealth schema types
# Get details for specific type
ghealth schema type --name steps
# View available operations for a type
ghealth schema type --name exercise | jq -r '.operations[]'
Best Practices for Agents
- Always use
daily-rollupfor totals —listreturns intervals, not sums - Handle pagination — check for
nextPageTokenin responses - Respect missing data — don't coalesce absent days to zero
- Use exit codes — check
$?for error handling - Parse with jq — all output is structured JSON under
.dataPoints - Set explicit date ranges —
--fromand--toprevent unbounded queries - Use
--dry-runfor validation before destructive operations - Store credentials securely — use environment variables for tokens