JSON payload
This page describes a destination set to JSON. A destination set to CSV sends the same
samples as a CSV document. Everything here is version 1: keys keep their
names, types and meanings for as long as the app emits schemaVersion 1. The only
change allowed inside version 1 is that new keys may appear — so ignore keys you do not
recognise.
The envelope
Every request body — an export batch or the connectivity probe — is one object with exactly these twelve keys, always all present.
| Key | Type | Meaning |
|---|---|---|
schemaVersion | integer | Always 1 today. Check it before anything else, and reject any other value. |
connectivityTest | boolean | true only on the probe, which carries no samples. Do not record a payload with true here. |
exportId | string (UUID) | Shared by every batch of one export run. |
generatedAt | timestamp | When the run’s batches were built — one instant, identical on every batch of the run. Not when any sample was recorded. |
batchSequence | integer | 1-based, incrementing within one run. |
isFinalBatch | boolean | true on the last batch of the run and no other. |
basis | string | window or anchor: which mechanism produced the batch. See Delivery semantics. |
windowStart | timestamp | No sample in the batch starts before it, on either basis. |
windowEnd | timestamp | No sample in the batch ends after it, on either basis. Exclusive on basis window. |
occasionStart | timestamp | Start of the run this batch belongs to; identical on every batch of the run. A label, not a filter. |
occasionEnd | timestamp | End of that run. |
samples | array | The batch’s samples. Empty on the probe; never absent. |
The sample object
Every sample carries these eight keys, always:
| Key | Type | Meaning |
|---|---|---|
uuid | string (UUID) | Apple Health’s own identifier for the sample. The deduplication key. |
id | string | 64 lowercase hex characters: a SHA-256 fingerprint of the sample’s content. Not a key — it repeats by design. |
type | string | Apple’s type identifier, e.g. HKQuantityTypeIdentifierStepCount. See the catalog. |
group | string | One of activity, heart, body, sleep, vitals, workouts. A type belongs to one group, forever. |
source | string | The bundle identifier of whatever recorded the sample. |
start | timestamp | When the reading began. |
end | timestamp | When it ended; equal to start for an instantaneous reading. |
kind | string | quantity, category or workout. The discriminator: it decides which keys follow and what value means. |
Then, per kind:
kind quantity — a number in a unit
| Key | Type | Meaning |
|---|---|---|
value | number | Always present. Decode as floating point. |
unit | string | Always present; one of the units. |
kind category — an enumerated state
| Key | Type | Meaning |
|---|---|---|
value | integer | Always present: Apple’s raw enum value. |
name | string | Present only when this build has a name for that integer. |
Sleep analysis is the only category type in version 1:
value | name |
|---|---|
| 0 | inBed |
| 1 | asleepUnspecified |
| 2 | awake |
| 3 | asleepCore |
| 4 | asleepDeep |
| 5 | asleepREM |
An integer this build does not know arrives with no name key at all. Key on value.
kind workout — a recorded session
| Key | Type | Meaning |
|---|---|---|
activityType | integer | Always present: Apple’s HKWorkoutActivityType raw value. |
activityName | string | Present only when this build has a name for that integer, in lowerCamelCase: running, cycling, traditionalStrengthTraining… |
durationSeconds | number | Always present. |
distanceMeters | number | Present only when the workout recorded a distance. |
activeEnergyKilocalories | number | Present only when the workout recorded energy. |
A workout has no value and no unit; the units are in the key names. Activity types
14, 15 and 30 are ones Apple deprecated and arrive unnamed.
Absent, never null
An optional with no value is an absent key. The payload contains no null anywhere.
A workout with no distance has no distanceMeters key; it never has
"distanceMeters": null, and it never has 0.
Numbers and identifiers
- A whole number has no decimal point.
42steps and a1800-second workout are bare integers on the wire, though they are measurements. Sovalue42on a quantity andvalue4on a category look alike. Switch onkind: decode a quantity’svalueas floating point and a category’s as an integer. - UUIDs are uppercase, in canonical 8-4-4-4-12 form, in both
exportIdanduuid. If your storage changes case anywhere, change it everywhere, or deduplication silently stops working.
Units
unit is always one of these spellings. The list may grow; no spelling will change.
unit | Meaning |
|---|---|
count | a dimensionless count |
count/min | counts per minute |
m | metres |
kcal | kilocalories |
min | minutes |
ms | milliseconds |
kg | kilograms |
fraction | a proportion in 0…1 |
ml/(kg*min) | millilitres per kilogram per minute |
degC | degrees Celsius |
mmHg | millimetres of mercury |
Heart rate and respiratory rate are count/min, not per second. Blood oxygen and body fat
are a fraction: 0.98, not 98 — multiply by 100 yourself for a percentage. Slashes and
parentheses arrive unescaped.
Dates
Every timestamp is ISO 8601 in exactly this shape: full date, T, full time, always
three fractional digits, and a numeric UTC offset with a colon — or Z for UTC.
2026-08-21T10:25:30.000+02:00 and 2026-08-21T08:25:30.000Z are the same instant.
The offset is the exporting phone’s at the moment of encoding, not the sample’s, so a
user who travels resends a historic sample under a different offset. Store instants in
UTC. Two distinct samples can carry identical start and end: milliseconds are the
limit of the format.
The catalog
Six groups, each enabled independently by the user. Expect any subset: an export carrying
only sleep is normal.
group | type | kind | unit |
|---|---|---|---|
activity | HKQuantityTypeIdentifierStepCount | quantity | count |
activity | HKQuantityTypeIdentifierDistanceWalkingRunning | quantity | m |
activity | HKQuantityTypeIdentifierFlightsClimbed | quantity | count |
activity | HKQuantityTypeIdentifierActiveEnergyBurned | quantity | kcal |
activity | HKQuantityTypeIdentifierBasalEnergyBurned | quantity | kcal |
activity | HKQuantityTypeIdentifierAppleExerciseTime | quantity | min |
activity | HKQuantityTypeIdentifierAppleStandTime | quantity | min |
heart | HKQuantityTypeIdentifierHeartRate | quantity | count/min |
heart | HKQuantityTypeIdentifierRestingHeartRate | quantity | count/min |
heart | HKQuantityTypeIdentifierWalkingHeartRateAverage | quantity | count/min |
heart | HKQuantityTypeIdentifierHeartRateVariabilitySDNN | quantity | ms |
body | HKQuantityTypeIdentifierBodyMass | quantity | kg |
body | HKQuantityTypeIdentifierHeight | quantity | m |
body | HKQuantityTypeIdentifierBodyMassIndex | quantity | count |
body | HKQuantityTypeIdentifierBodyFatPercentage | quantity | fraction |
body | HKQuantityTypeIdentifierLeanBodyMass | quantity | kg |
sleep | HKCategoryTypeIdentifierSleepAnalysis | category | — |
vitals | HKQuantityTypeIdentifierOxygenSaturation | quantity | fraction |
vitals | HKQuantityTypeIdentifierRespiratoryRate | quantity | count/min |
vitals | HKQuantityTypeIdentifierVO2Max | quantity | ml/(kg*min) |
vitals | HKQuantityTypeIdentifierBodyTemperature | quantity | degC |
vitals | HKQuantityTypeIdentifierBloodPressureSystolic | quantity | mmHg |
vitals | HKQuantityTypeIdentifierBloodPressureDiastolic | quantity | mmHg |
workouts | HKWorkoutTypeIdentifier | workout | — |
Workout routes (GPS traces) are not sent and will not be added to version 1. Heart rate
recorded during a workout arrives as ordinary heart samples.
The connectivity probe
Test connection sends the envelope with no samples and connectivityTest true —
always exactly these bytes. The nil exportId and the epoch generatedAt are fixed
constants. Answer 2xx and record nothing.
examples/fixtures/probe.json · tested by npm run test:examples{"basis":"window","batchSequence":1,"connectivityTest":true,"exportId":"00000000-0000-0000-0000-000000000000","generatedAt":"1970-01-01T00:00:00.000Z","isFinalBatch":true,"occasionEnd":"1970-01-01T00:00:00.000Z","occasionStart":"1970-01-01T00:00:00.000Z","samples":[],"schemaVersion":1,"windowEnd":"1970-01-01T00:00:00.000Z","windowStart":"1970-01-01T00:00:00.000Z"}Worked example
One batch carrying all three kinds: 42 steps, 45 minutes of deep sleep, and a 30-minute 5 km run. These are the contract’s own example bytes; on the wire the object is unindented, with no trailing newline.
examples/fixtures/contract-example.json, indented for reading · tested by npm run test:examples{
"basis": "window",
"batchSequence": 1,
"connectivityTest": false,
"exportId": "6C7F1E22-1B3D-4A5E-9F00-2C1D4E5A6B70",
"generatedAt": "2026-08-21T12:00:00.000+02:00",
"isFinalBatch": true,
"occasionEnd": "2026-08-21T12:00:00.000+02:00",
"occasionStart": "2026-08-20T12:00:00.000+02:00",
"samples": [
{
"end": "2026-08-21T10:25:30.000+02:00",
"group": "activity",
"id": "3cae82ce29b729ba872bdd31f1db79f0a31013c3546710bd4fe1adb8192ec011",
"kind": "quantity",
"source": "com.apple.Health",
"start": "2026-08-21T10:25:30.000+02:00",
"type": "HKQuantityTypeIdentifierStepCount",
"unit": "count",
"uuid": "A1B2C3D4-0000-4000-8000-000000000001",
"value": 42
},
{
"end": "2026-08-21T00:55:00.000+02:00",
"group": "sleep",
"id": "8b8f956b04e59162cf497570f22a2f9f96f82b5ffddc090b0c060ba910f14e21",
"kind": "category",
"name": "asleepDeep",
"source": "com.apple.Health",
"start": "2026-08-21T00:10:00.000+02:00",
"type": "HKCategoryTypeIdentifierSleepAnalysis",
"uuid": "A1B2C3D4-0000-4000-8000-000000000002",
"value": 4
},
{
"activeEnergyKilocalories": 320,
"activityName": "running",
"activityType": 37,
"distanceMeters": 5000,
"durationSeconds": 1800,
"end": "2026-08-21T07:30:00.000+02:00",
"group": "workouts",
"id": "689c84ea50249537ec039e00b603c06985d1c24a864d02cba7261764e669e477",
"kind": "workout",
"source": "com.apple.Health",
"start": "2026-08-21T07:00:00.000+02:00",
"type": "HKWorkoutTypeIdentifier",
"uuid": "A1B2C3D4-0000-4000-8000-000000000003"
}
],
"schemaVersion": 1,
"windowEnd": "2026-08-21T12:00:00.000+02:00",
"windowStart": "2026-08-20T12:00:00.000+02:00"
}