---
title: "Heart Rate Zones API: Zone Thresholds and Time in Zone"
canonical: "https://aifitnessapi.com/data/heart-rate-zones-api"
cluster: "Health Data"
primary_query: "heart rate zones api"
last_reviewed: "2026-10-18"
description: "Heart rate zones as data: Google Health API daily thresholds, time and calories per zone, Active Zone Minutes, and Bluetooth User Data zone limits."
publisher: "AIFitnessAPI — independent, not sponsored"
cite_as: "\"Heart Rate Zones API: Zone Thresholds and Time in Zone\", AIFitnessAPI, https://aifitnessapi.com/data/heart-rate-zones-api"
---

# Heart Rate Zones API: Zone Thresholds and Time in Zone

> Heart rate zones exist as data on two platforms here. The Google Health API computes them: daily-heart-rate-zones gives each day's light, moderate, vigorous and peak zones with a minimum and maximum heart rate, which Google describes as thresholds based on the Karvonen algorithm, and time-in-heart-rate-zone, calories-in-heart-rate-zone and active-zone-minutes report what the user did in them. Bluetooth's User Data service stores a user's zones instead: the Three, Four and Five Zone Heart Rate Limits characteristics hold the boundaries between zones as single bytes in beats per minute. HealthKit, Health Connect and Wear OS Health Services define no zone type, so on those platforms zones are something your app computes from heart rate samples, with thresholds it chooses and discloses.

