Filter data

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

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_time
Sleep: sleep.interval.end_time, sleep.interval.civil_end_time
ECG: 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 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 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"
      }
    }
  ]
}