CLI for the Google Health API v4 — built for AI agents and developers. 40 verified data types: steps, heart rate, exercise, sleep, weight, SpO2, HRV, ECG, blood glucose, nutrition, and more Agent-first: simplified JSON output, deterministic exit codes,
$ npx -y skills add Google-Health-API/google-health-cli --agent claude-code
What's inside
CLI for the Google Health API v4 — built for AI agents and developers.
--dry-run, --rawgo build -o ghealth .ghealth setup # One-time: GCP project + OAuth
ghealth data steps daily-rollup --from 2026-03-22 --to 2026-03-29 # Weekly step totals
ghealth data heart-rate list --from today --limit 10 # Recent heart rate readings
ghealth schema types # See all available data types
Download and Install Go
git clone https://github.com/Google-Health-API/google-health-cli.git
cd google-health-cli
go build -o ghealth .
ghealth setup
Walks you through: GCP project ID, OAuth credentials (download from Console — Desktop application type), Health API enablement, scope selection, and browser-based OAuth login.
Files written under ~/.config/ghealth/ (override with GHEALTH_CONFIG_DIR):
client_secret.json — your OAuth client (mode 0600)credentials.json — access + refresh tokens (mode 0600, plaintext JSON)config.toml — active profile (project, scopes)Tokens refresh automatically.
ghealth setup \
--project-id my-project \
--client-secret ~/Downloads/client_secret_123.json \
--scopes-preset readonly \
--skip-enable-api \
--no-prompt
Add --non-interactive-auth to skip the browser step too — complete later with ghealth auth login --complete <code> (see below).
| Scenario | Method |
|---|---|
| Interactive | ghealth setup or ghealth auth login |
| Headless / no browser | ghealth auth login --non-interactive → click URL on any device → ghealth auth login --complete <code> |
| Move tokens between machines | ghealth auth export → ghealth auth import |
| Pre-configured token | export GHEALTH_ACCESS_TOKEN=ya29... |
| Credential file | export GHEALTH_CREDENTIALS_FILE=/path/to/creds.json |
| GCP environment | Application Default Credentials (automatic) |
Precedence: GHEALTH_ACCESS_TOKEN > GHEALTH_CREDENTIALS_FILE > stored credentials > ADC.
# 1. On the host running ghealth:
ghealth auth login --non-interactive --scopes-preset readonly
# → JSON with auth_url (PKCE S256 challenge + random state baked in)
# and a complete_command. pending_auth.json holds the verifier locally.
# 2. Open auth_url in any browser, click "Allow".
# The browser will redirect to a localhost URL that fails to load — expected.
# Copy either the full redirected URL or just the 'code' query parameter.
# 3. Back on the ghealth host (both forms work):
ghealth auth login --complete 'http://localhost/?code=4/0AX4XfWh...&state=cQq...'
ghealth auth login --complete 4/0AX4XfWh...
# → state validated, PKCE verifier sent on exchange, tokens persisted.
State mismatch (URL paste with the wrong state parameter) clears the pending flow and returns exit 2. The bare-code form skips state validation but still consumes the pending file, so a stale flow can't be replayed.
# source (already authenticated):
ghealth auth export > /tmp/ghealth-creds.json
scp /tmp/ghealth-creds.json target:
# target (also needs client_secret.json — either run 'ghealth setup --non-interactive-auth' or copy it):
ghealth auth import --file /tmp/ghealth-creds.json
When no OAuth client_secret.json is configured, every auth command returns a
structured error with a next_steps array — the same six steps every time —
so an agent can relay it to a user verbatim without scraping prose:
ghealth auth login
# → exit 5, JSON on stderr:
# {
# "error": {
# "type": "config", "code": 5,
# "message": "No OAuth client_secret.json configured",
# "hint": "Run 'ghealth setup' to create or import OAuth credentials",
# "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"
# ]
# }
# }
Same next_steps are emitted by:
ghealth auth login (interactive / --non-interactive / --complete)ghealth auth status when neither stored creds nor env creds are presentghealth auth refresh, ghealth auth export (when nothing to refresh/export)ghealth setup --no-prompt when --client-secret is missingTo fetch the checklist without triggering an error (e.g. so an agent can display the bootstrap steps before calling auth at all):
ghealth setup --instructions
# → exit 0, JSON on stdout with status: "instructions" and the next_steps array
40 types verified against the live API. Run ghealth schema types for the full list.
| Type | Key Values | Operations |
|---|---|---|
steps | (use daily-rollup for countSum) | list, rollup, daily-rollup, reconcile |
heart-rate | beatsPerMinute | list, rollup, daily-rollup, reconcile |
exercise | type, duration, calories, avgHeartRate, notes | list, get, create, update, delete, reconcile, export-tcx |
sleep | minutesAsleep, minutesAwake, stageMinutes | list, get, create, update, delete, reconcile |
weight | weightGrams | list, get, create, update, delete, rollup, daily-rollup, reconcile |
body-fat | percentage | list, get, create, update, delete, rollup, daily-rollup, reconcile |
height | heightMillimeters | list, get, create, update, delete, reconcile |
distance | (use daily-rollup for millimetersSum) | list, rollup, daily-rollup, reconcile |
heart-rate-variability | RMSSD | list, reconcile |
oxygen-saturation | percentage (SpO2) | list, reconcile |
altitude | altitude value | list, rollup, daily-rollup, reconcile |
active-zone-minutes | activeZoneMinutes, heartRateZone | list, daily-rollup, reconcile |
activity-level | SEDENTARY, LIGHT, MODERATE, VIGOROUS | list, reconcile |
basal-energy-burned | kcal per interval (BMR) | list, reconcile |
active-energy-burned | kcal per interval (activity) | list, rollup, daily-rollup, reconcile |
vo2-max | VO2 max value | list, reconcile |
total-calories | (use daily-rollup for kcalSum) | daily-rollup |
sedentary-period | sedentary intervals | list, daily-rollup, reconcile |
swim-lengths-data | swimStrokeType, strokeCount (use daily-rollup for strokeCountSum) | list, rollup, daily-rollup, reconcile |
hydration-log | milliliters consumed | list, get, daily-rollup, reconcile |
nutrition-log | nutrients, energy, mealType, food | list, get, rollup, daily-rollup, reconcile |
food | nutrient profiles, servings (catalog — no time filter) | list, get |
food-measurement-unit | displayName (catalog — no time filter) | list, get |
blood-glucose | mg/dL, mealType, measurementTiming | list, get, rollup, daily-rollup, reconcile |
core-body-temperature | temperatureCelsius | list, get, rollup, daily-rollup, reconcile |
electrocardiogram | waveform, resultClassification (requires ecg.readonly) | list |
irregular-rhythm-notification | alert windows (requires irn.readonly) | list |
daily-resting-heart-rate | beatsPerMinute per day | list, reconcile |
daily-heart-rate-variability | daily HRV summary | list, reconcile |
daily-oxygen-saturation | daily SpO2 summary | list, reconcile |
daily-respiratory-rate | daily respiratory rate | list, reconcile |
daily-vo2-max | daily VO2 max | list, reconcile |
daily-sleep-temperature-derivations | temp deviation from baseline | list, reconcile |
respiratory-rate-sleep-summary | per-stage respiratory rate | list, reconcile |
run-vo2-max | VO2 max from running | list, daily-rollup, reconcile |
floors | (rollup only — countSum) | rollup, daily-rollup, reconcile |
active-minutes | (rollup only) | rollup, daily-rollup, reconcile |
time-in-heart-rate-zone | (rollup only) | daily-rollup, reconcile |
calories-in-heart-rate-zone | (rollup only — caloriesInHeartRateZones array per bucket) | rollup, daily-rollup, reconcile |
daily-heart-rate-zones | (reconcile only) | reconcile |
export-tcx writes the raw Google TCX, or — with --as csv — flattens it to one row per trackpoint (time, activity, lap, sport, latitude_deg, longitude_deg, altitude_m, distance_m, heart_rate_bpm, cadence_rpm, speed_mps, watts) for direct pd.read_csv consumption. Indoor activities have no track and yield a header-only CSV; their summary/notes live in data exercise list. Pass --output - to stream to stdout instead of a file.
ghealth data exercise export-tcx --id <id> --output ride.csv --as csv
ghealth data exercise export-tcx --id <id> --output - --as csv | head # stream to stdout
# Recent heart rate (sample-type: returns individual readings)
ghealth data heart-rate list --from today --limit 10
# Daily step totals for a week (rollup: returns aggregated counts)
ghealth data steps daily-rollup --from 2026-03-22 --to 2026-03-29
# Exercises this month
ghealth data exercise list --from 2026-03-01
# Weight history
ghealth data weight list --limit 20
# Sleep (summary by default, --detail for stage-by-stage breakdown)
ghealth data sleep list --limit 5
ghealth data sleep list --limit 5 --detail
Every read (list, get, rollup, daily-rollup, reconcile) returns the same JSON shape — an object {"dataPoints": [...]} with optional _hints and nextPageToken — so the rows are always under dataPoints.
list returns up to --limit rows (default 500). When more exist it includes a nextPageToken; pass it back with --page-token to fetch the next page losslessly (no rows skipped or repeated):
ghealth data heart-rate list --from 2026-06-15 --limit 500 # → {"dataPoints":[…], "nextPageToken":"ABC"}
ghealth data heart-rate list --from 2026-06-15 --limit 500 --page-token ABC
Some types (steps, distance) return time intervals without values from list. Use daily-rollup to get totals:
# This returns minute-by-minute intervals (no step count):
ghealth data steps list --from today --limit 5
# This returns daily totals with actual counts:
ghealth data steps daily-rollup --from 2026-03-22 --to 2026-03-29
# → {"dataPoints": [{"date": "2026-03-28", "countSum": "9037"}, ...]}
For the presence-aware types — altitude, distance, floors, steps, total-calories — a date that is absent from rollup output means the device was not worn (or did not sync) that day, not that the value was zero. A bucket with countSum: "0" is a true zero: the device was worn and genuinely recorded no activity.
countSum: "0" → true zero (worn, no activity)FAQ
google-health-cli is a Claude Code plugin with 2 hand-picked skills for data work, indexed on Flowy. Install it with the command on its page. It includes ghealth-shared, ghealth. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.
Is this plugin yours?
Claim it with GitHubSubmit a pluginPromote it