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.

KeyTypeMeaning
schemaVersionintegerAlways 1 today. Check it before anything else, and reject any other value.
connectivityTestbooleantrue only on the probe, which carries no samples. Do not record a payload with true here.
exportIdstring (UUID)Shared by every batch of one export run.
generatedAttimestampWhen the run’s batches were built — one instant, identical on every batch of the run. Not when any sample was recorded.
batchSequenceinteger1-based, incrementing within one run.
isFinalBatchbooleantrue on the last batch of the run and no other.
basisstringwindow or anchor: which mechanism produced the batch. See Delivery semantics.
windowStarttimestampNo sample in the batch starts before it, on either basis.
windowEndtimestampNo sample in the batch ends after it, on either basis. Exclusive on basis window.
occasionStarttimestampStart of the run this batch belongs to; identical on every batch of the run. A label, not a filter.
occasionEndtimestampEnd of that run.
samplesarrayThe batch’s samples. Empty on the probe; never absent.

The sample object

Every sample carries these eight keys, always:

KeyTypeMeaning
uuidstring (UUID)Apple Health’s own identifier for the sample. The deduplication key.
idstring64 lowercase hex characters: a SHA-256 fingerprint of the sample’s content. Not a key — it repeats by design.
typestringApple’s type identifier, e.g. HKQuantityTypeIdentifierStepCount. See the catalog.
groupstringOne of activity, heart, body, sleep, vitals, workouts. A type belongs to one group, forever.
sourcestringThe bundle identifier of whatever recorded the sample.
starttimestampWhen the reading began.
endtimestampWhen it ended; equal to start for an instantaneous reading.
kindstringquantity, category or workout. The discriminator: it decides which keys follow and what value means.

Then, per kind:

kind quantity — a number in a unit

KeyTypeMeaning
valuenumberAlways present. Decode as floating point.
unitstringAlways present; one of the units.

kind category — an enumerated state

KeyTypeMeaning
valueintegerAlways present: Apple’s raw enum value.
namestringPresent only when this build has a name for that integer.

Sleep analysis is the only category type in version 1:

valuename
0inBed
1asleepUnspecified
2awake
3asleepCore
4asleepDeep
5asleepREM

An integer this build does not know arrives with no name key at all. Key on value.

kind workout — a recorded session

KeyTypeMeaning
activityTypeintegerAlways present: Apple’s HKWorkoutActivityType raw value.
activityNamestringPresent only when this build has a name for that integer, in lowerCamelCase: running, cycling, traditionalStrengthTraining…
durationSecondsnumberAlways present.
distanceMetersnumberPresent only when the workout recorded a distance.
activeEnergyKilocaloriesnumberPresent 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

Units

unit is always one of these spellings. The list may grow; no spelling will change.

unitMeaning
counta dimensionless count
count/mincounts per minute
mmetres
kcalkilocalories
minminutes
msmilliseconds
kgkilograms
fractiona proportion in 0…1
ml/(kg*min)millilitres per kilogram per minute
degCdegrees Celsius
mmHgmillimetres 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.

grouptypekindunit
activityHKQuantityTypeIdentifierStepCountquantitycount
activityHKQuantityTypeIdentifierDistanceWalkingRunningquantitym
activityHKQuantityTypeIdentifierFlightsClimbedquantitycount
activityHKQuantityTypeIdentifierActiveEnergyBurnedquantitykcal
activityHKQuantityTypeIdentifierBasalEnergyBurnedquantitykcal
activityHKQuantityTypeIdentifierAppleExerciseTimequantitymin
activityHKQuantityTypeIdentifierAppleStandTimequantitymin
heartHKQuantityTypeIdentifierHeartRatequantitycount/min
heartHKQuantityTypeIdentifierRestingHeartRatequantitycount/min
heartHKQuantityTypeIdentifierWalkingHeartRateAveragequantitycount/min
heartHKQuantityTypeIdentifierHeartRateVariabilitySDNNquantityms
bodyHKQuantityTypeIdentifierBodyMassquantitykg
bodyHKQuantityTypeIdentifierHeightquantitym
bodyHKQuantityTypeIdentifierBodyMassIndexquantitycount
bodyHKQuantityTypeIdentifierBodyFatPercentagequantityfraction
bodyHKQuantityTypeIdentifierLeanBodyMassquantitykg
sleepHKCategoryTypeIdentifierSleepAnalysiscategory—
vitalsHKQuantityTypeIdentifierOxygenSaturationquantityfraction
vitalsHKQuantityTypeIdentifierRespiratoryRatequantitycount/min
vitalsHKQuantityTypeIdentifierVO2Maxquantityml/(kg*min)
vitalsHKQuantityTypeIdentifierBodyTemperaturequantitydegC
vitalsHKQuantityTypeIdentifierBloodPressureSystolicquantitymmHg
vitalsHKQuantityTypeIdentifierBloodPressureDiastolicquantitymmHg
workoutsHKWorkoutTypeIdentifierworkout—

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