AnpherosAnpheros PlatformDevelopers

Wearable data in FHIR (Apple Health, Health Connect)

Get smartwatch, ring and scale data into a FHIR record: which devices reach which health store, a LOINC mapping table, daily summaries and a tested upsert.

To get data from a smartwatch, ring or smart scale into a medical record, read it on the phone from the operating system's health store — Apple Health on iOS, Health Connect on Android — summarise it per day, map each metric to a LOINC code and a UCUM unit, and write it as FHIR R4 Observations marked as device data. Reading from the phone's health store instead of from each vendor's cloud lets one integration cover Apple Watch, Galaxy Watch, Pixel Watch, Garmin, Fitbit, Oura and Withings, as long as the person has allowed their vendor app to share. This page gives the data path, a mapping table you can reuse, a validated FHIR example and code that was run against the platform.

The data path

 watch, ring, scale ──► vendor app (Garmin Connect, Fitbit, Oura, Withings, Samsung Health…)
                              │  shares, with the person's permission
                              ▼
                 Apple Health (iOS)  /  Health Connect (Android)
                              │  your app reads, with the person's permission, on the phone
                              ▼
                 your app ──► your backend ──► FHIR R4 Observations in the person's record

Neither Apple Health nor Health Connect has a server API: data can only be read by an app on the person's phone. Your app reads it, sends compact daily values to your backend, and the backend writes them to the record.

Which devices reach which health store

What each vendor shares changes with app versions; the vendor's own documentation is the reference.

Device or app Apple Health (iOS) Health Connect (Android) Notes
Apple Watch Yes, natively — Everything the watch measures lands in Apple Health
Samsung Galaxy Watch, Samsung Health — Partly Samsung Health decides which types it exports and how often; see Samsung Health
Pixel Watch, Fitbit No official sync Yes, through the Fitbit app (Google help)
Garmin Yes, from Garmin Connect after each device sync Yes, Garmin Connect supports Health Connect on Android 14 and later (Android Developers, August 2025) Garmin-specific scores such as Body Battery are not shared
Oura Yes Yes (Oura) Readiness and other Oura scores stay in the Oura app
Withings Yes, through Health Mate Yes (Android Developers blog) Scales, blood pressure monitors, watches

Two practical consequences. First, your app depends on the person having switched sharing on in their vendor app; onboarding should say so. Second, the health store may hold the same steps from two sources (a phone and a watch); read the store's own daily totals rather than adding up samples, because Apple Health and Health Connect already de-duplicate overlapping sources.

Mapping table: health store type → FHIR Observation

The codes Anpheros Daily uses when it writes wearable data to a FHIR record:

Metric Apple HealthKit Health Connect LOINC UCUM unit Category
Heart rate heartRate HeartRateRecord 8867-4 /min vital-signs
Resting heart rate restingHeartRate RestingHeartRateRecord 40443-4 /min vital-signs
Heart rate variability heartRateVariabilitySDNN HeartRateVariabilityRmssdRecord 80404-7 (SDNN only) ms vital-signs
Blood oxygen (SpO₂) oxygenSaturation OxygenSaturationRecord 59408-5 % vital-signs
Respiratory rate respiratoryRate RespiratoryRateRecord 9279-1 /min vital-signs
Steps per day stepCount StepsRecord 41950-7 {steps} activity
Active energy activeEnergyBurned ActiveCaloriesBurnedRecord 41981-2 kcal activity
Sleep duration sleepAnalysis (category) SleepSessionRecord 93832-4; stages 93831-6 deep, 93830-8 light, 93829-0 REM min activity
Body weight bodyMass WeightRecord 29463-7 kg vital-signs
Blood pressure bloodPressure (correlation) BloodPressureRecord 85354-9 with 8480-6 and 8462-4 mm[Hg] vital-signs
Blood glucose (meter) bloodGlucose BloodGlucoseRecord 2339-0 mg/dL vital-signs
Body temperature bodyTemperature BodyTemperatureRecord 8310-5 Cel vital-signs

Watch the heart rate variability row: Apple Health stores SDNN, Health Connect stores RMSSD. They are different statistics and must not be written under the same code. LOINC 80404-7 is SDNN; for RMSSD, Anpheros Daily uses its own code system rather than pretending the two are the same.

Summaries, not every sample

A watch produces thousands of heart-rate samples a day. A medical record needs something a person or a doctor can read:

A daily heart-rate summary as FHIR R4 (it passes the FHIR R4 validator):

{
  "resourceType": "Observation",
  "status": "final",
  "identifier": [{"system": "https://your-app.test/fhir/sid/daily-wearable", "value": "p-123:2026-10-01:heart_rate"}],
  "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/p-123"},
  "effectivePeriod": {"start": "2026-10-01T00:00:00-04:00", "end": "2026-10-02T00:00:00-04:00"},
  "valueQuantity": {"value": 71.4, "unit": "/min", "system": "http://unitsofmeasure.org", "code": "/min"},
  "component": [
    {"code": {"text": "minimum"}, "valueQuantity": {"value": 52, "unit": "/min", "system": "http://unitsofmeasure.org", "code": "/min"}},
    {"code": {"text": "maximum"}, "valueQuantity": {"value": 138, "unit": "/min", "system": "http://unitsofmeasure.org", "code": "/min"}}
  ],
  "device": {"display": "Apple Watch (via Apple Health)"}
}

