A plain-language guide to every Data Type the Google Health catalog exposes through gohealthcli. Each entry names the sync key you pass to sync --types, the upstream record shape (sample / interval / session / daily), the OAuth scope required, and what the catalog row supports. Run gohealthcli catalog list --json for the canonical machine-readable list, gohealthcli catalog scopes --json for exact scope membership, and gohealthcli catalog describe <data-type> --json for one type's compiled endpoint contract plus discovery-backed field names and JSON types. For storage shape, see docs/data-model.md.
#How to read this page
- Sync key — the literal string accepted by
sync --types. - Shape — how the upstream returns each Data Point:
sample(point-in-time reading),interval(a value over a span),session(a user activity with start/end),daily(one row per civil date). - Scope — the OAuth scope required. Opt-in keys (
ecg,irn,nutrition,tcx, plussettings, which gates thesettings/devicesidentity commands rather than any sync type) are granted viaconnect --add-scopes …; none of them are part of the defaultconnectgrant. - Rollups — which
--rollupkinds the catalog row supports beyond raw Data Points. - Stored as — the table the row lands in (
data_pointsfor raw,rollupsfor aggregates) plus the normalized view exposed for queries andexport.
The catalog is authoritative in internal/googlehealth/catalog.go; this page is its narrative companion. catalog describe keeps that compiled catalog authoritative: discovery facts are separately sourced and cannot override endpoint, filter, scope, record, or Rollup behavior.
#Get one Data Point by ID
The compiled catalog exposes the get endpoint family for blood-glucose, body-fat, core-body-temperature, exercise, height, hydration-log, nutrition-log, sleep, and weight. Fetch one with gohealthcli raw data-type <data-type> get --id <provider-id>. The Provider ID is opaque, the request treats it as one URL path component, and the operation does not accept a range, source filter, or paging input. Run catalog describe <data-type> --json and inspect compiled.endpoint_families instead of copying this support list into scripts.
#How long does each type take to sync?
Sync cost is proportional to Data Point count. Sustained throughput measures roughly 2,000–5,000 Data Points per minute on real runs; the table plans with the conservative ~2,000/min. Densities were measured 2026-06-10 from one real account backed by a Pixel Watch 4 (continuous heart-rate sampling) — your numbers scale with what your devices record. Cursor-resumed incremental syncs cover only the gap since the last run and finish in seconds regardless of type; Rollup syncs land one row per day or window and are always trivial.
For a raw Data Point Initial Backfill, sync requests the largest safe provider page size automatically: pageSize=10000 for heart-rate, steps, and other ordinary raw Data Types, with the documented smaller pageSize=25 cap for sleep and exercise. This lowers page count and retry surface, but it cannot make continuous heart-rate history small: a high-density account still has hundreds of thousands of raw Data Points over weeks. The raw debugging command remains explicit — it sends no page size unless you pass gohealthcli raw --page-size.
Points-per-day spans orders of magnitude because a Data Point is the upstream record unit, and that unit varies by shape: a sample point is a single reading (the watch emits a heart-rate sample every ~3 seconds), an interval point is one time-bucket (a steps point covers about a minute of walking), a session point is one whole activity (a sleep point is an entire night, stage breakdown included), and a daily point is one row per civil date.
Each sync key links to its full description further down this page.
| Sync key | Shape | Points/day | Two weeks ≈ | Sync time ≈ |
|---|---|---|---|---|
heart-rate | sample | ~27,500 | ~385,000 pts | 1.5–3 h |
time-in-heart-rate-zone | interval | ~960 | ~13,400 pts | ~5 min |
active-energy-burned | interval | ~630 | ~8,800 pts | ~4 min |
activity-level | interval | ~540 | ~7,600 pts | ~4 min |
oxygen-saturation | sample | ~480 | ~6,700 pts | ~3 min |
active-minutes | interval | ~280 | ~3,900 pts | ~2 min |
steps | interval | ~260 | ~3,600 pts | ~2 min |
distance | interval | ~210 | ~2,900 pts | ~1–2 min |
heart-rate-variability | sample | ~60 | ~800 pts | under a minute |
sedentary-period | interval | ~14 | ~190 pts | seconds |
active-zone-minutes | interval | ~13 | ~180 pts | seconds |
altitude | interval | ~8 | ~120 pts | seconds |
swim-lengths-data | interval | ~7 | ~100 pts | seconds |
exercise | session | ~2 | ~30 pts | seconds |
sleep | session | ~1 | ~18 pts | seconds |
vo2-max, run-vo2-max, respiratory-rate-sleep-summary, the daily-* types | sample / daily | ~1 | ~14 pts | seconds |
Not yet measured on this account: basal-energy-burned, floors, calories-in-heart-rate-zone, electrocardiogram, irregular-rhythm-notification, weight, body-fat, height, blood-glucose, core-body-temperature, hydration-log, and nutrition-log. Most of these are sparse user-logged or per-event records (weigh-ins, ECG sessions, hydration or food entries) and should sync in seconds; a continuously-recording source — a CGM feeding blood-glucose, for instance — raises density and cost accordingly.
A run longer than an OAuth access token's ~1-hour lifetime survives it: the token is refreshed mid-run on the first 401 and the failed page retried, so even a two-week heart-rate backfill can run as a single --from/--to window in the standard init --oauth-client-file setup. The sync reference has the full timing prose, and sync --status watches a long run live from a second terminal.
#Activity and fitness
#Steps
- Sync key:
steps - Shape: interval
- Scope:
activity_and_fitness.readonly - Rollups:
daily,hourly,weekly,window=<duration>(1h / 1d / 7d granularities) - Stored as:
data_points(raw) orrollups(aggregated); normalized viewdaily-steps
Step counts over a time interval (typically one minute or one stride bucket from the device). The default Data Type when sync is run with no --types flag.
#Distance
- Sync key:
distance - Shape: interval
- Scope:
activity_and_fitness.readonly - Stored as:
data_points
Distance travelled over a time interval, in metres. Reconcile-capable, so --source-family wearable works.
#Floors
- Sync key:
floors - Shape: interval
- Scope:
activity_and_fitness.readonly - Rollups:
daily,hourly,weekly,window=<duration> - Stored as:
data_points(raw) orrollups(aggregated); normalized viewfloors-intervals
Floors climbed over an interval. Not yet a default sync type — opt in via --types floors until the upstream filter shape is confirmed across multiple weeks of real data. Google does not expose floors through dataPoints.list; use a Rollup mode or --source-family wearable so gohealthcli uses a supported provider endpoint.
#Altitude
- Sync key:
altitude - Shape: interval
- Scope:
activity_and_fitness.readonly - Stored as:
data_points; normalized exportaltitude-intervals
Altitude readings over an interval.
#Active energy burned
- Sync key:
active-energy-burned - Shape: interval
- Scope:
activity_and_fitness.readonly - Stored as:
data_points
Active-only energy expenditure (kilocalories) over an interval, excluding basal metabolism.
#Basal energy burned
- Sync key:
basal-energy-burned - Shape: interval
- Scope:
activity_and_fitness.readonly - Rollups: none
- Stored as:
data_points; normalized exportbasal-energy-burned-intervals
Resting-metabolism energy expenditure in kilocalories over an interval. Raw sync uses list; --source-family wearable uses reconcile. Both filter on basal_energy_burned.interval.start_time. This opt-in Data Type is deliberately absent from sync --all until Google's English catalog catches up with the v4 discovery and localized calorie documentation.
#Active minutes
- Sync key:
active-minutes - Shape: interval
- Scope:
activity_and_fitness.readonly - Stored as:
data_points; normalized exportactive-minutes-intervals
Minutes the user spent above the device's active threshold over each interval.
#Active zone minutes
- Sync key:
active-zone-minutes - Shape: interval
- Scope:
activity_and_fitness.readonly - Stored as:
data_points; normalized exportactive-zone-minutes-intervals
Minutes spent in any active heart-rate zone over an interval. Counts moderate-zone minutes once and vigorous-zone minutes twice, per the Fitbit convention.
#Activity level
- Sync key:
activity-level - Shape: interval
- Scope:
activity_and_fitness.readonly - Stored as:
data_points; normalized exportactivity-level-intervals
Per-interval activity classification (sedentary / lightly active / fairly active / very active).
#Sedentary period
- Sync key:
sedentary-period - Shape: interval
- Scope:
activity_and_fitness.readonly - Stored as:
data_points; normalized exportsedentary-period-intervals
Continuous spans of low movement the provider flagged as sedentary.
#Calories in heart-rate zone
- Sync key:
calories-in-heart-rate-zone - Shape: interval
- Scope:
activity_and_fitness.readonly - Status: catalog-known, but raw Data Point sync is not implemented because Google exposes this type only through Rollup operations.
Calories burned while in each heart-rate zone over an interval. The catalog row is kept for future debugging until gohealthcli pins and parses the Rollup payload shape.
#Time in heart-rate zone
- Sync key:
time-in-heart-rate-zone - Shape: interval
- Scope:
activity_and_fitness.readonly - Stored as:
data_points; normalized exporttime-in-heart-rate-zone-intervals
Minutes spent in each heart-rate zone (out-of-zone / fat-burn / cardio / peak) over an interval.
#VO2 max
- Sync key:
vo2-max - Shape: sample
- Scope:
activity_and_fitness.readonly - Stored as:
data_points; normalized exportvo2-max-samples
Estimated maximum oxygen uptake (mL/kg/min) at a point in time, derived from heart-rate response to exertion.
#Run VO2 max
- Sync key:
run-vo2-max - Shape: sample
- Scope:
activity_and_fitness.readonly - Stored as:
data_points; normalized exportrun-vo2-max-samples
Run-specific VO2 max estimate, fitted from outdoor run heart-rate and pace data.
#Daily VO2 max
- Sync key:
daily-vo2-max - Shape: daily
- Scope:
activity_and_fitness.readonly - Stored as:
data_points; normalized exportdaily-vo2-max
One VO2 max value per civil date, the provider's daily summary derivation.
#Swim lengths data
- Sync key:
swim-lengths-data - Shape: interval
- Scope:
activity_and_fitness.readonly - Stored as:
data_points; normalized exportswim-lengths-data-intervals
Per-length swim metrics (stroke type, length, duration) captured by waterproof wearables.
#Total calories
- Sync key:
total-calories - Shape: Rollup-only
- Scope:
activity_and_fitness.readonly - Rollups:
daily,hourly,weekly,window=<duration>(1h / 1d / 7d granularities) - Stored as:
rollups; normalized exporttotal-calories-rollups
Google exposes total calories only through dailyRollUp and physical-window rollUp; raw Data Point sync remains rejected. An absent totalCalories union member means no manual or on-wrist data and is skipped. A present kcalSum: 0 is an explicit measurement and remains queryable/exportable. Google limits both endpoint modes to 14-day request spans; gohealthcli chunks longer ranges without gaps and keeps daily and physical Rollup cursors independent.
#Heart rate Data Types
#Heart rate
- Sync key:
heart-rate - Shape: sample
- Scope:
health_metrics_and_measurements.readonly - Rollups:
daily,hourly,weekly,window=<duration>(1h / 1d / 7d granularities) - Stored as:
data_pointsorrollups; normalized viewheart-rate-samples
Beats-per-minute readings at a point in time. The high-volume Data Type for any wearable, typically arriving at one sample per few seconds to minutes. Daily heart-rate Rollups are summary-history records in the rollups table and do not replace or imply a backfill of raw heart-rate samples.
Choose the Initial Backfill path by the question you need to answer:
- Exact sample history:
gohealthcli sync --types heart-rate --from 2026-01-01 --to 2026-01-15 --plain - Fast daily summary history:
gohealthcli sync --types heart-rate --rollup daily --from 2026-01-01 --to 2026-03-01 --plain - Fast hourly summary history:
gohealthcli sync --types heart-rate --rollup hourly --from 2026-01-01 --to 2026-01-15 --plain
Raw heart-rate Data Points and heart-rate Rollups write separate archive records and carry separate Sync Cursors. Running the daily Rollup path first is a good way to get trend history quickly, but a later raw sample sync still needs its own raw Initial Backfill.
#Heart-rate variability
- Sync key:
heart-rate-variability - Shape: sample
- Scope:
health_metrics_and_measurements.readonly - Stored as:
data_points
Beat-to-beat variability measurements (HRV — typically RMSSD in milliseconds), a recovery and autonomic-balance proxy.
#Daily resting heart rate
- Sync key:
daily-resting-heart-rate - Shape: daily
- Scope:
health_metrics_and_measurements.readonly - Stored as:
data_points; normalized viewresting-heart-rate-by-day
One resting-heart-rate estimate per civil date.
#Daily heart-rate variability
- Sync key:
daily-heart-rate-variability - Shape: daily
- Scope:
health_metrics_and_measurements.readonly - Stored as:
data_points
One HRV summary per civil date.
#Daily heart-rate zones
- Sync key:
daily-heart-rate-zones - Shape: daily
- Scope:
health_metrics_and_measurements.readonly - Stored as:
data_points; normalized exportdaily-heart-rate-zones
Per-day minutes spent in each heart-rate zone, the canonical daily rollup of zone-resident time.
#Heart rhythm (Tier 2)
These are gated behind opt-in scopes the user grants via gohealthcli connect --add-scopes ecg,irn. They are list-only session shapes.
#Electrocardiogram
- Sync key:
electrocardiogram - Shape: session
- Scope:
ecg.readonly(Tier 2, opt-in viaconnect --add-scopes ecg) - Stored as:
data_points; normalized exportelectrocardiogram-sessions
A user-triggered ECG measurement: start/end, classification (e.g. sinus rhythm, AFib), and the underlying samples preserved in raw JSON. Google's list endpoint accepts only a physical electrocardiogram.interval.start_time >= ... filter. Sync applies the requested exclusive --to boundary before archiving, so its normal [from,to) contract still holds.
#Irregular rhythm notification
- Sync key:
irregular-rhythm-notification - Shape: session
- Scope:
irn.readonly(Tier 2 opt-in) - Stored as:
data_points; normalized exportirregular-rhythm-notifications
A provider-issued alert that the wearable detected an irregular rhythm over a span. The companion current-irn-profile view tracks per-Connection IRN onboarding state.
#Sleep and respiration
#Sleep
- Sync key:
sleep - Shape: session
- Scope:
sleep.readonly - Stored as:
data_points; normalized viewssleep-sessionsandsleep-stages
One row per sleep session, with the per-stage (LIGHT / DEEP / REM / AWAKE) breakdown preserved inside the raw JSON. The sleep-stages view explodes that array into one row per stage so downstream queries can read stage duration without parsing JSON.
#Oxygen saturation
- Sync key:
oxygen-saturation - Shape: sample
- Scope:
health_metrics_and_measurements.readonly - Stored as:
data_points
Blood-oxygen (SpO2) saturation readings as a percentage, at a point in time. Wearables typically measure these only during sleep.
#Daily oxygen saturation
- Sync key:
daily-oxygen-saturation - Shape: daily
- Scope:
health_metrics_and_measurements.readonly - Stored as:
data_points
One SpO2 summary per civil date (typically min / mean / max across the sleep window).
#Daily respiratory rate
- Sync key:
daily-respiratory-rate - Shape: daily
- Scope:
health_metrics_and_measurements.readonly - Stored as:
data_points
One respiratory-rate summary per civil date.
#Respiratory rate sleep summary
- Sync key:
respiratory-rate-sleep-summary - Shape: sample
- Scope:
health_metrics_and_measurements.readonly - Stored as:
data_points; normalized exportrespiratory-rate-sleep-summary
Breaths-per-minute summary derived from the sleep window — emitted per sleep session, not per civil date.
#Daily sleep temperature derivations
- Sync key:
daily-sleep-temperature-derivations - Shape: daily
- Scope:
sleep.readonly - Stored as:
data_points; normalized exportdaily-sleep-temperature-derivations
One row per civil date carrying the provider's nightly skin-temperature derivations.
#Exercise Data Types
#Exercise
- Sync key:
exercise - Shape: session
- Scope:
activity_and_fitness.readonly(plusgooglehealth.location.readonlyfor TCX routes, opt-in viaconnect --add-scopes tcx) - Stored as:
data_points; normalized viewsexercise-sessionsandexercise-splits; optional TCX sidecar Attachment
One row per logged workout: exercise type, start/end, distance, calories, splits, and per-stage metadata preserved in raw JSON. When the TCX scope is granted, each session also archives the upstream TCX route XML as a content-addressed sidecar (<archive>.attachments/tcx/…); see docs/data-model.md for the attachment store.
#Body measurements
#Weight
- Sync key:
weight - Shape: sample
- Scope:
health_metrics_and_measurements.readonly - Stored as:
data_points; normalized viewweight-samples
One body-weight reading at a point in time, typically from a smart scale or manual entry.
#Body fat
- Sync key:
body-fat - Shape: sample
- Scope:
health_metrics_and_measurements.readonly - Stored as:
data_points; normalized exportbody-fat-samples
Body-fat percentage at a point in time.
#Height
- Sync key:
height - Shape: sample
- Scope:
health_metrics_and_measurements.readonly - Stored as:
data_points; normalized exportsheight-samplesandcurrent-height
Height readings; one current-height row tracks the latest value per Connection.
#Other biomarkers
#Blood glucose
- Sync key:
blood-glucose - Shape: sample
- Scope:
health_metrics_and_measurements.readonly - Stored as:
data_points; normalized exportblood-glucose-samples
Glucose readings at a point in time, from a CGM or manual entry.
#Core body temperature
- Sync key:
core-body-temperature - Shape: sample
- Scope:
health_metrics_and_measurements.readonly - Stored as:
data_points; normalized exportcore-body-temperature-samples
Core-temperature readings at a point in time.
#Nutrition and hydration
#Hydration log
- Sync key:
hydration-log - Shape: session
- Scope:
nutrition.readonly(opt-in viaconnect --add-scopes nutrition— never part of the defaultconnectgrant) - Stored as:
data_points; normalized viewhydration-log-sessions
A user-logged hydration entry: volume over a civil window.
#Nutrition log
- Sync key:
nutrition-log - Shape: session
- Scope:
nutrition.readonly(opt-in viaconnect --add-scopes nutrition— never part of the defaultconnectgrant) - List / reconcile: yes; civil filter
nutrition_log.interval.civil_start_time - Rollups: not implemented
- Stored as:
data_points; normalized viewsnutrition-log-sessionsandnutrition-log-nutrients - Default /
sync --all: no; explicitsync --types nutrition-logonly
One raw Data Point per logged food. Identified foods retain the Food resource name; anonymous foods retain their display name. The Normalized View keeps one row per parent log with nullable meal, serving, energy, carbohydrate, fat, and food-reference columns. It also retains the unexpanded nutrients_json array; nutrition-log-nutrients expands that array into one row per nutrient with the parent session and food context, the original array index, the nutrient enum, and nullable grams. Unknown nutrient enum values pass through unchanged.
#Upstream write-only Data Types
Google Health discovery revision 20260817 adds menstrual-period, moods, ovulation-test, and symptoms to the raw Data Point union. Google's Data Types table and Women's Health guide document only create, update, and batchDelete operations for them, with write-only scopes. gohealthcli does not write or delete Provider health data, so these four names are not sync keys and do not appear in catalog list or completion. catalog verify accounts for them under the upstream_write_only known gap.
#Identity, device, and settings snapshots
These ride alongside the Data Point catalog and capture per-Connection metadata. They land in the identity_snapshots table and are exposed through normalized views.
- Profile —
gohealthcli profile. Provider account profile (membership date, age, stride lengths). Stored askind='profile'; queryable via thesearchable-textview. - Settings —
gohealthcli settings. Measurement system, timezone, stride length type. Exposed via thecurrent-settingsview. - Paired devices —
gohealthcli devices. One row per paired wearable with resource name, device type, device version, and battery status/level. Exposed via thepaired-devicesview. - IRN profile —
gohealthcli irn-profile(Tier 2irn.readonlyopt-in). Tracks onboarding and enrollment state for irregular-rhythm notifications. Exposed via thecurrent-irn-profileview.
#Rollups
steps, heart-rate, floors, and Rollup-only total-calories support --rollup daily, which calls the upstream dailyRollUp endpoint and writes to the rollups table instead of data_points. The same catalog rows support the windowed rollUp endpoint (--rollup hourly, --rollup weekly, or --rollup window=<duration>) at 1h / 1d / 7d granularities. Heart-rate daily Rollups are a fast daily summary-history path, not a replacement for raw heart-rate samples. Total-calories requests are capped at 14 days per Provider call, with physical chunk boundaries aligned to whole requested windows. Each rollup kind carries its own Sync Cursor — syncing daily aggregates does not disturb raw or hourly cursors for the same Data Type. See sync for the full flag matrix.