# Withings API to FHIR: heart rate, SpO₂, sleep and weight (with code)

> Read the Withings Public API and store measures, sleep and activity as FHIR R4 Observations, with OAuth, the 120/min limit and LOINC mapping.

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

**The Withings Public API gives your app a person's measures (weight and body composition, blood pressure, heart rate, SpO₂, temperature), activity and steps, sleep and ECG after they approve your OAuth app. Individuals and companies can use it without a formal contract; Withings offers paid plans for higher volume. You can store each reading as a LOINC-coded FHIR R4 Observation with one standard API call. Anpheros does not connect to Withings' cloud itself: your code reads Withings, and Anpheros keeps the record with the person's consent. On Android, the Withings app shares data with Health Connect in both directions.**

## Read Withings data and store it in FHIR

First, heart rate and SpO₂ measures from Withings (an access token from your Withings OAuth app with the `user.metrics` scope; every Withings call is a POST with an `action`):

```bash
curl -X POST -H "Authorization: Bearer $WITHINGS_TOKEN" \
  "https://wbsapi.withings.net/measure?action=getmeas&meastypes=11,54&category=1&startdate=1793664000&enddate=1793750400"
```

Then one SpO₂ reading 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":"vital-signs"}]}],
       "code":{"coding":[{"system":"http://loinc.org","code":"59408-5","display":"Oxygen saturation in Arterial blood by Pulse oximetry"}]},
       "subject":{"reference":"Patient/<id>"},
       "effectiveDateTime":"2026-11-03T07:14:00+01:00",
       "valueQuantity":{"value":96,"unit":"%","system":"http://unitsofmeasure.org","code":"%"},
       "device":{"display":"Withings device (Withings app)"}}'
```

The same in Python: every SpO₂ reading becomes one Observation, sent together in one transaction Bundle.

```python
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"}

# SpO₂ readings your backend already read with getmeas (measure type 54);
# parse the response as Withings' API reference describes.
spo2_readings = [("2026-11-02T07:20:00+01:00", 97), ("2026-11-03T07:14:00+01:00", 96)]

entries = [{
    "request": {"method": "POST", "url": "Observation"},
    "resource": {
        "resourceType": "Observation", "status": "final",
        "category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "vital-signs"}]}],
        "code": {"coding": [{"system": "http://loinc.org", "code": "59408-5",
                             "display": "Oxygen saturation in Arterial blood by Pulse oximetry"}]},
        "subject": {"reference": "Patient/<id>"},
        "effectiveDateTime": when,
        "valueQuantity": {"value": pct, "unit": "%", "system": "http://unitsofmeasure.org", "code": "%"},
        "device": {"display": "Withings device (Withings app)"},
    },
} for when, pct in spo2_readings]

r = requests.post(FHIR, headers=fhir, json={"resourceType": "Bundle", "type": "transaction", "entry": entries})
print(r.status_code, len(entries), "readings saved")
```

And in JavaScript (Node 18+), the night's lowest and highest heart rate from the sleep summary:

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

// From POST /v2/sleep with action=getsummary and data_fields=hr_min,hr_max (scope user.sleepevents).
const night = { start: '2026-11-02T23:10:00+01:00', end: '2026-11-03T06:50:00+01:00', hrMin: 51, hrMax: 78 };
const bpm = (value) => ({ value, unit: '/min', system: 'http://unitsofmeasure.org', code: '/min' });

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: 'http://loinc.org', code: '8867-4', display: 'Heart rate' }] },
    subject: { reference: 'Patient/<id>' },
    effectivePeriod: { start: night.start, end: night.end },
    component: [
      { code: { text: 'minimum' }, valueQuantity: bpm(night.hrMin) },
      { code: { text: 'maximum' }, valueQuantity: bpm(night.hrMax) },
    ],
    device: { display: 'Withings device (Withings app)' },
  }),
});
```

Request names (`getmeas`, `meastypes`, `getsummary`, `hr_min`, `hr_max`) are from Withings' OpenAPI specification; the response layout is 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 day is read again, give each Observation a stable identifier, as shown in [Wearable data in FHIR](https://developers.anpheros.com/guides/wearable-data-fhir).

## Ways to get Withings data into FHIR

