---
title: "Apple Health days, as Health stores them"
method: POST
path: "/wellness/health-days/batch"
tags: ["Recovery"]
---

# Apple Health days, as Health stores them

`POST /wellness/health-days/batch`

## Request

- Base URL: `https://bodylab.lol/api/v1`
- URL: `https://bodylab.lol/api/v1/wellness/health-days/batch`
- Auth: HTTP bearer

For the iOS app: each item is one Health stream's complete sample set for one local day. At most 50 days per request. The server archives each day and derives its readings (resting HR, HRV, VO2max, weight, sleep minutes and the sleep period, and from step_count's statistics the day's step count). Idempotent: a day identical to the one held answers "unchanged". An empty day deletes the readings derived from it.

## Headers

- `X-Client-Platform` string
- `X-Client-Version` string

## Request body

- object
  - `days` object[], required
    - `stream` string, required: Which Health stream this day's samples are. Known values: sleep_analysis, resting_hr, hrv_sdnn, vo2max, body_mass, step_count. Treat unknown values as "other".
    - `day` string, date, required: The local day the phone filed these samples under: a reading's own local day, or for sleep_analysis the day its interval ends into with an 18:00 rollover (dayRule).
    - `dayRule` string, required: How the phone assigned samples to day, e.g. "local_day" or "sleep_rollover_18".
    - `timeZone` string, required: The IANA zone the phone used to assign the day.
    - `samples` object[], required: Every sample Health holds for this stream and day, from every source. Empty when the day's samples were all deleted. Always empty for step_count, whose day is its statistics.
      - `uuid` string, required: HealthKit's UUID for the sample.
      - `start` string, date-time, required: An instant: ISO-8601 with offset, e.g. 2026-09-13T06:30:00Z.
      - `end` string, date-time, required: Equal to start for a point reading.
      - `value` union, required: A quantity in the stream's unit (resting_hr count/min, hrv_sdnn ms, vo2max mL/kg·min, body_mass kg), or for sleep_analysis the HKCategoryValueSleepAnalysis case name (inBed, asleepUnspecified, asleepCore, asleepDeep, asleepREM, awake).
        - number
        - string
      - `source` object, required
        - `bundleId` string, required: sourceRevision.source.bundleIdentifier, as Health reports it.
        - `name` string, nullable, required
        - `productType` string, nullable, required: sourceRevision.productType, e.g. a watch model identifier.
      - `timeZone` string, nullable, required: The sample's HKTimeZone metadata, when it has one. No other metadata is sent.
    - `statistics` object: Required for step_count, and absent for every other stream; a day breaking either rule is answered rejected (missing_statistics, unexpected_statistics).
      - `sumCount` number, required: sumQuantity() for the day: HealthKit's merged total across every source, overlaps removed as the Health app removes them. This is the day's count. 0 when nothing was counted, which removes the day's count.
      - `sources` object[], required: Each source's own total for the day, kept as evidence of what HealthKit merged. Never added up: their sum counts the same steps once per device that recorded them.
        - `bundleId` string, required: The source's bundle identifier, as Health reports it.
        - `name` string, nullable, required
        - `sumCount` number, required: sumQuantity(for: source) for the day: that source's own total.

## Response `200`

OK

- object
  - `results` object[], required
    - `stream` string, required
    - `day` string, required
    - `result` string, required: stored: archived and its readings derived; unchanged: identical to what's held; rejected: see reason. Known values: stored, unchanged, rejected. Treat unknown values as "other".
    - `reason` string, nullable, required: Set when result is "rejected". unsupported_stream: a stream this server doesn't read yet. unknown_time_zone: the server can't read clocks in timeZone. missing_statistics: a step_count day without its statistics. unexpected_statistics: statistics on a stream that has none. Known values: unsupported_stream, unknown_time_zone, missing_statistics, unexpected_statistics. Treat unknown values as "other".

## Other responses

- `401`: Error: UNAUTHORIZED.

---

[API](https://skmtc.dev/bodylab/apis/body-lab-api.md) · [All operations](https://skmtc.dev/bodylab/apis/body-lab-api/llms.txt) · [OpenAPI document](https://skmtc.dev/bodylab/apis/body-lab-api/revisions/544c201eaf88?raw)
