All documentation

The data

Delivery schema

Fields, record kinds and payload examples.

The receiver contract for automatic destinations. Manual exports package the same record shape differently; see Export formats.

Conventions#

  • Timestamps are ISO 8601 in UTC with fractional seconds and a Z suffix: 2026-08-22T21:10:46.500Z. The one exception is the opt-in Health Auto Export compatibility mode, which uses local time in that app's format.
  • Units are untranslated HealthKit strings: count/min, not bpm.
  • Identifiers are lowercase HealthKit UUID strings, stable across delivery.
  • Nothing is null. Unknown fields are absent. Missing device means HealthKit reported none.

The record#

NDJSON and JSON deliver this shape unchanged. CSV, Metrics JSON and line protocol are projections of it.

Fields present on every record
FieldTypeNotes
schemaVersionIntegerCurrently 1.
catalogVersionIntegerVersion of Hozz’s Health type catalogue.
idStringLowercase UUID. Stable across redeliveries.
typeStringHealthKit type identifier.
kindStringSee below.
startDateStringISO 8601 UTC.
endDateStringISO 8601 UTC. Equals startDate for instantaneous samples.
sourceObjectWhere the sample came from.
deviceObjectOptional. The hardware, when HealthKit named one.
metadataObjectOptional. HealthKit metadata, type-tagged.
A complete quantity record
{
  "schemaVersion": 1,
  "catalogVersion": 6,
  "id": "2f1a9c04-8e2b-4a1d-9f30-11c4e6a7b920",
  "type": "HKQuantityTypeIdentifierHeartRate",
  "kind": "quantity",
  "startDate": "2026-08-22T21:10:46.500Z",
  "endDate": "2026-08-22T21:10:46.500Z",
  "quantity": { "unit": "count/min", "value": 62.5, "description": "62.5 count/min" },
  "source": {
    "name": "Brandon's Apple Watch",
    "bundleIdentifier": "com.apple.health.ABC123",
    "version": "11.2",
    "productType": "Watch7,1",
    "operatingSystem": { "major": 26, "minor": 5, "patch": 0 }
  },
  "device": {
    "name": "Apple Watch",
    "manufacturer": "Apple Inc.",
    "model": "Watch",
    "hardwareVersion": "Watch7,1",
    "softwareVersion": "26.5"
  },
  "metadata": {
    "HKWasUserEntered": { "type": "bool", "value": true },
    "HKTimeZone": { "type": "string", "value": "Europe/London" }
  }
}

kind#

Record kinds and their extra fields
kindExtra fieldsWhat it is
quantityquantityA measurement — steps, heart rate, weight.
categoryvalue (Integer)A classified event — a sleep stage, a stand hour.
workoutactivityType, duration, eventsOne workout.
correlationmembersA grouping, such as a blood pressure reading.
workoutRouteworkoutA GPS route’s own record.
workoutRouteLocationsroute, sequence, offset, count, locationsOne page of route points.
workoutRouteEndroute, locationsMarks a route as completely written.
deletion—A tombstone. Carries only kind, id, type, schemaVersion.
sampleEncodingErrormessageA sample Hozz could not encode, written in its place so the batch never silently omits it.
sample—An HKSample subclass this build has no specific handling for.

Metadata is type-tagged#

Each metadata value carries a type: string, number, bool, date, data (base64), quantity (which carries a description string rather than a value), array (whose value is an array of tagged values), or unsupported (with a class naming what it was).

Workout routes#

Routes use three record kinds so large tracks can be paged.

One page of route points
{
  "kind": "workoutRouteLocations",
  "route": "…",
  "sequence": 0,
  "offset": 0,
  "count": 500,
  "locations": [
    {
      "timestamp": "2026-08-22T07:00:00.000Z",
      "latitude": 51.5007,
      "longitude": -0.1246,
      "altitude": 11.2,
      "horizontalAccuracy": 4.1,
      "verticalAccuracy": 3.0,
      "course": 182.0,
      "speed": 3.1
    }
  ]
}