Writing daily values without duplicates

Phones sync several times a day, and the last value of a day keeps changing until midnight. Give each Observation a stable identifier — person, day, metric — and update it instead of creating a new one. With the Anpheros TypeScript SDK, on your backend:

import { Anpheros, apiKey } from '@anpheros/sdk';

const anpheros = new Anpheros({ auth: apiKey(process.env.ANPHEROS_KEY!) });

// What the phone sends: one value per metric per local day
type DailyValue = { metric: 'heart_rate' | 'steps'; day: string; value: number; min?: number; max?: number };

const CODES = {
  heart_rate: { code: '8867-4', display: 'Heart rate', unit: '/min', category: 'vital-signs' },
  steps: { code: '41950-7', display: 'Number of steps in 24 hour Measured', unit: '{steps}', category: 'activity' },
} as const;

// One Observation per person, metric and day: re-sending a day updates it instead of adding a copy.
async function upsertDaily(patientId: string, v: DailyValue, source: string) {
  const c = CODES[v.metric];
  const identifier = { system: 'https://your-app.test/fhir/sid/daily-wearable', value: `${patientId}:${v.day}:${v.metric}` };
  const resource: any = {
    resourceType: 'Observation', status: 'final',
    identifier: [identifier],
    category: [{ coding: [{ system: 'http://terminology.hl7.org/CodeSystem/observation-category', code: c.category }] }],
    code: { coding: [{ system: 'http://loinc.org', code: c.code, display: c.display }] },
    subject: { reference: `Patient/${patientId}` },
    effectivePeriod: { start: `${v.day}T00:00:00Z`, end: `${v.day}T23:59:59Z` },
    valueQuantity: { value: v.value, unit: c.unit, system: 'http://unitsofmeasure.org', code: c.unit },
  };
  if (v.min !== undefined && v.max !== undefined) {
    resource.component = [
      { code: { text: 'minimum' }, valueQuantity: { value: v.min, unit: c.unit, system: 'http://unitsofmeasure.org', code: c.unit } },
      { code: { text: 'maximum' }, valueQuantity: { value: v.max, unit: c.unit, system: 'http://unitsofmeasure.org', code: c.unit } },
    ];
  }
  const found = await anpheros.fhir.search('Observation', { identifier: `${identifier.system}|${identifier.value}` });
  const existing = found.entry?.[0]?.resource;
  return existing
    ? anpheros.fhir.update({ ...resource, id: existing.id }, { author_type: 'device' })
    : anpheros.fhir.create(resource, { author_type: 'device', source_system: source });
}

Run against a local instance of the platform with a sandbox key, two daily values followed by a later sync of the same day's steps left two Observations, the steps one at version 2 with the new value, and provenance v1 device apple-health · v2 device. The history keeps the earlier value, and every reader can see that the data came from a device, not from a clinician.

In production, use the real day boundaries in the person's time zone (the example above uses UTC for brevity), as the HealthKit and Health Connect code on the platform pages does.

Reading on the phone

Rules worth following

What Anpheros does and does not do

The Anpheros Platform does not connect to Apple Health, Health Connect or any vendor cloud by itself: your app reads the data on the phone and writes it through the API. Anpheros' own app, Anpheros Daily, does exactly that: it reads Apple Health on iPhone and Health Connect on Android, keeps daily summaries and writes them to the person's FHIR record with the codes above. Connecting devices is part of its Plus and Max plans.

Frequently asked questions

How do I get Apple Watch data into a FHIR server?

Read it on the iPhone from Apple Health with HealthKit (there is no server API), summarise it per day, and have your backend write each metric as an Observation with a LOINC code and UCUM unit, marked as device data.

Is there a cloud API for Health Connect or Apple Health?

No. Both keep data on the phone; an app installed on the phone reads it with the person's permission. Vendor clouds such as Garmin's, Fitbit's or Oura's have their own APIs, but reading the phone's health store covers most of them with one integration.

Which LOINC code should I use for steps?

41950-7, "Number of steps in 24 hour Measured", with a daily effectivePeriod. For heart rate use 8867-4, for SpO₂ 59408-5, for sleep duration 93832-4.

Should I store every heart-rate sample in FHIR?

Usually not. A daily summary with average, minimum and maximum is what a person or clinician reads; individual readings belong in the record only when they matter on their own, such as an irregular-rhythm event.

Can I use one code for HRV from Apple Health and Health Connect?

No. Apple Health reports SDNN and Health Connect reports RMSSD; they measure different things and need different codes.

Related