miniHabits
BUILD LOG · OUTLINE · 9 APR 2026

HealthKit integration notes from an app that built it and then removed it

Permissions you cannot query, observers that over-fire, duplicate samples from three sources, and a guideline 2.5.1 rejection at the end of it.

build log 504 words of 1 700 planned

miniHabits had a HealthKit mode that read five things: steps, exercise minutes, workouts, sleep and mindful minutes. Five is not many, and the integration still took longer than the entire statistics screen. It is not in the app. App Review rejected the first submission under guideline 2.5.1 — the binary referenced HealthKit while no primary feature required health data — and rather than argue the point I removed the mode, because they were right.

That is the part worth writing down, so here it is first. A habit tracker that can optionally fill one habit from Health does not have a primary feature that requires health data. It has a convenience. Apple’s line is that HealthKit is for apps whose core purpose is health and fitness data, and a tracker whose core purpose is a chain of days does not qualify by adding a step counter to it. The rejection took eight days to arrive and about a day to fix, and the app is better for it: what shipped instead is a quantity habit you type into, which never lies about where the number came from.

The technical notes still stand, because the integration worked before it was deleted.

Read permissions are deliberately opaque: for privacy reasons you cannot ask whether the user granted read access to a type, only whether they were asked. So an empty result means either no permission or no data, and your UI has to handle both without accusing the user of anything. Observer queries fire more often than you expect, including for samples that change retroactively when another app writes history, which means a naive implementation recomputes a habit twenty times an hour.

Duplicates are the third. A single walk can produce step samples from the phone, the Watch and a third party app, and summing them gives a number that is confidently wrong. The statistics query handles this if you use it, and the sample query does not, which is not obvious from the names.

Outline

Status: outline. The introduction above is finished copy. Everything below is the section plan for the full draft, targeted at about 1 700 words.

  1. Guideline 2.5.1, in full. What the rejection said, what triggered it, and why an Info.plist key is enough to trigger it even when the framework is never linked.
  2. The five metrics. Why these, and what each one mapped to in the app.
  3. Opaque read permissions. The API, the reason for it, and the UI that has to cope.
  4. Observer queries. Over-firing, retroactive edits, and the debounce that fixed it.
  5. Duplicate samples. Statistics query versus sample query, with numbers.
  6. Background delivery. What you get, how often, and what the system throttles.
  7. Removing it without losing data. The decode migration that turns a saved Health habit into a quantity habit instead of an unreadable file.
  8. What to build instead. When a tracker should reach for HealthKit, and when it should just let someone type a number.
Build your plan, then choose annual or lifetime access.
Get miniHabits