---
title: "Unified health-data schema: one shape for four platforms"
canonical: "https://aifitnessapi.com/schema"
markdown: "https://aifitnessapi.com/schema.md"
type: "dataset-index"
last_reviewed: "2026-11-05"
description: "JSON Schema and TypeScript types for heart rate, steps, sleep, workouts, weight, HRV and SpO2, each field mapped to HealthKit, Health Connect and Google."
publisher: "AIFitnessAPI — funded by KinesteX; first-party pages disclose it"
cite_as: "\"Unified health-data schema: one shape for four platforms\", AIFitnessAPI, https://aifitnessapi.com/schema"
---

# Unified health-data schema: one shape for four platforms

> A vendor-neutral JSON Schema (draft 2020-12) and TypeScript types for 9 common health records — heart rate, steps, sleep sessions with stages, workouts, weight, body fat, height, heart rate variability and SpO2 — with each field mapped, or marked not mapped, for Apple HealthKit, Android Health Connect, the Google Health API and Wear OS Health Services. Each mapping quotes the platform's own documentation, each unit conversion names the sentence that states it (HealthKit's percent is 0.0 to 1.0, Google stores weight in grams), and each gap says why: Wear OS covers 3 of the 9, and HealthKit's SDNN is never relabeled as RMSSD. Version 1.0.0, CC BY 4.0.

