megabyte

Apple Health app sync to a private API

Why Apple Health data needs an on-device HealthKit bridge before it can reach a private backend, and how HealthSync handles that sync path.

Apple Health data is useful because it already sits close to the person wearing the device. Steps, workouts, sleep, heart rate, energy, nutrition, water, and body measurements all meet inside the Health app after the user grants permission to the apps and devices they trust.

The hard part starts when you want that data somewhere else.

There is no general Apple Health REST API that a backend can call. HealthKit is an on-device framework. A server cannot just ask Apple for a user's Apple Health history. The user has to install an iOS app, approve HealthKit read permissions, and let that app read selected samples locally.

That is the reason I built HealthSync Apple Health app sync. It is an iOS HealthKit bridge for sending selected Apple Health data to a private backend API controlled by the user.

The sync shape

The architecture is intentionally simple:

  1. The iOS app asks for HealthKit read permissions on-device.
  2. The user selects which data types are allowed to sync.
  3. The app reads samples and workouts from HealthKit.
  4. The app normalizes those records into JSON batches.
  5. The app stores pending batches locally when upload is not possible.
  6. The app posts the batch to the configured backend URL with an auth token.
  7. The backend stores the records for dashboards, agents, exports, or analysis.

This makes the phone the only place where Apple Health access happens. The backend receives only what the user configured the app to send.

Why not export files?

Apple's export flow is useful for occasional archives, but it is awkward for recurring workflows. A zipped export is too manual for dashboards, private agents, or self-hosted databases that need recent data.

If the goal is a personal dashboard, a local warehouse, a quantified-self database, or an automation stack, a small app-to-API bridge is a better fit. The sync can happen in batches, use stable identifiers, retry failed uploads, and keep the backend schema under the user's control.

What belongs in the backend

The backend should stay boring. It needs an authenticated ingest endpoint, idempotency rules, and a schema that preserves enough source metadata to avoid confusing repeated syncs with new records.

For HealthSync, the useful backend contract is:

  • accept POST /api/apple-health/sync
  • authenticate requests with a token
  • store metrics and workouts separately enough for querying
  • keep the local device id and source identifiers
  • accept repeated batches without duplicating records
  • expose read endpoints for dashboards and agents

That is enough for the first useful version. The backend does not need Apple credentials, and it should not pretend to be an Apple Health API replacement.

The privacy boundary

The important product boundary is that health data does not go to a random third-party cloud by default. The user configures the backend URL and token. That backend can be self-hosted, private, or a hosted workspace the user chose.

The iOS app should also avoid collecting unrelated telemetry. For health data tools, the simplest trust model is usually the best one: read only the selected HealthKit types, sync only to the configured endpoint, and keep retry state local.

Where HealthSync fits

HealthSync is not trying to replace the Apple Health app. Apple Health remains the source-of-truth interface on the phone. HealthSync is the bridge for people who want those records in their own system.

The detailed resource page is here:

The code is here:

The product is most useful for builders who already know what they want to do with the data once it leaves the phone: Grafana dashboards, Postgres tables, personal agents, home automations, or research notebooks.

The main constraint is also the main design rule: Apple Health sync starts on the iPhone. Once that is accepted, the rest of the system can stay clean, private, and easy to reason about.

On this page