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, skin-temperature-sensors, weight |
.irn.readonly |
irregular-rhythm-notification |
.nutrition.readonly |
food, food-measurement-unit, hydration-log, nutrition-log |
.sleep.readonly |
sleep |
If your client is granted only a write scope (such as
.activity_and_fitness.writeonly) for a requested data type, you can still
query data points that your own client wrote. In this case, requests are
implicitly restricted to the self-sources data source family. For details and
data type limitations, see
Filter by data source family.
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, manual entries, or data written by your own client).
The list, reconcile, rollUp, and dailyRollUp endpoints all support the
dataSourceFamily parameter. The passing mechanism depends on the endpoint:
| Endpoint (HTTP method) | Mechanism |
|---|---|
list (GET) |
Pass dataSourceFamily as a URL query parameter. |
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 (when granted a read scope). Includes data points from all available first-party (1P) and third-party (3P) data sources. Third-party app data is 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). |
users/me/dataSourceFamilies/self-sources |
Includes only the data that the calling client wrote through the Google Health API (data points whose data source was registered through the API with the same OAuth client ID as the caller). |
If no data point matches the requested data source family, the response is an empty list rather than an error.
Write-only scopes and self-sources
Callers that are granted only write scopes (such as
.activity_and_fitness.writeonly) for the requested data types can read the
data they wrote themselves, and only that data.
- Requests without an explicit
dataSourceFamilyparameter, or withdataSourceFamilyset tousers/me/dataSourceFamilies/self-sources, are implicitly restricted toself-sources. - Requesting any other data source family (such as
all-sources,google-wearables, orgoogle-sources) with only a write scope fails with a403 Forbidden(PERMISSION_DENIED) error.
Data type limitations on the list endpoint
On the list endpoint (users.dataTypes.dataPoints.list), filtering by
dataSourceFamily is not supported for the sleep, food, and
food-measurement-unit data types:
- Explicitly setting
dataSourceFamilyonlistforsleep,food, orfood-measurement-unitfails with a400 Bad Request(INVALID_ARGUMENT) error. - Calling
listfor these data types when granted only a write scope (where the restriction toself-sourcesis implied by the caller's scopes) fails with a403 Forbidden(PERMISSION_DENIED) error. - To filter
sleepdata points bydataSourceFamily, use thereconcileendpoint instead.
Data source family examples
To list raw data points that your own client wrote for a user, call the list
endpoint with the dataSourceFamily query parameter set to
users/me/dataSourceFamilies/self-sources.
For example, the following GET request lists raw body-fat measurements
written by the calling client starting on 2026-03-01:
Request
GET https://health.googleapis.com/v4/users/me/dataTypes/body-fat/dataPoints ?dataSourceFamily=users/me/dataSourceFamilies/self-sources &filter=body_fat.sample_time.physical_time >= "2026-03-01T00:00:00Z" Authorization: Bearer access-token Accept: application/json
Response
{
"dataPoints": [
{
"name": "users/123456789/dataTypes/body-fat/dataPoints/1234567890",
"dataSource": {
"recordingMethod": "UNKNOWN",
"application": {
"packageName": "",
"webClientId": "",
"googleWebClientId": "google-web-client-id"
},
"platform": "GOOGLE_WEB_API"
},
"bodyFat": {
"sampleTime": {
"physicalTime": "2026-03-10T10:00:00Z",
"utcOffset": "0s",
"civilTime": {
"date": {
"year": 2026,
"month": 3,
"day": 10
},
"time": {
"hours": 10
}
}
},
"percentage": 20
}
}
],
"nextPageToken": ""
}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"
}
}
]
}