Pages hold 500 points at fixed offsets. Negative “unknown” values are omitted. workoutRouteEnd carries the final count, exposing truncation.

workout.state is resolved or unresolved. Unresolved routes carry a reason, not a guessed workout.

NDJSON#

The default. One record per line, \n terminated, application/x-ndjson. Lossless.

application/x-ndjson
{"catalogVersion":6,"endDate":"2026-08-22T21:10:46.500Z","id":"2f1a…","kind":"quantity","metadata":{},"quantity":{"description":"62.5 count/min","unit":"count/min","value":62.5},"schemaVersion":1,"source":{"bundleIdentifier":"com.apple.health.ABC","name":"Apple Watch"},"startDate":"2026-08-22T21:10:46.500Z","type":"HKQuantityTypeIdentifierHeartRate"}
{"id":"9c40…","kind":"deletion","schemaVersion":1,"type":"HKQuantityTypeIdentifierStepCount"}

Sorted keys make identical records produce identical bytes.

JSON#

The same records as one array, application/json. Lossless.

application/json
[
{"catalogVersion":6,"id":"2f1a…","kind":"quantity","…":"…"},
{"id":"9c40…","kind":"deletion","schemaVersion":1,"type":"HKQuantityTypeIdentifierStepCount"}
]

CSV#

One flat table, text/csv, with a header row.

text/csv
id,type,kind,startDate,endDate,value,unit,sourceName,deleted
2f1a…,HKQuantityTypeIdentifierHeartRate,quantity,2026-08-22T21:10:46.500Z,2026-08-22T21:10:46.500Z,62.5,count/min,Apple Watch,false
9c40…,HKQuantityTypeIdentifierStepCount,deletion,,,,,,true

Whole numbers omit decimals. deleted is true only for deletion.

Metrics JSON#

Grouped by metric, application/json.

application/json
{
  "data": {
    "metrics": [
      {
        "name": "heart_rate",
        "units": "count/min",
        "data": [
          {
            "date": "2026-08-22T21:10:46.500Z",
            "qty": 62.5,
            "units": "count/min",
            "source": "Apple Watch"
          }
        ]
      },
      {
        "name": "sleep_analysis",
        "units": "count",
        "data": [
          {
            "date": "2026-08-22T22:00:00.000Z",
            "endDate": "2026-08-22T22:30:00.000Z",
            "qty": 3,
            "units": "count",
            "source": "Apple Watch"
          }
        ]
      }
    ],
    "workouts": [
      { "id": "7b21…", "name": "Workout", "start": "…", "end": "…" }
    ],
    "deletions": [
      { "id": "9c40…", "name": "step_count", "type": "HKQuantityTypeIdentifierStepCount", "date": "" }
    ]
  }
}
Metrics JSON fields
FieldTypeNotes
metrics[].nameStringShort snake_case name — see below.
metrics[].unitsStringHealthKit’s unit string, or count when the type has none.
…data[].dateStringISO 8601 UTC. The sample’s startDate.
…data[].qtyNumberAbsent when the record has no numeric value.
…data[].unitsStringRepeated per point.
…data[].sourceStringAbsent when HealthKit named no source.
…data[].endDateStringPresent only when the sample covers an interval.
workouts[]ArrayAbsent when the batch has none.
deletions[]ArrayAbsent when the batch has none.

endDate preserves intervals. Hozz sends HealthKit samples, not rollups.

deletions carries tombstones. Health provides no removal time.

Metric names#

Lowercase snake_case. A curated name is used where one exists: HKQuantityTypeIdentifierStepCount becomes step_count, HKQuantityTypeIdentifierActiveEnergyBurned becomes active_energy, HKQuantityTypeIdentifierOxygenSaturation becomes blood_oxygen_saturation.

Uncurated types drop the HealthKit prefix and become snake_case.

InfluxDB line protocol#

text/plain; charset=utf-8. See the InfluxDB page for measurements, tags, precision and escaping.

Delivery mechanics#

Headers, status-code handling and how the idempotency key is derived are on Your own endpoint. Folder file naming is on Folder, and topic layout is on MQTT.