Owner's Health
loading...
Public API
The heart-rate data shown on this page is also published as an unauthenticated, read-only API. CORS is enabled, so you can fetch it directly from the browser.
https://freedomflight.jp/health/v2/api/public
What the API returns right now
LoadingGET /latest?type=heart-rate
Endpoints
| Method / Path | Description |
|---|---|
| GET /types | List of published data types and their limits, so clients don't have to hardcode them. |
| GET /latest?type=heart-rate | The latest single sample, with an age_seconds field. |
| GET /series?type=heart-rate&minutes=60 | Time series. By default it returns the recorded granularity without decimation. |
/series parameters
| Name | Default | Description |
|---|---|---|
| type | heart-rate | Data type. Unknown / unpublished types return 404. |
| minutes | 60 | How many minutes back from the end. Max 1440 (24 h); anything larger returns 400. |
| end | now | End timestamp (ISO 8601). Use it to slice a window in the past. |
| max_points | 50000 | When set, the series is decimated to evenly spaced points. Check truncated to see whether decimation happened. |
Response example
$ curl -s 'https://freedomflight.jp/health/v2/api/public/latest?type=heart-rate'
{
"data_type": "heart-rate",
"label": "心拍数",
"unit": "bpm",
"value": 66.0,
"time": "2026-08-27T03:59:43Z", // UTC
"age_seconds": 465 // seconds since the sample was taken
}
$ curl -s 'https://freedomflight.jp/health/v2/api/public/series?type=heart-rate&minutes=60'
{
"data_type": "heart-rate",
"label": "心拍数",
"unit": "bpm",
"start": "2026-08-27T03:07:28Z",
"end": "2026-08-27T04:07:28Z",
"count": 1433,
"truncated": false, // true = decimated by max_points
"points": [
{ "t": "2026-08-27T03:07:30Z", "v": 61.0 },
{ "t": "2026-08-27T03:07:33Z", "v": 62.0 },
...
]
}
Resolution and freshness
Data is stored at the granularity recorded by the Fitbit Charge 6, so heart rate can be retrieved at a median interval of 2–3 seconds (roughly 36,000 points per day).
However, it is not real-time. The watch syncs via the phone to Google's cloud, so even the newest point is typically several to a dozen-plus minutes old. When the watch is away from the phone (e.g. while sleeping) gaps of several hours can occur. This lag comes from the data provider and cannot be shortened on our side.
For that reason, short windows like minutes=5
can legitimately return count = 0.
If you need "the current value", use /latest and judge staleness from
age_seconds. minutes of 30 or more is recommended.
Limits
- 60 requests / minute per IP. Beyond that you get 429 plus a
Retry-Afterheader. - Up to 24 hours / 50,000 points per request.
- Only heart rate is published (check with
/types). Other data types return 404. - Read-only — no write methods exist.
Usage examples
// Browser (CORS enabled)
const r = await fetch(
'https://freedomflight.jp/health/v2/api/public/series?type=heart-rate&minutes=60');
const { points, unit } = await r.json();
console.log(points.length, unit); // 1433 'bpm'
# Python
import urllib.request, json
BASE = "https://freedomflight.jp/health/v2/api/public"
with urllib.request.urlopen(f"{BASE}/latest?type=heart-rate") as r:
d = json.load(r)
print(f"{d['value']:.0f} {d['unit']} ({d['age_seconds'] // 60} min ago)")
# Last 6 hours decimated to 5-minute granularity
url = f"{BASE}/series?type=heart-rate&minutes=360&max_points=72"
with urllib.request.urlopen(url) as r:
s = json.load(r)
print(s["count"], "points", "decimated" if s["truncated"] else "as recorded")
Implementation: GoogleHealthCollector (collects and stores 42 data types from Google Health API v4). It is a separate pipeline from the heart-rate panel on this page; only heart rate from it is published.