How to Integrate Apple HealthKit (2026)
Updated July 9, 2026
Apple HealthKit gives your iOS app read and write access to the user's on-device health and fitness data — steps, workouts, heart rate, sleep, and dozens of other types — through a local, OS-managed store called HKHealthStore. Crucially, it is not an OAuth cloud API: there are no client secrets, tokens, or server endpoints. The user grants access in a system permission sheet, and your Swift code reads or writes the data directly on the device. This tutorial walks through wiring up the HealthKit capability, requesting authorization, reading step data, and getting background updates.
HealthKit is free (no API fees) and iOS/iPadOS/watchOS/visionOS only — there is no cross-platform endpoint. If you also need Android, you integrate Google Health Connect separately; the two are compared side by side in Apple HealthKit vs. Google Health Connect. Everything below is native iOS/Swift.
Version note. API signatures below are long-stable public HealthKit API, but a few initializers are version-gated (for example
HKQuantityType(.stepCount)is iOS 16+). As of 2026, verify exact signatures against the current Apple docs before shipping.
What you'll need
- An Apple Developer account and Xcode. HealthKit requires a paid membership to enable the capability and ship to devices.
- A physical device or a simulator with the Health app. HealthKit data is not available on every target (notably some iPad configurations), so you must guard against it at runtime.
- The HealthKit capability added to your app target, plus two Info.plist usage-description strings (below). Missing these is the most common first-run crash.
There is no app-registration or partner-approval step like a cloud fitness API — authorization happens entirely on the user's device. The only external gate is App Review, which checks that your usage-description strings are honest and that you actually use the data you request.
Step 1: Add the HealthKit capability and Info.plist keys
In Xcode, select your target, open Signing & Capabilities, and add the HealthKit capability. This provisions the entitlement your app needs to talk to HKHealthStore.
Then add usage-description strings to your Info.plist. These are the text the system shows the user in the permission sheet, and they are mandatory:
NSHealthShareUsageDescription— required if you read any health data.NSHealthUpdateUsageDescription— required if you write / share any health data.
<!-- Info.plist -->
<key>NSHealthShareUsageDescription</key>
<string>We read your step count to show your daily activity progress.</string>
<key>NSHealthUpdateUsageDescription</key>
<string>We save completed workouts to your Health app.</string>
If you call the authorization API without the matching key present, the app crashes rather than showing a sheet — so add both keys for whichever operations you support before writing any code.
Step 2: Check availability and request authorization
Everything starts with a single HKHealthStore instance. Before touching it, guard with the static HKHealthStore.isHealthDataAvailable(), which returns false on unsupported devices. Then call requestAuthorization(toShare:read:) with the set of types you want to write (Set of HKSampleType) and the set you want to read (Set of HKObjectType).
import HealthKit
let healthStore = HKHealthStore()
func requestAuth() async throws {
guard HKHealthStore.isHealthDataAvailable() else { return }
let stepType = HKQuantityType(.stepCount) // iOS 16+ initializer
let toRead: Set<HKObjectType> = [stepType]
let toShare: Set<HKSampleType> = [stepType]
// Presents the system permission sheet.
try await healthStore.requestAuthorization(toShare: toShare, read: toRead)
}
The async form shown here is iOS 15+. Older code uses the completion-handler variant requestAuthorization(toShare:read:completion:); either is fine. On older OS versions you can also build the step type with HKObjectType.quantityType(forIdentifier: .stepCount) instead of the HKQuantityType(.stepCount) initializer.
Step 3: Understand the read-denial caveat (important)
This is the single most misunderstood part of HealthKit, so handle it deliberately. To avoid leaking whether a person has health data at all, the system does not tell you whether the user granted or denied READ access. authorizationStatus(for:) reliably reflects only write / share status; for read types it typically returns .notDetermined even after the user grants access.
The consequence: requestAuthorization succeeding means "the sheet was shown," not "read was granted." Never gate your UI on read-authorization status. The documented pattern is to just run the query and treat an empty result as "no data or no permission" — the two are indistinguishable, and that is by design. Build your UI to degrade gracefully when a query comes back empty.
Step 4: Read today's step count
For a running total like steps, use HKStatisticsQuery with the .cumulativeSum option (rather than summing raw samples yourself). Scope it with a date predicate so you only aggregate today's data:
func readTodaySteps() {
let stepType = HKQuantityType(.stepCount)
let startOfDay = Calendar.current.startOfDay(for: Date())
let predicate = HKQuery.predicateForSamples(
withStart: startOfDay, end: Date(), options: .strictStartDate)
let query = HKStatisticsQuery(
quantityType: stepType,
quantitySamplePredicate: predicate,
options: .cumulativeSum
) { _, statistics, error in
guard let sum = statistics?.sumQuantity() else {
// Empty == no data OR read permission not granted (indistinguishable).
return
}
let steps = sum.doubleValue(for: HKUnit.count())
print("Steps today: \(steps)")
}
healthStore.execute(query)
}
If you need the individual raw samples instead of an aggregate — for example to inspect timestamps or the source app — use HKSampleQuery with the same predicate. For per-workout rep and form data that HealthKit does not capture, pair this with an on-device motion pipeline; the AI workout tracking on iOS with Swift guide walks through that side.
Step 5: Get background updates with HKObserverQuery
Polling drains battery. To react when new data lands, register an HKObserverQuery and turn on enableBackgroundDelivery(for:frequency:withCompletion:). The observer's completion handler must be called or the OS stops delivering updates.
func startObservingSteps() {
let stepType = HKQuantityType(.stepCount)
let observer = HKObserverQuery(sampleType: stepType, predicate: nil) {
_, completionHandler, error in
// Fetch new data here (e.g. an HKAnchoredObjectQuery), then:
completionHandler() // MUST call, or the OS stops delivering updates.
}
healthStore.execute(observer)
healthStore.enableBackgroundDelivery(
for: stepType, frequency: .immediate) { success, error in
// .immediate / .hourly / .daily via HKUpdateFrequency; delivery is OS-throttled.
}
}
Frequency is chosen from HKUpdateFrequency (.immediate, .hourly, .daily). Note that background delivery is budgeted and throttled by the OS — you will not get literal real-time firing, and the actual cadence depends on system conditions. Design around eventual delivery, not guaranteed timing.
Gotchas and production notes
- Read denial is invisible. Repeating the Step 3 point because it bites everyone: never assume an empty query means "no data." Always handle empty results as a valid, expected state.
- Both Info.plist keys or a crash. Add
NSHealthShareUsageDescriptionfor reads andNSHealthUpdateUsageDescriptionfor writes before your first authorization call. - Availability first. Guard every entry point with
HKHealthStore.isHealthDataAvailable(); it is false on unsupported devices and your queries will otherwise fail silently. - Aggregate, don't hand-sum, for cumulative types.
HKStatisticsQuerywith.cumulativeSumavoids double-counting when multiple sources (phone plus watch) write steps. - App Review scrutinizes health usage. Your usage-description strings must accurately describe what you do with the data, and you should request only the types you genuinely use.
- Background delivery is throttled. Treat
HKObserverQueryplusenableBackgroundDeliveryas best-effort, and always call the completion handler. - Version-gate the volatile bits.
HKQuantityType(.stepCount)is iOS 16+; fall back toHKObjectType.quantityType(forIdentifier:)on older targets, and verify current signatures in Apple's docs before release.
Frequently asked questions
- Does Apple HealthKit use OAuth or API keys?
- No. HealthKit is an on-device, permission-based framework, not a cloud API. There are no client secrets, access tokens, refresh flows, or server endpoints. The user grants access in a native system permission sheet and your app reads or writes a local, OS-managed store directly. The only credential you need is a paid Apple Developer account to enable the capability and ship the app.
- Why can't I tell whether the user granted read permission?
- This is intentional. To avoid leaking whether a person has any health data, HealthKit does not report read-authorization status. authorizationStatus(for:) reliably reflects only write/share access; for read types it typically returns notDetermined even after a grant. The documented pattern is to run your query and treat an empty result as no data or no permission, since the two are indistinguishable by design.
- Does HealthKit work on Android or on the web?
- No. HealthKit is limited to Apple platforms (iOS, iPadOS, watchOS, visionOS) and there is no cross-platform server endpoint. For Android you integrate Google Health Connect, which is a separate on-device framework with its own SDK, permissions model, and quirks such as a default 30-day history window.
- Why does my app crash the first time I request authorization?
- Almost always a missing Info.plist usage-description key. HealthKit requires NSHealthShareUsageDescription to read data and NSHealthUpdateUsageDescription to write data. If you call requestAuthorization without the key that matches your operation, the app crashes instead of showing the permission sheet, so add both keys for whatever you support before running the flow.
- How should I read step totals without double-counting?
- Use HKStatisticsQuery with the cumulativeSum option rather than fetching raw samples and adding them yourself. When multiple sources such as an iPhone and an Apple Watch both write steps, aggregating through the statistics query avoids double-counting. Verify version-gated details like the HKQuantityType(.stepCount) initializer, which is iOS 16+, against current Apple docs.
Keep reading
Independent comparison, last reviewed July 9, 2026. Pricing, rate limits, and feature availability change often — confirm current details in each provider’s official documentation before you commit. Product and company names are trademarks of their respective owners; AIFitnessAPI is not affiliated with, endorsed by, or sponsored by any product listed here.
← All integration guides · by AIFitnessAPI