AnpherosAnpheros PlatformDevelopers

Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no cardYour first API call in 2 minutes →

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.

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):

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_…):

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.

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:

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
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
A direct Oura connector inside Anpheros On our roadmap Not available yet. Use the button below to tell us you need it

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, 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

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

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/oura-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.

Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no cardYour first API call in 2 minutes →

Product updates by email

New features, guides and the end of the beta. One email a month at most.

Send me occasional Anpheros Platform updates by email (new features, API changes, guides). I'll confirm by email and can unsubscribe in one click from any email. Privacy