- Canonical: https://aifitnessapi.com/schema
- Last reviewed: 2026-11-05
- Publisher: AIFitnessAPI (https://aifitnessapi.com)
- Cite as: "Unified health-data schema: one shape for four platforms", AIFitnessAPI, https://aifitnessapi.com/schema

---

[JSON Schema (draft 2020-12)](https://aifitnessapi.com/schema/v1/unified-health-data.schema.json) · [TypeScript types (.d.ts)](https://aifitnessapi.com/schema/v1/unified-health-data.d.ts) · [Mappings with evidence (JSON)](https://aifitnessapi.com/schema/v1/mappings.json) · [Try the converter](https://aifitnessapi.com/tools/schema-converter)

## The 9 records

- Heart rate sample heartRate
- Step count over an interval steps
- Sleep session with stages sleepSession
- Workout session workout
- Body mass (weight) bodyMass
- Body fat percentage bodyFat
- Height height
- Heart rate variability heartRateVariability
- Blood oxygen saturation (SpO2) oxygenSaturation

## How to read the tables

- A mapped cell names the platform field, its declared type, any conversion, and a Source with the platform’s own sentence. A Not mapped cell says why.

## Heart rate sample type: "heartRate"

One heart-rate reading at one instant, in beats per minute.

| Unified field | HealthKit | Health Connect | Google Health API | Wear OS Health Services |
|---|---|---|---|---|
| (type) | HKQuantityTypeIdentifier.heartRate | HeartRateRecord | heart-rate | DataType.HEART_RATE_BPM |
| time | startDate Date a sample at one moment has startDate equal to endDate | samples[].time Instant | heartRate.sampleTime.physicalTime string/google-datetime | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| utcOffset | Not mapped HealthKit samples carry no UTC offset field. An export.xml date includes the offset (the converter reads it); in the API, the optional HKMetadataKeyTimeZone metadata is a zone name, not an offset. | startZoneOffset ZoneOffset? the record's startZoneOffset, applied to every sample (a Sample has no offset of its own) | heartRate.sampleTime.utcOffset string/google-duration google-duration seconds ("3600s") → ±HH:MM | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| beatsPerMinute | quantity HKQuantity (count/time unit family) read with doubleValue(for:) in count/min | samples[].beatsPerMinute @IntRange (from = 1, to = 300) Long | heartRate.beatsPerMinute string/int64 int64 arrives as a JSON string: parse to a number | SampleDataPoint.value Double |

## Step count over an interval type: "steps"

The number of steps taken between two instants.

| Unified field | HealthKit | Health Connect | Google Health API | Wear OS Health Services |
|---|---|---|---|---|
| (type) | HKQuantityTypeIdentifier.stepCount | StepsRecord | steps | DataType.STEPS |
| startTime | startDate Date | startTime Instant | steps.interval.startTime string/google-datetime | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| endTime | endDate Date | endTime Instant | steps.interval.endTime string/google-datetime | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| startUtcOffset | Not mapped HealthKit samples carry no UTC offset field. An export.xml date includes the offset (the converter reads it); in the API, the optional HKMetadataKeyTimeZone metadata is a zone name, not an offset. | startZoneOffset ZoneOffset? ZoneOffset → ±HH:MM; null stays null | steps.interval.startUtcOffset string/google-duration google-duration seconds ("3600s") → ±HH:MM | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| endUtcOffset | Not mapped HealthKit samples carry no UTC offset field. An export.xml date includes the offset (the converter reads it); in the API, the optional HKMetadataKeyTimeZone metadata is a zone name, not an offset. | endZoneOffset ZoneOffset? ZoneOffset → ±HH:MM; null stays null | steps.interval.endUtcOffset string/google-duration google-duration seconds ("3600s") → ±HH:MM | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| count | quantity HKQuantity (count unit family) read with doubleValue(for:) in count | count Long | steps.count string/int64 int64 arrives as a JSON string: parse to an integer | IntervalDataPoint.value Long |

- Google prints different lower bounds: Health Connect's count is “Valid range: 1-1000000.”, the Google Health API's “Required. Number of steps in the recorded interval. Must be in the range `[0, 1000000]`.” A zero-step interval converts to the unified shape, but cannot be written back to Health Connect.

## Sleep session with stages type: "sleepSession"

One sleep period and the stages inside it, each stage an interval with one value from the unified stage vocabulary.

| Unified field | HealthKit | Health Connect | Google Health API | Wear OS Health Services |
|---|---|---|---|---|
| (type) | HKCategoryTypeIdentifier.sleepAnalysis | SleepSessionRecord | sleep | No type |
| startTime | startDate Date | startTime Instant | sleep.interval.startTime string/google-datetime | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| endTime | endDate Date | endTime Instant | sleep.interval.endTime string/google-datetime | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| startUtcOffset | Not mapped HealthKit samples carry no UTC offset field. An export.xml date includes the offset (the converter reads it); in the API, the optional HKMetadataKeyTimeZone metadata is a zone name, not an offset. | startZoneOffset ZoneOffset? ZoneOffset → ±HH:MM; null stays null | sleep.interval.startUtcOffset string/google-duration google-duration seconds ("3600s") → ±HH:MM | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| endUtcOffset | Not mapped HealthKit samples carry no UTC offset field. An export.xml date includes the offset (the converter reads it); in the API, the optional HKMetadataKeyTimeZone metadata is a zone name, not an offset. | endZoneOffset ZoneOffset? ZoneOffset → ±HH:MM; null stays null | sleep.interval.endUtcOffset string/google-duration google-duration seconds ("3600s") → ±HH:MM | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| stages | value (HKCategoryValueSleepAnalysis) Int each sample's startDate, endDate and value become one stage | stages[].startTime, stages[].endTime, stages[].stage List<SleepSessionRecord.Stage>; stage: Int stage Int → the unified vocabulary (table below) | sleep.stages[] (startTime, endTime, type) array<SleepStage> type string → the unified vocabulary (table below); outOfBedSegments are not read | Not mapped No sleep type in Health Services. |

- Stage vocabularies differ: Health Connect separates awake from awake-in-bed and out-of-bed, the Google Health API adds restless, and Apple's core stage is “light or intermediate”. The unified vocabulary keeps every distinction any source makes, so a conversion never merges two source values — except Apple's core into light, stated in the table.

### Sleep stages: 10 unified values

| Value | Unified | Platform says |
|---|---|---|
| HKCategoryValueSleepAnalysisInBed | inBed | The user is in bed. |
| HKCategoryValueSleepAnalysisAwake | awake | The user is awake. |
| HKCategoryValueSleepAnalysisAsleepCore | light | The user is in light or intermediate sleep. Apple's core stage is “light or intermediate”; the unified vocabulary has no intermediate stage, so it lands on light. |
| HKCategoryValueSleepAnalysisAsleepDeep | deep | The user is in deep sleep. |
| HKCategoryValueSleepAnalysisAsleepREM | rem | The user is in REM sleep. |
| HKCategoryValueSleepAnalysisAsleepUnspecified | asleep | The user is asleep, but the specific stage isn’t known. |
| HKCategoryValueSleepAnalysisAsleep | asleep | The user is sleeping. Deprecated in iOS 16.0; Apple's availability data names HKCategoryValueSleepAnalysis.asleepUnspecified as its new name. |

| Value | Unified | Platform says |
|---|---|---|
| STAGE_TYPE_UNKNOWN (0) | unknown | Use this type if the stage of sleep is unknown. |
| STAGE_TYPE_AWAKE (1) | awake | The user is awake and either known to be in bed, or it is unknown whether they are in bed or not. |
| STAGE_TYPE_SLEEPING (2) | asleep | The user is asleep but the particular stage of sleep (light, deep or REM) is unknown. |
| STAGE_TYPE_OUT_OF_BED (3) | outOfBed | The user is out of bed and assumed to be awake. |
| STAGE_TYPE_LIGHT (4) | light | The user is in a light sleep stage. |
| STAGE_TYPE_DEEP (5) | deep | The user is in a deep sleep stage. |
| STAGE_TYPE_REM (6) | rem | The user is in a REM sleep stage. |
| STAGE_TYPE_AWAKE_IN_BED (7) | awakeInBed | The user is awake and in bed. |

| Value | Unified | Platform says |
|---|---|---|
| SLEEP_STAGE_TYPE_UNSPECIFIED | unknown | The default unset value. |
| AWAKE | awake | Sleep stage AWAKE. |
| LIGHT | light | Sleep stage LIGHT. |
| DEEP | deep | Sleep stage DEEP. |
| REM | rem | Sleep stage REM. |
| ASLEEP | asleep | Sleep stage ASLEEP. |
| RESTLESS | restless | Sleep stage RESTLESS. |

## Workout session type: "workout"

One exercise session: when it ran, what it was, and the totals where the source states them.

| Unified field | HealthKit | Health Connect | Google Health API | Wear OS Health Services |
|---|---|---|---|---|
| (type) | HKWorkout | ExerciseSessionRecord | exercise | ExerciseType |
| startTime | startDate Date | startTime Instant | exercise.interval.startTime string/google-datetime | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| endTime | endDate Date | endTime Instant | exercise.interval.endTime string/google-datetime | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| startUtcOffset | Not mapped HealthKit samples carry no UTC offset field. An export.xml date includes the offset (the converter reads it); in the API, the optional HKMetadataKeyTimeZone metadata is a zone name, not an offset. | startZoneOffset ZoneOffset? ZoneOffset → ±HH:MM; null stays null | exercise.interval.startUtcOffset string/google-duration google-duration seconds ("3600s") → ±HH:MM | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| endUtcOffset | Not mapped HealthKit samples carry no UTC offset field. An export.xml date includes the offset (the converter reads it); in the API, the optional HKMetadataKeyTimeZone metadata is a zone name, not an offset. | endZoneOffset ZoneOffset? ZoneOffset → ±HH:MM; null stays null | exercise.interval.endUtcOffset string/google-duration google-duration seconds ("3600s") → ±HH:MM | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| activityType | workoutActivityType HKWorkoutActivityType Objective-C constant → crosswalk key | exerciseType Int Int value → EXERCISE_TYPE_ constant → crosswalk key | exercise.exerciseType string enum string → crosswalk key | name String constant name → crosswalk key |
| nativeActivityType | workoutActivityType HKWorkoutActivityType | exerciseType Int | exercise.exerciseType string | name String |
| energyKcal | Not mapped HKWorkout.totalEnergyBurned is deprecated as of iOS 18.0 (Apple: “Use statisticsForType: passing the HKQuantityType for HKQuantityTypeIdentifierActiveEnergyBurned”); per-type statistics are not in this site's datasets, so the converter does not read energy from HealthKit. | Not mapped ExerciseSessionRecord has no energy property: Health Connect stores it as separate calories records over the same interval. | exercise.metricsSummary.caloriesKcal number/double | Not mapped Exercise totals in Health Services arrive as DataType values during the exercise (CALORIES_TOTAL and similar), not on a session object; not mapped here. |
| distanceMeters | Not mapped HKWorkout.totalDistance is deprecated as of iOS 27.3 (Apple: “Use statisticsForType: passing the HKQuantityType for the desired distance type”); not read. | Not mapped ExerciseSessionRecord has no distance property: Health Connect stores it as separate DistanceRecord records. | exercise.metricsSummary.distanceMillimeters number/double ÷ 1,000 (the field name states millimeters) | Not mapped As for energy: DISTANCE_TOTAL-style values arrive during the exercise; not mapped here. |

- activityType uses the workout crosswalk's key (87 keys are shared by two or more platforms). A key shared by name is not a claim that two platforms mean the same activity; /workout-types shows each platform's own description.

## Body mass (weight) type: "bodyMass"

The user's weight at one instant, in kilograms.

| Unified field | HealthKit | Health Connect | Google Health API | Wear OS Health Services |
|---|---|---|---|---|
| (type) | HKQuantityTypeIdentifier.bodyMass | WeightRecord | weight | No type |
| time | startDate Date a sample at one moment has startDate equal to endDate | time Instant | weight.sampleTime.physicalTime string/google-datetime | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| utcOffset | Not mapped HealthKit samples carry no UTC offset field. An export.xml date includes the offset (the converter reads it); in the API, the optional HKMetadataKeyTimeZone metadata is a zone name, not an offset. | zoneOffset ZoneOffset? ZoneOffset → ±HH:MM; null stays null | weight.sampleTime.utcOffset string/google-duration google-duration seconds ("3600s") → ±HH:MM | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| kilograms | quantity HKQuantity (mass unit family) read with doubleValue(for:) in kg | weight Mass Mass → kilograms (inKilograms) | weight.weightGrams number/double ÷ 1,000 | Not mapped No body-mass type in Health Services. |

- Pounds: Google's migration guide divides grams by 453.59237 (“Retrieve the user's weight in grams from the `weightGrams` field in the Weight data type. Convert this to kilograms (divide by 1,000) or pounds (divide by 453.59237).”), and elsewhere says “Retrieve the user's weight in grams from the `weightGrams` field in the Weight data type and convert to kilograms (divide by 1,000). To convert pounds to kilograms, divide by 2.2.” The converter uses 453.59237 g per pound.

## Body fat percentage type: "bodyFat"

Body fat as a percentage from 0 to 100.

| Unified field | HealthKit | Health Connect | Google Health API | Wear OS Health Services |
|---|---|---|---|---|
| (type) | HKQuantityTypeIdentifier.bodyFatPercentage | BodyFatRecord | body-fat | No type |
| time | startDate Date a sample at one moment has startDate equal to endDate | time Instant | bodyFat.sampleTime.physicalTime string/google-datetime | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| utcOffset | Not mapped HealthKit samples carry no UTC offset field. An export.xml date includes the offset (the converter reads it); in the API, the optional HKMetadataKeyTimeZone metadata is a zone name, not an offset. | zoneOffset ZoneOffset? ZoneOffset → ±HH:MM; null stays null | bodyFat.sampleTime.utcOffset string/google-duration google-duration seconds ("3600s") → ±HH:MM | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| percent | quantity HKQuantity (percent unit family) × 100 (Apple: “Percent measures a value between 0.0 and 1.0.”) | percentage Percentage | bodyFat.percentage number/double | Not mapped No body-fat type in Health Services. |

- HealthKit's percent unit is a fraction (0.0–1.0); Health Connect and the Google Health API state 0–100. Mixing them is the classic 0.23-percent body-fat bug.

## Height type: "height"

The user's height at one instant, in meters.

| Unified field | HealthKit | Health Connect | Google Health API | Wear OS Health Services |
|---|---|---|---|---|
| (type) | HKQuantityTypeIdentifier.height | HeightRecord | height | No type |
| time | startDate Date a sample at one moment has startDate equal to endDate | time Instant | height.sampleTime.physicalTime string/google-datetime | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| utcOffset | Not mapped HealthKit samples carry no UTC offset field. An export.xml date includes the offset (the converter reads it); in the API, the optional HKMetadataKeyTimeZone metadata is a zone name, not an offset. | zoneOffset ZoneOffset? ZoneOffset → ±HH:MM; null stays null | height.sampleTime.utcOffset string/google-duration google-duration seconds ("3600s") → ±HH:MM | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| meters | quantity HKQuantity (length unit family) read with doubleValue(for:) in m | height Length Length → meters (inMeters) | height.heightMillimeters string/int64 ÷ 1,000 (int64 arrives as a JSON string) | Not mapped No height type in Health Services. |

## Heart rate variability type: "heartRateVariability"

One HRV reading in milliseconds, tagged with the statistic it is. SDNN and RMSSD are different measures and never convert into each other.

| Unified field | HealthKit | Health Connect | Google Health API | Wear OS Health Services |
|---|---|---|---|---|
| (type) | HKQuantityTypeIdentifier.heartRateVariabilitySDNN | HeartRateVariabilityRmssdRecord | heart-rate-variability | No type |
| time | startDate Date a sample at one moment has startDate equal to endDate | time Instant | heartRateVariability.sampleTime.physicalTime string/google-datetime | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| utcOffset | Not mapped HealthKit samples carry no UTC offset field. An export.xml date includes the offset (the converter reads it); in the API, the optional HKMetadataKeyTimeZone metadata is a zone name, not an offset. | zoneOffset ZoneOffset? ZoneOffset → ±HH:MM; null stays null | heartRateVariability.sampleTime.utcOffset string/google-duration google-duration seconds ("3600s") → ±HH:MM | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| metric | (the type) sdnn always sdnn | (the record class) rmssd always rmssd | heartRateVariability.rootMeanSquareOfSuccessiveDifferencesMilliseconds rmssd rmssd when this field is present | Not mapped No HRV type in Health Services. |
| milliseconds | quantity HKQuantity (time unit family) read with doubleValue(for:) in ms | heartRateVariabilityMillis Double | heartRateVariability.rootMeanSquareOfSuccessiveDifferencesMilliseconds number/double | Not mapped No HRV type in Health Services. |

- HealthKit publishes SDNN; Health Connect and the Google Health API publish RMSSD. A pipeline that stores both in one column without the metric tag mixes two different numbers.
- heartRateVariability.standardDeviationMilliseconds (Google Health API): Not mapped to sdnn. Google describes it as “Optional. The standard deviation of the heart rate variability measurement.” — the deviation of the measurement, not of the heartbeat intervals — so calling it SDNN would be a guess.

## Blood oxygen saturation (SpO2) type: "oxygenSaturation"

One SpO2 reading as a percentage from 0 to 100.

| Unified field | HealthKit | Health Connect | Google Health API | Wear OS Health Services |
|---|---|---|---|---|
| (type) | HKQuantityTypeIdentifier.oxygenSaturation | OxygenSaturationRecord | oxygen-saturation | No type |
| time | startDate Date a sample at one moment has startDate equal to endDate | time Instant | oxygenSaturation.sampleTime.physicalTime string/google-datetime | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| utcOffset | Not mapped HealthKit samples carry no UTC offset field. An export.xml date includes the offset (the converter reads it); in the API, the optional HKMetadataKeyTimeZone metadata is a zone name, not an offset. | zoneOffset ZoneOffset? ZoneOffset → ±HH:MM; null stays null | oxygenSaturation.sampleTime.utcOffset string/google-duration google-duration seconds ("3600s") → ±HH:MM | Not mapped Health Services DataPoint time fields are not in this site's Health Services dataset, and Health Services has no JSON form to convert. |
| percent | quantity HKQuantity (percent unit family) × 100 (Apple: “Percent measures a value between 0.0 and 1.0.”) | percentage Percentage | oxygenSaturation.percentage number/double | Not mapped No SpO2 type in Health Services. |

## Unit conversions, each with its source

A factor appears only where a source states it, or where the platform’s field name states its unit and the factor is an SI prefix. Every other mapped value is already in the unified unit.

| Record · platform | From | To | Factor and source |
|---|---|---|---|
| Workout session · Google Health API | exercise.metricsSummary.distanceMillimeters | distanceMeters | × 0.001 — ÷ 1,000 (the field name states millimeters) |
| Body mass (weight) · Google Health API | weight.weightGrams | kilograms | × 0.001 — ÷ 1,000 |
| Body fat percentage · HealthKit | quantity | percent | × 100 — × 100 (Apple: “Percent measures a value between 0.0 and 1.0.”) |
| Height · Google Health API | height.heightMillimeters | meters | × 0.001 — ÷ 1,000 (int64 arrives as a JSON string) |
| Blood oxygen saturation (SpO2) · HealthKit | quantity | percent | × 100 — × 100 (Apple: “Percent measures a value between 0.0 and 1.0.”) |

## What is not mapped, and why

- Sleep session with stages on Wear OS Health Services. None of the 65 Health Services DataType constants (Jetpack reference, read 2026-10-04) has a name or description mentioning “sleep”: no sleep type to map.
- Body mass (weight) on Wear OS Health Services. None of the 65 Health Services DataType constants (Jetpack reference, read 2026-10-04) has a name or description mentioning “weight” or “body mass”: no body-mass type to map.
- Body fat percentage on Wear OS Health Services. None of the 65 Health Services DataType constants (Jetpack reference, read 2026-10-04) has a name or description mentioning “fat”: no body-fat type to map.
- Height on Wear OS Health Services. None of the 65 Health Services DataType constants (Jetpack reference, read 2026-10-04) has a name or description mentioning “height”: no height type to map.
- Heart rate variability on Wear OS Health Services. None of the 65 Health Services DataType constants (Jetpack reference, read 2026-10-04) has a name or description mentioning “variability”: no heart-rate-variability type to map.
- Blood oxygen saturation (SpO2) on Wear OS Health Services. None of the 65 Health Services DataType constants (Jetpack reference, read 2026-10-04) has a name or description mentioning “spo2” or “saturation”: no oxygen-saturation type to map.

## How it is generated

The record shapes are this site’s design. Every platform name in a mapping is looked up in a generated dataset, and the generator stops when one is missing, so a renamed field fails the build instead of shipping a stale mapping.

## Frequently asked questions

### Is there a common schema for HealthKit, Health Connect and Google Health API data?

Not from the platforms: each publishes its own types. This site publishes one, version 1.0.0, as a JSON Schema and TypeScript types at /schema/v1/unified-health-data.schema.json and /schema/v1/unified-health-data.d.ts, generated from its datasets of the platforms' own documentation so every field mapping carries the sentence it rests on.

[Permalink](https://aifitnessapi.com/schema#faq-1)

### Why does the unified HRV record carry a metric field instead of one number?

Because the platforms publish different statistics. HealthKit's type is heartRateVariabilitySDNN, the standard deviation of heartbeat intervals; Health Connect's record and the Google Health API's field are RMSSD. They are different measures of the same heartbeat series, and no formula converts one into the other, so the record says which one it is.

[Permalink](https://aifitnessapi.com/schema#faq-2)

### How do I convert HealthKit oxygen saturation to the same scale as Health Connect?

Multiply by 100. Apple's HKUnit.percent() documentation says percent measures a value between 0.0 and 1.0, while Health Connect's OxygenSaturationRecord.percentage and the Google Health API's percentage field state a 0 to 100 range. The unified record holds 0 to 100.

[Permalink](https://aifitnessapi.com/schema#faq-3)

### Can I use the unified schema in my own product?

Yes, under CC BY 4.0, the same license as this site's datasets: attribute aifitnessapi.com. The vendor sentences quoted as evidence in mappings.json remain their publishers' text. The schema is versioned in its URL, so v1 stays where it is when a later version changes a shape.

[Permalink](https://aifitnessapi.com/schema#faq-4)