- Canonical: https://aifitnessapi.com/data/heart-rate-zones-api
- Last reviewed: 2026-10-18
- Publisher: AIFitnessAPI (https://aifitnessapi.com) — independent, not sponsored
- Cite as: "Heart Rate Zones API: Zone Thresholds and Time in Zone", AIFitnessAPI, https://aifitnessapi.com/data/heart-rate-zones-api

---

A heart rate zone is a range of heart rates with a name. Whether an API gives you zones depends on who owns the range: a platform that computes it from the user's data, a device that stores what the user set, or nobody, in which case your app draws the lines. All three situations exist across the platforms this site covers.

## Computed zones: the Google Health API

| Type | Record | What it holds | Operations |
|---|---|---|---|
| [`daily-heart-rate-zones`](/google-health-api/daily-heart-rate-zones) | Daily | "User's heart rate zone thresholds based on the Karvonen algorithm for a specific day": a list of zones, each with a type and a minimum and maximum in beats per minute | `list`, `reconcile` |
| [`time-in-heart-rate-zone`](/google-health-api/time-in-heart-rate-zone) | Interval | "an interval spent in specific heart rate zone," tagged with the zone type | `list`, `reconcile`, `rollup`, `dailyRollup` |
| [`calories-in-heart-rate-zone`](/google-health-api/calories-in-heart-rate-zone) | Interval | "the amount of kilocalories burned in a specific heart rate zone" | `rollup`, `dailyRollup` only |
| [`active-zone-minutes`](/google-health-api/active-zone-minutes) | Interval | minutes earned in a zone, counted 1 in the fat-burn zone and 2 in cardio or peak | `list`, `reconcile`, `rollup`, `dailyRollup` |

Read from Google's documentation on 2026-10-17. All four have webhook support in Google's table. The thresholds type sits under the health-metrics-and-measurements scopes and the other three under activity-and-fitness, so an app reading both thresholds and time in zone asks for two scope families.

`calories-in-heart-rate-zone` has no data point at all: Google's table lists only the two rollups, and neither discovery document defines a data point property for it, which the type's page shows as a flag. You get calories per zone only as a rolled-up result, a list of zone and `kcal` pairs. Google's migration table maps the Fitbit Web API's calories-out-per-zone data to it, and its Active Zone Minutes to `active-zone-minutes`.

## Two zone vocabularies inside one API

The thresholds, time in zone and calories in zone use one set of names: `LIGHT`, `MODERATE`, `VIGOROUS` and `PEAK`. Active Zone Minutes uses another: `FAT_BURN`, `CARDIO` and `PEAK`. Google states no mapping between the two lists, and they have different lengths. Do not translate one into the other in your schema; store the zone name with the vocabulary it came from.

Google's thresholds are also dated. Each `daily-heart-rate-zones` record is "for a specific day," with the day's date in the user's time zone, so two days' time-in-zone figures may rest on different boundaries. If you show zone boundaries beside a historical chart, read the record for that day.

## Stored limits: Bluetooth User Data

The Bluetooth SIG takes the other approach. Its User Data service lists characteristics that describe a user, and three of them define zone systems:

| Characteristic | Zones | Fields |
|---|---|---|
| [Three Zone Heart Rate Limits 0x2A94](/bluetooth-fitness/three-zone-heart-rate-limits) | Hard, Moderate, Light | the Light (Fat burn) / Moderate (Aerobic) limit and the Moderate (Aerobic) / Hard (Anaerobic) limit |
| [Four Zone Heart Rate Limits 0x2B4C](/bluetooth-fitness/four-zone-heart-rate-limits) | Maximum, Hard, Moderate, Light | three limits between them |
| [Five Zone Heart Rate Limits 0x2A8B](/bluetooth-fitness/five-zone-heart-rate-limits) | Maximum, Hard, Moderate, Light, Very Light | four limits between them |

Read from the SIG's files on 2026-10-17. Every limit is a `uint8` in beats per minute, so a characteristic stores the boundaries between zones, one fewer than the number of zones, where Google stores a minimum and maximum for each zone. The same list has a [Two Zone Heart Rate Limits](/bluetooth-fitness#char-two-zone-heart-rate-limits) characteristic, plus aerobic, anaerobic and fat-burn limits and a [Heart Rate Max](/bluetooth-fitness#char-heart-rate-max), all rows on the GATT reference without pages of their own here.

The SIG's names do not line up with Google's either: its three-zone system calls the lowest zone Light (Fat burn), close to Active Zone Minutes' fat-burn zone, while its four- and five-zone systems name a Maximum zone where Google names Peak. Treat each as its own scheme.

## No zone type on HealthKit, Health Connect or Wear OS

Apple's identifier list, Health Connect's record list and Wear OS's `DataType` constants have no zone type. What they have is heart rate itself: HealthKit's [`heartRate`](/healthkit-identifiers/heart-rate) samples, Health Connect's [`HeartRateRecord`](/health-connect/heart-rate-record) series and Wear OS's [`HEART_RATE_BPM`](/wear-os-data-types/heart-rate-bpm). Zones on those platforms are your computation. The Karvonen algorithm Google names is not specified further in the Google pages this site's data was generated from, so this site cannot tell you the inputs Google uses; if you compute zones yourself, choose a method, show it to the user, and store the boundaries you used beside each result. The [heart rate API](/data/heart-rate-api) guide covers reading the samples.

## Traps

- **Two vocabularies, one API.** `PEAK` appears in both of Google's lists; the zones below it do not correspond.
- **Rollup-only calories.** Code that lists every Google type will find nothing to list for calories in zone.
- **Boundaries versus ranges.** Converting Bluetooth limits into Google-style ranges needs a lowest and highest value the characteristic does not carry.
- **Thresholds move.** Google's zones are per day; a single "your zones" screen should say which day it shows.
- **Scope families.** Thresholds and time in zone sit under different Google scopes.

## Before you ship

Re-read the Google type pages and the Bluetooth characteristic pages before each release; they regenerate from Google's and the SIG's sources. Keep each zone's vocabulary with it, read Google's thresholds for the day you display, and if your app draws its own zones on HealthKit or Health Connect data, say how.

## FAQ

### How does the Google Health API define its heart rate zones?

Google describes daily-heart-rate-zones as the user's heart rate zone thresholds based on the Karvonen algorithm for a specific day. Each Daily record lists zones of type LIGHT, MODERATE, VIGOROUS and PEAK, each with a minimum and maximum heart rate in beats per minute. The Google pages this site reads do not give the algorithm's inputs, so treat the thresholds as Google's output for that date rather than something to recompute.

[Permalink](https://aifitnessapi.com/data/heart-rate-zones-api#faq-1)

### Do HealthKit or Health Connect store heart rate zones?

No. Neither Apple's HealthKit identifiers nor Health Connect's record classes, nor Wear OS Health Services' data types, include a heart rate zone type. They store heart rate itself, as HealthKit heartRate samples, Health Connect HeartRateRecord series and Wear OS HEART_RATE_BPM, so on those platforms zones are computed by your app with thresholds you choose and should disclose.

[Permalink](https://aifitnessapi.com/data/heart-rate-zones-api#faq-2)

### What is the difference between time in heart rate zone and Active Zone Minutes?

Time in heart rate zone is a plain duration: intervals tagged LIGHT, MODERATE, VIGOROUS or PEAK, rolled up into the time spent in each. Active Zone Minutes are weighted minutes in a different zone vocabulary, FAT_BURN, CARDIO and PEAK, where Google counts a minute as 1 in the fat-burn zone and 2 in cardio or peak. They answer different questions and do not convert into each other.

[Permalink](https://aifitnessapi.com/data/heart-rate-zones-api#faq-3)

### How do Bluetooth devices store heart rate zone limits?

As User Data service characteristics that hold the boundaries between zones in beats per minute, each limit a single uint8. Three Zone Heart Rate Limits (0x2A94) holds two limits, Four Zone (0x2B4C) three and Five Zone (0x2A8B) four, so each stores one fewer value than it has zones. They are settings a device keeps for a user, not measurements of time spent in a zone.

[Permalink](https://aifitnessapi.com/data/heart-rate-zones-api#faq-4)
