September 3, 2026 · 4 min read

Oxygen saturation

Which devices supply it, how the averages are built, why the absolute level is rarely the point, and what to build.

What the oxygen saturation biomarkers measure

BiomarkerWhat it is
oxygen_saturationThe day’s average blood oxygen saturation across all readings
oxygen_saturation_sleepThe average across the hours the user was in bed
Fieldoxygen_saturation, oxygen_saturation_sleep
UnitPercent
ReportedDaily average
WindowMidnight to midnight, profile local time
Wearable requiredYes
APIGET /api/v1/profile/biomarker/{externalId}?categories=vitals plus one types parameter per field and a date range
WebhookBiomarkerCreatedIntegrationEvent. Entries update in place, upsert on id.

Data sources

PlatformRecordNotes
iOS, HealthKitoxygenSaturationApple Watch Series 6 and later, background readings mostly during sleep, plus on-demand readings
Android, Health ConnectOxygenSaturationRecordSamsung Health, Fitbit, Garmin Connect, Pixel Watch and other writers, mostly overnight
Direct integrationsProvider-dependentOura supplies overnight SpO2 where the user has enabled it
Phone onlyNone

How Sahha calculates oxygen saturation

  1. Pick one source for the day within the oxygen category, which also covers respiratory rate and VO2 max. One source wins per day; values are never blended.
  2. Daily is the average of that source’s readings with an end time inside the local day.
  3. Sleep averages each hour in which the user was in bed for at least 45 minutes, according to sleep records, then averages those hours. Hours before midnight belong to the previous calendar date.

Absence means no readings from the winning source that day.

Interpreting the value

The level is almost always in the high nineties. For healthy users at low altitude the daily average sits in a narrow band, and single readings below it are usually motion or a loose strap rather than physiology.

Change against the user’s own baseline is the signal. A sustained drop in the sleep average over several nights, with no change in altitude, is worth surfacing gently. It is also the pattern most manufacturers use to prompt users about sleep-disordered breathing, and it is not a diagnosis.

Altitude moves it immediately. A user who travels to a mountain town will show a lower average for the whole stay.

Oxygen saturation is not a score factor and has no trend or comparison insight.

Use cases

A sleep-time baseline with a soft alert. Keep a 14-night rolling average of the sleep value and surface a sustained drop as information to raise with a clinician, in language that avoids diagnosis.

GET /api/v1/profile/biomarker/{externalId}?categories=vitals&types=oxygen_saturation_sleep&startDateTime=2026-08-19&endDateTime=2026-09-02

Travel context. Pair a drop with a change in the profile’s time zone offset to explain altitude and travel rather than alarm the user.

Limitations

  • Spot readings, averaged. A day’s value can rest on a handful of readings, and Apple Watch takes them opportunistically.
  • Accuracy varies with fit and skin. Loose straps, tattoos, cold hands, and movement all lower readings, and pulse oximetry reads less accurately on darker skin [1].
  • Not for medical use. Do not build alerts that imply diagnosis.
  • Absence is not zero.

References

  1. Sjoding MW, Dickson RP, Iwashyna TJ, Gay SE, Valley TS. Racial bias in pulse oximetry measurement. New England Journal of Medicine. 2020;383(25):2477-2478. https://doi.org/10.1056/NEJMc2029240

Related