Start

Data Types

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, plus settings, which gates the settings/devices identity commands rather than any sync type) are granted via connect --add-scopes …; none of them are part of the default connect grant.
  • Rollups — which --rollup kinds the catalog row supports beyond raw Data Points.
  • Stored as — the table the row lands in (data_points for raw, rollups for aggregates) plus the normalized view exposed for queries and export.

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 keyShapePoints/dayTwo weeks ≈Sync time ≈
heart-ratesample~27,500~385,000 pts1.5–3 h
time-in-heart-rate-zoneinterval~960~13,400 pts~5 min
active-energy-burnedinterval~630~8,800 pts~4 min
activity-levelinterval~540~7,600 pts~4 min
oxygen-saturationsample~480~6,700 pts~3 min
active-minutesinterval~280~3,900 pts~2 min
stepsinterval~260~3,600 pts~2 min
distanceinterval~210~2,900 pts~1–2 min
heart-rate-variabilitysample~60~800 ptsunder a minute
sedentary-periodinterval~14~190 ptsseconds
active-zone-minutesinterval~13~180 ptsseconds
altitudeinterval~8~120 ptsseconds
swim-lengths-datainterval~7~100 ptsseconds
exercisesession~2~30 ptsseconds
sleepsession~1~18 ptsseconds
vo2-max, run-vo2-max, respiratory-rate-sleep-summary, the daily-* typessample / daily~1~14 ptsseconds

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) or rollups (aggregated); normalized view daily-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) or rollups (aggregated); normalized view floors-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 export altitude-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 export basal-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 export active-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 export active-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 export activity-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 export sedentary-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 export time-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 export vo2-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 export run-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 export daily-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 export swim-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 export total-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_points or rollups; normalized view heart-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 view resting-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 export daily-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 via connect --add-scopes ecg)
  • Stored as: data_points; normalized export electrocardiogram-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 export irregular-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 views sleep-sessions and sleep-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 export respiratory-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 export daily-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 (plus googlehealth.location.readonly for TCX routes, opt-in via connect --add-scopes tcx)
  • Stored as: data_points; normalized views exercise-sessions and exercise-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 view weight-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 export body-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 exports height-samples and current-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 export blood-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 export core-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 via connect --add-scopes nutrition — never part of the default connect grant)
  • Stored as: data_points; normalized view hydration-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 via connect --add-scopes nutrition — never part of the default connect grant)
  • List / reconcile: yes; civil filter nutrition_log.interval.civil_start_time
  • Rollups: not implemented
  • Stored as: data_points; normalized views nutrition-log-sessions and nutrition-log-nutrients
  • Default / sync --all: no; explicit sync --types nutrition-log only

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.

  • Profilegohealthcli profile. Provider account profile (membership date, age, stride lengths). Stored as kind='profile'; queryable via the searchable-text view.
  • Settingsgohealthcli settings. Measurement system, timezone, stride length type. Exposed via the current-settings view.
  • Paired devicesgohealthcli devices. One row per paired wearable with resource name, device type, device version, and battery status/level. Exposed via the paired-devices view.
  • IRN profilegohealthcli irn-profile (Tier 2 irn.readonly opt-in). Tracks onboarding and enrollment state for irregular-rhythm notifications. Exposed via the current-irn-profile view.

#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.