Export WHOOP data to CSV or Parquet
Export WHOOP recovery, strain, sleep and workouts as CSV or Parquet, by calendar day, from a self-hosted instance that keeps syncing.
The WHOOP app can email you a one-off CSV export of your sleep, recovery and strain, organized by physiological cycle (menu, Data Export, per WHOOP's article). When you want more than a one-off file, Open Health Sync pulls your WHOOP data through the WHOOP API and keeps it in a database you run, so you can export it again at any time as CSV or Parquet, by calendar day, in the same columns as your other devices, or read it over an API or in SQL.
1. Start an instance
Follow the quickstart to Start the stack: clone the repository,
create .env with a fresh encryption key, and start it with Docker Compose. Then create your
own user:
docker compose --env-file .env -f deploy/docker-compose.yml exec -T ohs \
/ohs admin personal setup > ~/.ohs.env
chmod 600 ~/.ohs.env
set -a; . ~/.ohs.env; set +aNo WHOOP keys yet? ohs seed --demo writes 90 days of synthetic data that includes a WHOOP strap
(heart rate, sleep with stages, HRV, recovery and strain, workouts), so you can try the exports
below first (demo data).
2. Connect your WHOOP account
WHOOP data reaches the API through a WHOOP developer app that you register yourself:
-
In the WHOOP Developer Dashboard (
developer-dashboard.whoop.com), create an app with the scopes the WHOOP connector guide lists, and the redirect URL<OHS_PUBLIC_URL>/oauth/whoop/callback. -
Add its keys to
.envand restart:OHS_WHOOP_CLIENT_ID=... OHS_WHOOP_CLIENT_SECRET=... -
Open WHOOP's consent page and allow access:
python3 recipes/connect.py whoop
The first data arrives a few seconds later, and the history follows in the background. Check
that it is done when pending_jobs reaches 0:
curl -s "$OHS_URL/v1/users/$OHS_USER/coverage" -H "Authorization: Bearer $OHS_KEY" | jq .pending_jobs3. Export
Everything at once, one file per list:
python3 recipes/export-all.py # CSV, into health-export-<date>/
python3 recipes/export-all.py --format parquet # Parquet, for pandas, Polars or DuckDBOr one list at a time over the API. The daily summaries hold WHOOP's recovery and strain scores and the resting heart rate, one row per calendar day:
curl -sOJ "$OHS_URL/v1/users/$OHS_USER/exports/daily?format=csv&view=all" \
-H "Authorization: Bearer $OHS_KEY"| File | WHOOP data in it |
|---|---|
daily | recovery_score, strain_score, resting_hr, total_kcal, per day |
sleep | each sleep and nap: stage durations (slow wave sleep is deep), sleep performance as sleep_score, respiratory rate |
workouts | activity, energy, distance, average and maximum heart rate, elevation gain |
samples | hrv_rmssd, resting_heart_rate, oxygen_saturation, skin_temperature, strain, workout_strain, body_mass |
Every row has a provider column: with other devices connected too, keep the rows where it is
whoop. view=all keeps every source's records; the default merged view keeps one source per
figure when several measured the same thing
(why). Add start and end
(YYYY-MM-DD) for a window, and type=hrv_rmssd to the samples export for one measurement.
Columns and types are described in CSV and Parquet exports.
import pandas as pd
daily = pd.read_parquet("health-export-2026-10-18/daily.parquet")
whoop = daily[daily["provider"] == "whoop"][["date", "recovery_score", "strain_score", "resting_hr"]]
print(whoop.tail(14))What the WHOOP API does not give
WHOOP's public API serves no raw heart rate series, no hypnogram and no ECG, so neither the API nor this export has them. Its recovery, strain and sleep performance scores come as WHOOP computes them.
Next
- Keep it running: new data arrives on WHOOP's webhooks and scheduled pulls, and the same export picks it up next time.
- Add your other devices: every connector exports in the same columns.
- Get the data as FHIR R4 for a clinical or research system.
Get Oura data as FHIR R4
Turn Oura ring data into FHIR R4 Observations with LOINC codes and UCUM units: sleep sessions, heart rate, SpO2 and daily readiness scores, checked with the HL7 validator.
Client SDKs
Kotlin, Swift, React Native and Flutter clients: what they share and which credential goes where.