# Oura API to FHIR: Oura sleep, HRV and heart rate in your app

> Read sleep, HRV, heart rate, SpO₂ and steps from the Oura API v2 and store them as LOINC-coded FHIR R4 Observations, with curl, Python and JavaScript; access, OAuth, endpoints and limits.

Source: https://developers.anpheros.com/guides/oura-api

**The Oura API v2 gives your app a person's sleep, heart rate, HRV, SpO₂ and activity after they approve your OAuth app. You can store each reading as a LOINC-coded FHIR R4 Observation with one standard API call. Anpheros does not connect to Oura's cloud itself. Your code reads Oura, and Anpheros keeps the record with the person's consent. On phones, the Oura app also writes to Apple Health and Health Connect, so the same data can reach you through HealthKit or Health Connect.**

## Read Oura sleep and store it in FHIR (5 minutes)

First, the night's sleep from Oura (an access token from your Oura OAuth app; Oura asks for a `User-Agent`):

```bash
curl -H "Authorization: Bearer $OURA_TOKEN" \
     -H "User-Agent: YourApp/1.0" \
     "https://api.ouraring.com/v2/usercollection/sleep?start_date=2026-11-02&end_date=2026-11-03"
```

Then the sleep duration as a FHIR Observation in the person's record (sandbox key `sk_test_…`):

```bash
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:10:00+01:00","end":"2026-11-03T06:52:00+01:00"},
       "valueQuantity":{"value":431,"unit":"min","system":"http://unitsofmeasure.org","code":"min"},
       "device":{"display":"Oura Ring"}}'
```

The same in Python: every sleep period becomes one Observation, sent together in one transaction Bundle.

```python
import os, requests

OURA = "https://api.ouraring.com/v2/usercollection"
FHIR = "https://platform.anpheros.com/fhir/R4"
oura = {"Authorization": f"Bearer {os.environ['OURA_TOKEN']}", "User-Agent": "YourApp/1.0"}
fhir = {"Authorization": f"Bearer {os.environ['ANPHEROS_KEY']}", "Content-Type": "application/fhir+json"}

sleep = requests.get(f"{OURA}/sleep", headers=oura,
                     params={"start_date": "2026-11-01", "end_date": "2026-11-03"}).json()["data"]

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": s["bedtime_start"], "end": s["bedtime_end"]},
        "valueQuantity": {"value": round(s["total_sleep_duration"] / 60), "unit": "min",
                          "system": "http://unitsofmeasure.org", "code": "min"},
        "device": {"display": "Oura Ring"},
    },
} for s in sleep if s.get("total_sleep_duration")]

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+), steps per day from `daily_activity`:

```js
const oura = { Authorization: `Bearer ${process.env.OURA_TOKEN}`, 'User-Agent': 'YourApp/1.0' };
const fhir = { Authorization: `Bearer ${process.env.ANPHEROS_KEY}`, 'Content-Type': 'application/fhir+json' };

const { data } = await (await fetch(
  'https://api.ouraring.com/v2/usercollection/daily_activity?start_date=2026-11-01&end_date=2026-11-03',
  { headers: oura })).json();

for (const day of data) {
  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: 'activity' }] }],
      code: { coding: [{ system: 'http://loinc.org', code: '41950-7', display: 'Number of steps in 24 hour Measured' }] },
      subject: { reference: 'Patient/<id>' },
      effectivePeriod: { start: day.day, end: day.day },
      valueQuantity: { value: day.steps, unit: '{steps}', system: 'http://unitsofmeasure.org', code: '{steps}' },
      device: { display: 'Oura Ring' },
    }),
  });
}
```

Field names (`total_sleep_duration` in seconds, `bedtime_start`, `bedtime_end`, `steps`) are from Oura's API v2 specification. The Anpheros side is standard FHIR R4, so the same requests work with any FHIR server.

## Ways to get Oura data into FHIR

| Path | Status | What you need |
|---|---|---|
| Your backend reads the Oura API v2, then writes FHIR to Anpheros | **Works today** | An Oura OAuth app, the person's consent, an active Oura Membership |
| Oura app → Apple Health → your iPhone app reads HealthKit → Anpheros | **Works today** | HealthKit permissions. See [Apple Health to FHIR](https://developers.anpheros.com/guides/apple-health-fhir) |
| Oura app → Health Connect → your Android app → Anpheros | **Works today** | Health Connect permissions. Gen3 and later rings need a membership. See [Health Connect to FHIR](https://developers.anpheros.com/guides/health-connect-fhir) |
| A direct Oura connector inside Anpheros | **On our roadmap** | Not available yet. Use the button below to tell us you need it |

<!-- visual:cta-wear-oura -->

## The Oura API v2 in short

**Access.** Register an API application in Oura's developer portal. A new application works for up to 10 users until Oura approves it; after approval there is no user limit. Each person approves every data type you ask for. When a person's Oura subscription has expired, the API answers 403. Personal access tokens stopped working in December 2025, so every integration now uses OAuth. API v1 was removed in January 2024.

**Auth.** OAuth 2.0 authorization code flow (server side), or a client-side implicit flow with a token of about 30 days and no refresh token. Authorize at `https://cloud.ouraring.com/oauth/authorize`, exchange the code at `https://api.ouraring.com/oauth/token`. Refresh tokens are single use: each refresh returns a new one. Scopes: `email`, `personal`, `daily`, `heartrate`, `workout`, `tag`, `session`, `spo2`, and `heart_health` in the newest specification.

