The Google Health API relies on filter expressions to restrict returned data
points when calling standard retrieval methods. Specifically, filters are
used with:

- [`list` endpoints](https://developers.google.com/health/reference/rest/v4/users.dataTypes.dataPoints/list.md.txt): querying raw data points.
- [`reconcile`
  endpoints](https://developers.google.com/health/reference/rest/v4/users.dataTypes.dataPoints/reconcile): querying reconciled stream data points.

This guide explains how filtering is implemented, the syntax and conventions,
and has examples for each record type.

<br />

**Important:** The format of data type identifiers differs when the data type has more than one word. In an endpoint, the data type name must be in kebab case, such as `body-fat`. In a filter parameter, however, the data type name must be in snake case, such as `body_fat`.

<br />

## Filter conventions

The Google Health API implements filters following the
[Google API Improvement Proposals (AIP-160)](https://google.aip.dev/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:

<br />

| 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` <br /> **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` <br /> **Example:** `body_fat.sample_time.physical_time` |
| **Daily** | Daily metrics and summaries (for example, `daily-heart-rate-variability`). | `{dataType}.date` <br /> **Example:** `daily_heart_rate_variability.date` |
| **Session** | Extended user sessions with specialized timing rules. | **Exercise:** `{dataType}.interval.civil_start_time` **Sleep:** `sleep.interval.end_time`, `sleep.interval.civil_end_time` **ECG:** `electrocardiogram.interval.start_time` <br /> **Example:** `sleep.interval.civil_end_time` |

<br />

## 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`):

<br />

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

<br />

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](https://developers.google.com/health/filters.md.txt#filter-by-data-source).

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

> [!IMPORTANT]
> **Important:** You must specify the `dataSourceFamily` parameter using the full resource URI format, such as `users/me/dataSourceFamilies/google-wearables`. Short identifier strings (like `"google-wearables"`, `"wearables"`, or `"FITBIT"`) are invalid and cause the API to return a `400 Bad Request` with an `INVALID_ARGUMENT` error.

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 `dataSourceFamily` parameter, or with `dataSourceFamily` set to `users/me/dataSourceFamilies/self-sources`, are implicitly restricted to `self-sources`.
- Requesting any other data source family (such as `all-sources`, `google-wearables`, or `google-sources`) with only a write scope fails with a `403 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 `dataSourceFamily` on `list` for `sleep`, `food`, or `food-measurement-unit` fails with a `400 Bad Request` (`INVALID_ARGUMENT`) error.
- Calling `list` for these data types when granted only a write scope (where the restriction to `self-sources` is implied by the caller's scopes) fails with a `403 Forbidden` (`PERMISSION_DENIED`) error.
- To filter `sleep` data points by `dataSourceFamily`, use the [`reconcile` endpoint](https://developers.google.com/health/reference/rest/v4/users.dataTypes.dataPoints/reconcile.md.txt) 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"
      }
    }
  ]
}
```