| Path | Status | What you need |
|---|---|---|
| Your backend reads the Withings Public API, then writes FHIR to Anpheros | **Works today** | A Withings developer app and the person's OAuth consent. No contract for the Public API; paid plans for higher volume |
| Withings app → Health Connect → your Android app → Anpheros | **Works today** | Health Connect permissions. The person chooses the export categories in the Withings app. See [Health Connect to FHIR](https://developers.anpheros.com/guides/health-connect-fhir) |
| Withings app → Apple Health → your iPhone app → Anpheros | **Works today** | Withings Health Mate writes to Apple Health when the person turns it on; Withings does not publish the full list of types. See [Apple Health to FHIR](https://developers.anpheros.com/guides/apple-health-fhir) |
| A direct Withings connector inside Anpheros | **On our roadmap** | Not available yet. Use the button below to tell us you need it |

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

## The Withings API in short

**Access.** The Public API is meant for individuals and partners without a formal contract. Withings also lists higher-volume plans, an Enterprise plan and Withings Pro products; prices are not shown on the public page. A few services, such as `oauth2-listusers`, are only available under contract.

**Auth.** OAuth 2.0 authorization code flow. Send the person to `https://account.withings.com/oauth2_user/authorize2` with `response_type=code`, `client_id`, `scope`, `redirect_uri` and `state` (`mode=demo` gives demo data). The code is valid for only 30 seconds. Exchange it with `POST https://wbsapi.withings.net/v2/oauth2`, `action=requesttoken`, plus a `nonce` and a `signature` computed with your client secret. Access tokens last 3 hours and refresh tokens 1 year; the old refresh token stays valid for 8 hours after a new one is issued, or until the new access token is used. Scopes, comma-separated: `user.info`, `user.metrics`, `user.activity`, `user.sleepevents`.

| Data | Endpoint (POST, base `https://wbsapi.withings.net`) | Fields |
|---|---|---|
| Measures | `/measure`, `action=getmeas` | `meastype` or `meastypes`, `category`, `startdate`, `enddate`, `lastupdate`; types include 11 heart pulse, 54 SpO₂, 9 diastolic blood pressure, 73 skin temperature, 123 VO₂ max |
| Activity and steps | `/v2/measure`, `action=getactivity` (daily) or `getintradayactivity` | intraday can include heart rate, SpO₂, `rmssd`, `sdnn1`, `hrv_quality` |
| Sleep | `/v2/sleep`, `action=get` or `getsummary` | `get`: `hr`, `rr`, `snoring`, `sdnn_1`, `rmssd`; `getsummary`: `startdateymd`, `enddateymd`, `data_fields` such as `hr_min`, `hr_max`, `rmssd_start_avg` |
| ECG | `/v2/heart`, `action=list` or `get` | |
| Webhooks | `/notify`, `action=subscribe`, `list`, `get`, `update`, `revoke` | |

The standard API allows 120 requests per minute per application, and the Enterprise plan raises that. Use webhooks (`notify`) to learn when new data arrives instead of polling. Withings has reorganised its developer docs into a v3 guide covering the Public API, the Advanced Health Data API and the Research API.

Sources: [Withings Public API overview](https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/public-health-data-api-overview/), [authorization URL](https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/get-access/oauth-authorization-url/), [access and refresh tokens](https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/get-access/access-and-refresh-tokens-no-recover/), [OpenAPI specification](https://developer.withings.com/openapi.yaml), [keeping data up to date](https://developer.withings.com/developer-guide/v3/data-api/keep-user-data-up-to-date/), [Withings and Health Connect](https://support.withings.com/hc/en-us/articles/27322856325905-Partner-Apps-Sharing-data-with-Health-Connect), November 2026.

## Withings data as FHIR Observations

| Withings data | LOINC | UCUM unit | Note |
|---|---|---|---|
| Heart pulse (type 11) | 8867-4 | `/min` | |
| SpO₂ (type 54) | 59408-5 | `%` | |
| Blood pressure | 85354-9 with 8480-6 and 8462-4 | `mm[Hg]` | one Observation, systolic and diastolic as components |
| Body weight | 29463-7 | `kg` | |
| Steps (`getactivity`) | 41950-7 | `{steps}` | one per day |
| Sleep duration | 93832-4 | `min` | one per night |
| HRV (`rmssd`, `sdnn_1`) | see note | `ms` | Withings returns both. Only an SDNN value may use LOINC 80404-7; never file `rmssd` under it. See [Wearable data in FHIR](https://developers.anpheros.com/guides/wearable-data-fhir) |
| Respiratory rate in sleep (`rr`) | 9279-1 | `/min` | |

## Frequently asked questions

### Is the Withings API free?
The Withings Public API needs no formal contract. Withings sells plans for higher volume and enterprise use, but the prices are not on its public page.

### What is the Withings API rate limit?
120 requests per minute per application on the standard API. The Enterprise plan has higher limits.

### Why does my Withings authorization code stop working?
The code from the authorization URL is valid for only 30 seconds. Exchange it for tokens straight away on your server.

### How long do Withings access tokens last?
Access tokens last 3 hours and refresh tokens 1 year. After a refresh, the old refresh token stays valid for 8 hours or until you use the new access token.

### Which Withings HRV value goes under LOINC 80404-7?
Only an SDNN value, such as `sdnn_1` from the sleep data. `rmssd` is a different statistic and needs its own code.

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

## 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)
- [Oura API to FHIR](https://developers.anpheros.com/guides/oura-api) · [Garmin API to FHIR](https://developers.anpheros.com/guides/garmin-api) · [Dexcom API to FHIR](https://developers.anpheros.com/guides/dexcom-api)
