Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no cardYour first API call in 2 minutes →
WHOOP API to FHIR: sleep, recovery and HRV (with code)
Read WHOOP API v2 sleep and recovery and store them as FHIR R4 Observations: OAuth scopes, rate limits, RMSSD handling and Apple Health limits.
The WHOOP API v2 gives your app a person's sleep, recovery, strain cycles, workouts and body measurements after they approve your OAuth app. Any WHOOP account can create apps in the WHOOP Developer Dashboard. You can store each reading as a LOINC-coded FHIR R4 Observation with one standard API call. Anpheros does not connect to WHOOP's cloud itself: your code reads WHOOP, and Anpheros keeps the record with the person's consent. On phones, the WHOOP app writes sleep, resting heart rate, respiratory rate, blood oxygen and steps to Apple Health and Health Connect, but it does not write HRV to Apple Health.
Read WHOOP data and store it in FHIR
First, the person's sleep records from WHOOP (an access token from your WHOOP OAuth app with the read:sleep scope):
curl -H "Authorization: Bearer $WHOOP_TOKEN" \
"https://api.prod.whoop.com/developer/v2/activity/sleep"
Then the sleep duration as a FHIR Observation in the person's record (sandbox key sk_test_…):
curl -X POST https://platform.anpheros.com/fhir/R4/Observation \
-H "Authorization: Bearer $ANPHEROS_KEY" \
-H "Content-Type: application/fhir+json" \
-H "X-Anpheros-Author-Type: device" \
-d '{"resourceType":"Observation","status":"final",
"category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"activity"}]}],
"code":{"coding":[{"system":"http://loinc.org","code":"93832-4","display":"Sleep duration"}]},
"subject":{"reference":"Patient/<id>"},
"effectivePeriod":{"start":"2026-11-02T23:05:00+01:00","end":"2026-11-03T06:40:00+01:00"},
"valueQuantity":{"value":418,"unit":"min","system":"http://unitsofmeasure.org","code":"min"},
"device":{"display":"WHOOP"}}'
The same in Python: every night becomes one Observation, sent together in one transaction Bundle.
import os, requests
FHIR = "https://platform.anpheros.com/fhir/R4"
fhir = {"Authorization": f"Bearer {os.environ['ANPHEROS_KEY']}", "Content-Type": "application/fhir+json",
"X-Anpheros-Author-Type": "device"}
# Nights your backend already read from GET /v2/activity/sleep; map WHOOP's fields
# (listed in its API reference) to start, end and minutes asleep.
sleep_by_night = [
{"start": "2026-11-01T23:30:00+01:00", "end": "2026-11-02T07:02:00+01:00", "minutes": 421},
{"start": "2026-11-02T23:05:00+01:00", "end": "2026-11-03T06:40:00+01:00", "minutes": 418},
]
entries = [{
"request": {"method": "POST", "url": "Observation"},
"resource": {
"resourceType": "Observation", "status": "final",
"category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "activity"}]}],
"code": {"coding": [{"system": "http://loinc.org", "code": "93832-4", "display": "Sleep duration"}]},
"subject": {"reference": "Patient/<id>"},
"effectivePeriod": {"start": n["start"], "end": n["end"]},
"valueQuantity": {"value": n["minutes"], "unit": "min", "system": "http://unitsofmeasure.org", "code": "min"},
"device": {"display": "WHOOP"},
},
} for n in sleep_by_night]
r = requests.post(FHIR, headers=fhir, json={"resourceType": "Bundle", "type": "transaction", "entry": entries})
print(r.status_code, len(entries), "nights saved")
And in JavaScript (Node 18+), the night's HRV from a recovery record. WHOOP measures HRV as RMSSD, so it goes under your own code, not under LOINC 80404-7 (which is SDNN):
const fhir = { Authorization: `Bearer ${process.env.ANPHEROS_KEY}`, 'Content-Type': 'application/fhir+json' };
// From GET /v2/recovery (scope read:recovery); field names are in WHOOP's API reference.
const recoveryForDay = { day: '2026-11-03', hrvRmssdMs: 62.4 };
await fetch('https://platform.anpheros.com/fhir/R4/Observation', {
method: 'POST', headers: fhir,
body: JSON.stringify({
resourceType: 'Observation', status: 'final',
category: [{ coding: [{ system: 'http://terminology.hl7.org/CodeSystem/observation-category', code: 'vital-signs' }] }],
code: { coding: [{ system: 'https://your-app.test/fhir/CodeSystem/wearable', code: 'hrv-rmssd' }],
text: 'Heart rate variability (RMSSD)' },
subject: { reference: 'Patient/<id>' },
effectiveDateTime: `${recoveryForDay.day}T07:00:00+01:00`,
valueQuantity: { value: recoveryForDay.hrvRmssdMs, unit: 'ms', system: 'http://unitsofmeasure.org', code: 'ms' },
device: { display: 'WHOOP' },
}),
});
WHOOP's response field names are in its API reference, so the examples start from values your code has already read. The Anpheros side is standard FHIR R4, so the same requests work with any FHIR server. To avoid copies when a night is read again, give each Observation a stable identifier, as shown in Wearable data in FHIR.
Ways to get WHOOP data into FHIR
| Path | Status | What you need |
|---|---|---|
| Your backend reads the WHOOP API v2, then writes FHIR to Anpheros | Works today | An app in the WHOOP Developer Dashboard (up to 5 per account) and the person's OAuth consent |
| WHOOP app → Apple Health → your iPhone app reads HealthKit → Anpheros | Works today | HealthKit permissions. No HRV: WHOOP does not write it to Apple Health. See Apple Health to FHIR |
| WHOOP app → Health Connect → your Android app → Anpheros | Works today | Health Connect permissions. Some types depend on the WHOOP plan; resting heart rate and respiratory rate need Peak. See Health Connect to FHIR |
| A direct WHOOP connector inside Anpheros | On our roadmap | Not available yet. Use the button below to tell us you need it |
The WHOOP API in short
Access. Sign in to the WHOOP Developer Dashboard with a WHOOP account and create an app. Each account can have up to 5 apps, and you can ask for more. WHOOP's developer docs do not say whether the person needs a paid membership or whether an app is reviewed before wide release. A separate client-credentials flow exists for WHOOP's Trusted Partner endpoints.
Auth. OAuth 2.0 authorization code flow. Authorize at https://api.prod.whoop.com/oauth/oauth2/auth, exchange the code at https://api.prod.whoop.com/oauth/oauth2/token. The state you send must be 8 characters. Scopes: read:recovery, read:cycles, read:workout, read:sleep, read:profile, read:body_measurement, and offline for a refresh token. Access tokens are short-lived (the docs' example shows one hour). Refresh tokens rotate: each refresh invalidates the old access and refresh tokens.
| Data | Endpoint (GET, base https://api.prod.whoop.com/developer) |
Fields |
|---|---|---|
| Sleep | /v2/activity/sleep, /v2/activity/sleep/{sleepId} |
sleep records |
| Recovery | /v2/recovery, /v2/cycle/{cycleId}/recovery |
recovery, including HRV and resting heart rate |
| Cycles (strain) | /v2/cycle, /v2/cycle/{cycleId}, /v2/cycle/{cycleId}/sleep |
daily strain cycles |
| Workouts | /v2/activity/workout |
workouts |
| Body | /v2/user/measurement/body |
height, weight, max heart rate |
| Profile | /v2/user/profile/basic |
basic profile |
There is no steps endpoint in the API; WHOOP writes steps to Apple Health and Health Connect instead. DELETE /v2/user/access revokes your app's access, and GET /v1/activity-mapping/{activityV1Id} turns an old v1 id into the v2 UUID. Default rate limits are 100 requests per minute and 10,000 per day, with X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers and a 429 when you go over; ask for more in the dashboard. All data endpoints are now v2 with UUID ids, and WHOOP has removed its v1 webhooks and publishes a v1 to v2 migration guide.
Sources: WHOOP API reference, getting started, OAuth, rate limiting, WHOOP and Apple Health, WHOOP and Health Connect, November 2026.
WHOOP data as FHIR Observations
| WHOOP data | LOINC | UCUM unit | Note |
|---|---|---|---|
| Sleep duration | 93832-4 | min |
one per night |
| Resting heart rate (recovery) | 40443-4 | /min |
one per day |
| HRV (recovery) | see note | ms |
WHOOP uses RMSSD; never file it under LOINC 80404-7, which is SDNN. Use your own code. See Wearable data in FHIR |
| Respiratory rate | 9279-1 | /min |
|
| Blood oxygen (SpO₂) | 59408-5 | % |
|
| Body weight | 29463-7 | kg |
from body measurements |
| Recovery score, strain | none | WHOOP scores with no LOINC code; keep them under your own code system or leave them out |
Frequently asked questions
How do I get access to the WHOOP API?
Sign in to the WHOOP Developer Dashboard with a WHOOP account and create an app, then send people through WHOOP's OAuth flow. Each account can hold up to 5 apps, and you can ask for more.
Is the WHOOP API free?
WHOOP's developer docs do not state a price, and they do not say whether the person needs a paid membership for API data.
Does WHOOP sync HRV to Apple Health?
No. WHOOP says it does not write HRV to Apple Health, because WHOOP measures RMSSD and Apple Health stores SDNN. To get HRV, read it from the WHOOP API recovery data.
Does the WHOOP API have steps?
No steps endpoint is listed in the WHOOP API. The WHOOP app writes steps to Apple Health and Health Connect, so read them there.
What is the WHOOP API rate limit?
100 requests per minute and 10,000 per day by default. You can ask for an increase in the Developer Dashboard.
Does Anpheros connect to WHOOP directly?
Not yet. Today your code reads the WHOOP API (or HealthKit / Health Connect on the phone) and writes standard FHIR to Anpheros. A direct WHOOP connector is on our roadmap; tell us with the button on this page if you need it.
Related
- Wearable APIs: every brand compared
- Wearable data in FHIR (Apple Health, Health Connect)
- Apple Health (HealthKit) to FHIR
- Health Connect to FHIR
- Oura API to FHIR · Garmin API to FHIR · Polar AccessLink API to FHIR
Ask your AI
Paste this into Claude, ChatGPT, Cursor or Claude Code. Your assistant reads this guide and adapts it to your code.
Read https://developers.anpheros.com/guides/whoop-api.md and help me apply this guide to my own project. Use the Anpheros sandbox (base URL https://platform.anpheros.com) with my API key from the ANPHEROS_KEY environment variable, never written in the code. Give me complete, runnable code, the response I should expect, and how to fix the errors in https://developers.anpheros.com/guides/troubleshooting.md if a call fails.Open in ClaudeOpen in ChatGPTThis guide as MarkdownBuild the whole integration with an AI coding tool
Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no cardYour first API call in 2 minutes →