| Data | Endpoint (GET, base `https://api.ouraring.com`) | Fields you will use |
|---|---|---|
| Sleep periods | `/v2/usercollection/sleep` | `total_sleep_duration`, stage durations, `average_heart_rate`, `lowest_heart_rate`, `average_hrv`, `hrv`, `average_breath` |
| Sleep score | `/v2/usercollection/daily_sleep` | daily score and contributors |
| Heart rate | `/v2/usercollection/heartrate` | `timestamp`, `bpm`, `source` (5-minute samples) |
| SpO₂ | `/v2/usercollection/daily_spo2` | `spo2_percentage.average`, `breathing_disturbance_index` |
| Steps and activity | `/v2/usercollection/daily_activity` | `steps`, calories |
| Also | `daily_readiness`, `daily_stress`, `daily_resilience`, `vO2_max`, `workout`, `session`, `tag` | |

HRV has no endpoint of its own: it comes inside `sleep`. Every endpoint has a sandbox twin under `/v2/sandbox/usercollection/…` with sample data. Webhooks (`/v2/webhook/subscription`) notify your server about 30 seconds after the ring syncs; Oura suggests one historical pull when a person connects, then webhooks. Rate limits apply per access token and per application, and a `429` comes with `Retry-After` and `X-RateLimit-*` headers. Sleep data appears after the person opens the Oura app.

Sources: [Oura API v2 documentation and OpenAPI specification](https://cloud.ouraring.com/v2/docs), [authentication](https://cloud.ouraring.com/docs/authentication), November 2026.

## Oura data as FHIR Observations

| Oura data | LOINC | UCUM unit | Note |
|---|---|---|---|
| Sleep duration (`total_sleep_duration`, seconds) | 93832-4 | `min` | divide by 60 |
| Deep, REM, light sleep | 93831-6, 93829-0, 93830-8 | `min` | components or separate Observations |
| Steps (`daily_activity.steps`) | 41950-7 | `{steps}` | one per day |
| Heart rate (`heartrate.bpm`) | 8867-4 | `/min` | daily min, average and max rather than every sample |
| Lowest heart rate in sleep | 40443-4 | `/min` | resting heart rate |
| SpO₂ average | 59408-5 | `%` | |
| HRV (`average_hrv`) | see note | `ms` | Oura reports a night-time average; do not file it under LOINC 80404-7 unless it is SDNN. See [Wearable data in FHIR](https://developers.anpheros.com/guides/wearable-data-fhir) |

## Frequently asked questions

### Is the Oura API free?
Oura does not charge developers for the API. People need an active Oura Membership for their data to come through the API: when it has expired, requests return 403.

### Can I still use an Oura personal access token?
No. Oura deprecated personal access tokens in December 2025. Create an OAuth application, even for your own data.

### Where is HRV in the Oura API?
Inside the sleep periods (`/v2/usercollection/sleep`): `average_hrv` for the night and `hrv` as a series. There is no separate HRV endpoint.

### How many users can my Oura app have?
Up to 10 until Oura approves the application; after approval, no limit.

### Does Anpheros connect to Oura directly?
Not yet. Today your code reads the Oura API (or HealthKit / Health Connect on the phone) and writes standard FHIR to Anpheros. A direct Oura connector is on our roadmap; tell us with the button on this page if you need it.

### What is the Oura API rate limit?
Oura limits each access token and each application and answers 429 with a `Retry-After` header. The current specification does not publish a fixed number, so read the `X-RateLimit-*` headers.

## Related

- [Wearable APIs: every brand compared](https://developers.anpheros.com/guides/wearable-apis)
- [Wearable data in FHIR (Apple Health, Health Connect)](https://developers.anpheros.com/guides/wearable-data-fhir)
- [Apple Health (HealthKit) to FHIR](https://developers.anpheros.com/guides/apple-health-fhir)
- [Health Connect to FHIR](https://developers.anpheros.com/guides/health-connect-fhir)
- [Garmin API to FHIR](https://developers.anpheros.com/guides/garmin-api) · [WHOOP API to FHIR](https://developers.anpheros.com/guides/whoop-api) · [Withings API to FHIR](https://developers.anpheros.com/guides/withings-api)
