The Google Health API relies on filter expressions to restrict returned data points when calling standard retrieval methods. Specifically, filters are used with:
listendpoints: querying raw data points.reconcileendpoints: querying reconciled stream data points.
This guide explains how filtering is implemented, the syntax and conventions, and has examples for each record type.
Filter conventions
The Google Health API implements filters following the Google API Improvement Proposals (AIP-160) standard.
Query parameters
Filters are passed using the URL query string in the filter parameter.
Because filters contain special characters (such as <, >=, and quotes),
they must be URL-encoded when transmitted in the HTTP request.
For example, a request with the filter
steps.interval.civil_start_time >= "2026-03-04T00:00:00"
should be encoded as:
GET https://health.googleapis.com/v4/users/me/dataTypes/steps/dataPoints ?filter=steps.interval.civil_start_time%20%3E%3D%20%222026-03-04T00%3A00%3A00%22
Naming format
If a data type has more than one word, the format of the identifier differs between the resource path and the filter:
- Endpoint path: use hyphens (for example,
/dataTypes/body-fat). - Filter string: use underscores (for example,
body_fat).
If you use hyphens in the filter identifier, the API returns a 400 Bad Request
with the error reason INVALID_DATA_POINT_FILTER.
Filter types and fields
The fields you use in a filter depend on the record type of the data type you are querying. The following table describes the fields supported for each record type:
| Record type | Description | Supported filter fields |
|---|---|---|
| Interval | Data points recorded over a duration of time (for example, steps, distance, calories). |
{dataType}.interval.start_time{dataType}.interval.civil_start_time
Example: steps.interval.civil_start_time
|
| Sample | Instantaneous observations or short measurements (for example, weight, body-fat, blood-glucose). |
{dataType}.sample_time.physical_time{dataType}.sample_time.civil_time
Example: body_fat.sample_time.physical_time
|
| Daily | Daily metrics and summaries (for example, daily-heart-rate-variability). |
{dataType}.date
Example: daily_heart_rate_variability.date
|
| Session | Extended user sessions with specialized timing rules. |
Exercise: {dataType}.interval.civil_start_timeSleep: sleep.interval.end_time, sleep.interval.civil_end_timeECG: electrocardiogram.interval.start_time
Example: sleep.interval.civil_end_time
|
Supported operators and format rules
Filter expressions must comply with the following formatting rules:
Comparison operators
Only two comparison operators are supported:
| Operator | Description |
|---|---|
>= |
Lower boundary (inclusive). |
< |
Upper boundary (exclusive). |
Using other comparison operators (such as >, <=, or =) returns a
400 Bad Request with INVALID_DATA_POINT_FILTER_RESTRICTION_COMPARATOR.
Logical operators
| Operator | Support |
|---|---|
AND |
Default and only supported operator. Used to combine start and end boundaries. |
OR |
Not supported. Using OR returns a 400 Bad Request with error reason INVALID_DATA_POINT_FILTER_EXPRESSION_STRUCTURE ("The filter must be a conjunction or sequence of restrictions. Found: DISJUNCTION"). |
Time literals
Time literals in filter expressions must use these formats:
| Time type | Requirements | Examples |
|---|---|---|
| Physical time | RFC 3339 format, ending in Z or a UTC offset. Represents real-world events in UTC. |
Correct: "2026-03-01T00:00:00Z", "2026-03-01T00:00:00-08:00"Incorrect: "2026-03-01" |
| Civil time | ISO 8601 format: YYYY-MM-DD[THH:mm:ss]. Represents time on a user's clock, independent of location or timezone offsets. |
Correct: "2026-03-01", "2026-03-01T08:30:00" |
Filter validation rules
Filters follow these rules:
| Rule | Description | Examples |
|---|---|---|
| No mixed time types | Don't mix physical and civil time boundaries. | Invalid: steps.interval.start_time >= "2026-03-01T00:00:00Z" AND steps.interval.civil_start_time < "2026-03-02"Error: INVALID_DATA_POINT_FILTER_MIXED_TIME_RESTRICTIONS ("Filter cannot contain both physical and civil time ranges") |
| No mixed data types | Use fields belonging to the data type in the request path. | Invalid: Path /users/.../dataTypes/steps/... with filter distance.interval.start_time >= "..."Error: INVALID_DATA_POINT_FILTER_COLLECTION_MISMATCH ("Data type in filter does not match parent data type collection") |
| Time order logic | The upper boundary must be greater than the lower boundary. | Invalid: steps.interval.start_time >= "2026-03-02T00:00:00Z" AND steps.interval.start_time < "2026-03-01T00:00:00Z"Error: INVALID_TIME_RANGE ("Query end time must be strictly larger than start time") |
| Case format | Use snake case matching the data type name. | Invalid: body-fat.sample_time.physical_time >= "..."Error: INVALID_DATA_POINT_FILTER ("Invalid filter") |
The API returns HTTP 400 Bad Request for invalid filters. Check
error.details[].metadata.detailedReasons for the error reason code.
Required OAuth scopes
To query data points using filters, the authorized user must grant the relevant
OAuth scope for that data type. If the required scope is missing, the API
returns a 403 Forbidden status with MISSING_OAUTH_SCOPE.
The following table maps OAuth scopes to the data types they grant read access
to. The table displays scopes in their relative format (for example,
.activity_and_fitness.readonly represents
https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonly):
| Required OAuth scope | Data types |
|---|---|
.activity_and_fitness.readonly |
active-energy-burned, active-minutes, active-zone-minutes, activity-level, altitude, calories-in-heart-rate-zone, daily-vo2-max, distance, exercise, floors, run-vo2-max, sedentary-period, steps, swim-lengths-data, time-in-heart-rate-zone, total-calories, vo2-max |
.ecg.readonly |
electrocardiogram |
.health_metrics_and_measurements.readonly |
blood-glucose, body-fat, core-body-temperature, daily-heart-rate-variability, daily-heart-rate-zones, daily-oxygen-saturation, daily-respiratory-rate, daily-resting-heart-rate, daily-sleep-temperature-derivations, heart-rate, heart-rate-variability, height, oxygen-saturation, respiratory-rate-sleep-summary, weight |
.irn.readonly |
irregular-rhythm-notification |
.nutrition.readonly |
food, food-measurement-unit, hydration-log, nutrition-log |
.sleep.readonly |
sleep |
Use cases and examples
The following examples demonstrate how to construct filters for common scenarios:
Query steps by physical time range
Query steps recorded between 2026-03-01T00:00:00Z (inclusive)
and 2026-03-02T00:00:00Z (exclusive):
GET https://health.googleapis.com/v4/users/me/dataTypes/steps/dataPoints ?filter=steps.interval.start_time >= "2026-03-01T00:00:00Z" AND steps.interval.start_time < "2026-03-02T00:00:00Z"
Query steps by civil time range
Query steps for an entire day on March 4, 2026, relative to the user's local clock:
GET https://health.googleapis.com/v4/users/me/dataTypes/steps/dataPoints ?filter=steps.interval.civil_start_time >= "2026-03-04" AND steps.interval.civil_start_time < "2026-03-05"
Query body fat by physical sample time
Query body fat measurements recorded starting in March 2026 (in UTC):
GET https://health.googleapis.com/v4/users/me/dataTypes/body-fat/dataPoints ?filter=body_fat.sample_time.physical_time >= "2026-03-01T00:00:00Z"
Query weight by civil sample time
Query weight logs recorded after a specific local time:
GET https://health.googleapis.com/v4/users/me/dataTypes/weight/dataPoints ?filter=weight.sample_time.civil_time >= "2026-03-01T08:00:00"
Query heart rate variability daily summaries
Query heart rate variability (HRV) daily summaries recorded before a specific date:
GET https://health.googleapis.com/v4/users/me/dataTypes/daily-heart-rate-variability/dataPoints ?filter=daily_heart_rate_variability.date < "2026-08-15"
Query exercise sessions by civil start time
Query exercise sessions that occurred during a specific week in the user's local timezone:
GET https://health.googleapis.com/v4/users/me/dataTypes/exercise/dataPoints ?filter=exercise.interval.civil_start_time >= "2026-03-01" AND exercise.interval.civil_start_time < "2026-03-08"
Query sleep sessions by civil end time
Query sleep records by the session's end time:
GET https://health.googleapis.com/v4/users/me/dataTypes/sleep/dataPoints ?filter=sleep.interval.civil_end_time >= "2026-03-04T05:00:00" AND sleep.interval.civil_end_time < "2026-03-04T12:00:00"
Query electrocardiograms by start time
Query ECG recordings starting from a physical UTC point in time. Note that
ECG queries only support a >= comparison on start_time:
GET https://health.googleapis.com/v4/users/me/dataTypes/electrocardiogram/dataPoints ?filter=electrocardiogram.interval.start_time >= "2026-03-10T12:00:00Z"
Filter by data source family
A data source family is a logical grouping of data sources (such as smartwatches, mobile apps, or manual entries). It lets you isolate or aggregate data from specific types of sources (for example, physical wearable devices versus manual entries).
The reconcile, rollUp, and dailyRollUp endpoints all support the
dataSourceFamily parameter. The passing mechanism depends on the endpoint:
| Endpoint (HTTP method) | Mechanism |
|---|---|
reconcile (GET) |
Pass dataSourceFamily as a URL query parameter. |
rollUp (POST) |
Pass dataSourceFamily as a field in the JSON request body. |
dailyRollUp (POST) |
Pass dataSourceFamily as a field in the JSON request body. |
Supported Data Source Families
The following table describes the supported dataSourceFamily values:
| Option | Description |
|---|---|
users/me/dataSourceFamilies/all-sources |
Default value. Returns data points reconciled across all registered first-party (1P) and third-party (3P) data sources. Third-party app data will be returned with this option (such as smartwatch steps + third-party app steps + mobile phone steps + manual steps). |
users/me/dataSourceFamilies/google-wearables |
Includes data recorded by Google and Fitbit tracker devices (such as Fitbit wearable trackers and Pixel Watch). Excludes manually logged data and phone-estimated data. Use this option when your integration requires raw sensor telemetry recorded directly by wearable hardware. |
users/me/dataSourceFamilies/google-sources |
Includes first-party Google and Fitbit sources. This includes physical tracker device records, data from Health Connect, and any manual entries logged through first-party apps (such as the Fitbit app or Google Fit). |
To get a reconciled data stream from a specific data source family, call the
reconcile endpoint with the dataSourceFamily query parameter.
For example, the following GET request fetches tracker-recorded sleep for the day after 2026-03-03:
Request
GET https://health.googleapis.com/v4/users/me/dataTypes/sleep/dataPoints:reconcile ?dataSourceFamily=users/me/dataSourceFamilies/google-wearables &filter=sleep.interval.civil_end_time >= "2026-03-03" Authorization: Bearer access-token Accept: application/json
Response
{
"dataPoints": [
{
"name": "users/2515055256096816351/dataTypes/sleep/dataPoints/2724123844716220216",
"dataSource": {
"recordingMethod": "DERIVED",
"device": {
"displayName": "Charge 6"
},
"platform": "FITBIT"
},
"sleep": {
"interval": {
"startTime": "2026-03-03T20:57:30Z",
"startUtcOffset": "0s",
"endTime": "2026-03-04T04:41:30Z",
"endUtcOffset": "0s"
},
"type": "STAGES",
"stages": [
{
"startTime": "2026-03-03T20:57:30Z",
"startUtcOffset": "0s",
"endTime": "2026-03-03T20:59:30Z",
"endUtcOffset": "0s",
"type": "AWAKE",
"createTime": "2026-03-04T04:43:40.937183Z",
"updateTime": "2026-03-04T04:43:40.937183Z"
},
{
"startTime": "2026-03-04T04:07:30Z",
"startUtcOffset": "0s",
"endTime": "2026-03-04T04:41:30Z",
"endUtcOffset": "0s",
"type": "AWAKE",
"createTime": "2026-03-04T04:43:40.937183Z",
"updateTime": "2026-03-04T04:43:40.937183Z"
}
],
"metadata": {
"stagesStatus": "SUCCEEDED",
"processed": true,
"main": true
},
"summary": {
"minutesInSleepPeriod": "464",
"minutesAfterWakeUp": "0",
"minutesToFallAsleep": "0",
"minutesAsleep": "407",
"minutesAwake": "57",
"stagesSummary": [
{
"type": "AWAKE",
"minutes": "56",
"count": "12"
},
{
"type": "LIGHT",
"minutes": "198",
"count": "19"
},
{
"type": "DEEP",
"minutes": "114",
"count": "10"
},
{
"type": "REM",
"minutes": "94",
"count": "4"
}
]
},
"createTime": "2026-03-04T04:43:40.337983Z",
"updateTime": "2026-03-04T04:43:40.937183Z"
}
}
],
"nextPageToken": ""
}To aggregate data points over a specific window size restricted to a particular
data source family, call the rollUp endpoint and pass the dataSourceFamily
field in the JSON request body.
The following POST request queries intraday walking step counts in hourly
intervals (3600s), aggregated exclusively from wearable devices:
Request
POST https://health.googleapis.com/v4/users/me/dataTypes/steps/dataPoints:rollUp
Authorization: Bearer access-token
Accept: application/json
{
"range": {
"startTime": "2026-07-29T00:00:00Z",
"endTime": "2026-07-29T23:59:59Z"
},
"windowSize": "3600s",
"dataSourceFamily": "users/me/dataSourceFamilies/google-wearables"
}Response
{
"rollupDataPoints": [
{
"startTime": "2026-07-29T08:00:00Z",
"endTime": "2026-07-29T09:00:00Z",
"steps": {
"countSum": "1200"
}
},
{
"startTime": "2026-07-29T09:00:00Z",
"endTime": "2026-07-29T10:00:00Z",
"steps": {
"countSum": "3450"
}
}
]
}To aggregate daily data points for a specific source family, call the
dailyRollUp endpoint and pass the dataSourceFamily field in the request
body.
For example, the following request calculates the daily rollups for the user's steps, including all first-party Google and Fitbit sources (wearables + manual entries):
Request
POST https://health.googleapis.com/v4/users/me/dataTypes/steps/dataPoints:dailyRollUp
Authorization: Bearer access-token
Accept: application/json
{
"range": {
"start": {
"date": {
"year": 2026,
"month": 7,
"day": 28
},
"time": {
"hours": 0,
"minutes": 0,
"seconds": 0,
"nanos": 0
}
},
"end": {
"date": {
"year": 2026,
"month": 7,
"day": 30
},
"time": {
"hours": 0,
"minutes": 0,
"seconds": 0,
"nanos": 0
}
}
},
"windowSizeDays": 1,
"dataSourceFamily": "users/me/dataSourceFamilies/google-sources"
}Response
{
"rollupDataPoints": [
{
"civilStartTime": {
"date": {
"year": 2026,
"month": 7,
"day": 28
},
"time": {}
},
"civilEndTime": {
"date": {
"year": 2026,
"month": 7,
"day": 28
},
"time": {
"hours": 23,
"minutes": 59,
"seconds": 59
}
},
"steps": {
"countSum": "8430"
}
},
{
"civilStartTime": {
"date": {
"year": 2026,
"month": 7,
"day": 29
},
"time": {}
},
"civilEndTime": {
"date": {
"year": 2026,
"month": 7,
"day": 29
},
"time": {
"hours": 23,
"minutes": 59,
"seconds": 59
}
},
"steps": {
"countSum": "11245"
}
}
]
}