Two ways in
| HealthAPI's REST API | The Health app's export.xml | |
|---|---|---|
| How current | As of your iPhone's last sync | When you exported it |
| Format | JSON, one day or metric at a time | One XML file with every reading |
| Totals match the Health app | Yes, each source counted once | No, you deduplicate yourself |
| Cost | A HealthAPI plan | Free |
Read it from the API
Install HealthAPI on your iPhone and let it sync. Copy your key from the app (Connect, then Other tools) and keep it in an environment variable rather than in your code:
pip install requests pandas
export HEALTHAPI_KEY="your key"A small helper for any endpoint, and one for a metric's daily trend. The trends endpoint tells you which field carries the day's value: total for steps and energy, avg for heart rate, HRV and the like.
import os
import requests
import pandas as pd
API = "https://healthapi.app/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['HEALTHAPI_KEY']}"}
def get(path, **params):
r = requests.get(API + path, headers=HEADERS, params=params, timeout=30)
r.raise_for_status()
return r.json()
def trend(metric, days=90):
"""One metric, one row per day: the total for steps and energy, the average otherwise."""
t = get("/query/trends", type=metric, days=days)
column = "total" if t["aggregation"] == "sum" else "avg"
rows = pd.DataFrame(t["data"])
if rows.empty:
return pd.Series(dtype=float, name=metric)
return rows.set_index(pd.to_datetime(rows["date"]))[column].rename(metric)Three metrics in one DataFrame
df = pd.concat(
[trend("stepCount"), trend("restingHeartRate"), trend("heartRateVariability")],
axis=1,
)
print(df.tail())
print(df.corr().round(2)) stepCount restingHeartRate heartRateVariability
date
2026-10-14 9120 55.0 64.0
2026-10-15 12408 54.0 71.0
2026-10-16 7333 56.0 58.0
2026-10-17 10871 54.0 69.0
2026-10-18 11284 54.0 68.0Days without data are left out of each series, so they show as NaN after the join. Ninety days of three metrics is three requests. Metric names are on the metrics page; the full set of endpoints, including daily and weekly summaries with sleep, is in the API reference.
Workouts
w = pd.DataFrame(get("/health/workouts", start_date="2026-01-01", limit=1000))
w["hours"] = w["duration_seconds"] / 3600
print(w.groupby("activity_type")["hours"].sum().sort_values(ascending=False))Each workout has activity_type, start and end, duration_seconds, total_distance_meters and total_energy_kcal.
Your whole history at once
For a one-off analysis of everything, download HealthAPI's JSON export. It doesn't count toward your daily requests:
r = requests.get(API + "/gdpr/export", headers=HEADERS, timeout=300)
r.raise_for_status()
data = r.json()
samples = pd.DataFrame(data["health_samples"])
# HealthAPI's own daily totals carry this source; skip them to keep only readings.
readings = samples[~samples["source_app"].isin(["healthapi:agg", "healthapi:dstat"])]
hrv = readings[readings["type"] == "heartRateVariability"]Rows from healthapi:agg are deduplicated daily totals and healthapi:dstat rows are daily averages; the export guide explains the file.
Or parse the Health app's export.xml
No account needed: in the Health app, tap your picture, then Export All Health Data, and unzip it on your computer. The file is large, so stream it with iterparse instead of loading it whole:
import xml.etree.ElementTree as ET
import pandas as pd
rows = []
for _, el in ET.iterparse("apple_health_export/export.xml"):
if el.tag == "Record" and el.get("type") == "HKQuantityTypeIdentifierRestingHeartRate":
rows.append({
"start": el.get("startDate"),
"value": float(el.get("value")),
"source": el.get("sourceName"),
})
el.clear() # keeps memory flat on a file of several hundred MB
rhr = pd.DataFrame(rows)
rhr["start"] = pd.to_datetime(rhr["start"])Type names carry Apple's prefix (HKQuantityTypeIdentifierStepCount), dates include the timezone offset, and readings from your iPhone and Apple Watch overlap: summing steps from both gives more than the Health app shows. Filter by source, or use the API's totals, which already do this.
Questions
Is there an official Apple Health API for Python?
No. HealthKit is a framework for apps on iPhone, iPad and Apple Watch, written in Swift or Objective-C, and Apple offers no web API for Health data. From Python you either parse the export file or read the data from a service your iPhone syncs to, such as HealthAPI.
Can I read HealthKit from Python on a Mac?
No. The Mac has no Health database to read, so PyObjC's HealthKit bindings have nothing to return there. The data lives on your iPhone.
How many requests does the code use?
One per call: each trend is one request for up to 365 days, so the three-metric DataFrame is three. Pro allows 100 a day and Developer 1,000; the free preview allows 25. The JSON export doesn't count. Every response carries X-RateLimit-Remaining, and GET /usage doesn't count as one.
Why not just add up the readings?
Because iPhone and Apple Watch both record steps and energy for the same minutes, so a plain sum is too high. The trends and summaries count each source once and match the Health app; the raw readings don't.
Which metric names can I use?
HealthKit's own, without the prefix: stepCount, restingHeartRate, heartRateVariability, bodyMass and so on. The metrics page lists all 50 with their units.
