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, 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"
      }
    }
  ]
}