How to Integrate Apple HealthKit (2026)
Last verified October 2, 2026 · 10 min read
Sources checked (23) · October 2, 2026
- developer.apple.com/documentation/healthkit/hkhealthstore — access point for HealthKit data; full, limited-window or no access
- developer.apple.com/documentation/healthkit/about-the-healthkit-framework — which devices have a HealthKit store; macOS and older iPadOS have none
- developer.apple.com/documentation/healthkit/setting-up-healthkit — capability, isHealthDataAvailable errors, Clinical Health Records review warning
- developer.apple.com/documentation/xcode/configuring-healthkit-access — purpose strings as an App Store requirement; background-delivery capability
- developer.apple.com/help/account/reference/supported-capabilities-ios — HealthKit capability for free and paid accounts; App Store distribution is paid-program only
- developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.healthkit — entitlement description, verbatim
- developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.healthkit.background-delivery — background-delivery entitlement description, verbatim
- developer.apple.com/documentation/bundleresources/information-property-list/nshealthshareusagedescription — read purpose-string key, verbatim
- developer.apple.com/documentation/bundleresources/information-property-list/nshealthupdateusagedescription — write purpose-string key, verbatim
- developer.apple.com/documentation/healthkit/hkhealthstore/requestauthorization(toshare:read:) — async form (iOS 15+), crash without usage keys
- developer.apple.com/documentation/healthkit/hkhealthstore/requestauthorization(toshare:read:completion:) — completion form (iOS 8+); success does not mean permission was granted
- developer.apple.com/documentation/healthkit/hkhealthstore/authorizationstatus(for:) — share-side status only; denied reads look like no data
- developer.apple.com/documentation/healthkit/hkhealthstore/earliestauthorizedsampledate(for:) — limited history window, iOS 27+
- developer.apple.com/documentation/healthkit/hkquantitytype/init(_:) — HKQuantityType(_:) is iOS 15+ and watchOS 8+
- developer.apple.com/documentation/healthkit/hkobjecttype/quantitytype(forIdentifier:) — deprecation marked at iOS 27.2
- developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/stepcount — count units, cumulative values
- developer.apple.com/documentation/healthkit/hkstatisticsquery/init(quantitytype:quantitysamplepredicate:options:completionhandler:) — statistics query initializer used in Step 4
- developer.apple.com/documentation/healthkit/hkstatistics — statistics merge data from all sources unless separateBySource
- developer.apple.com/documentation/healthkit/hkobserverquery/init(sampletype:predicate:updatehandler:) — observer query initializer and completion handler
- developer.apple.com/documentation/healthkit/hkobserverquerycompletionhandler — backoff and stop after three missed completions
- developer.apple.com/documentation/healthkit/hkhealthstore/enablebackgrounddelivery(for:frequency:withcompletion:) — entitlement requirement, hourly cap for stepCount, watchOS budget, no Simulator support, launch setup
- developer.apple.com/documentation/healthkit/hkupdatefrequency — immediate, hourly, daily and weekly
- developer.apple.com/app-store/review/guidelines/ — guideline 5.1.1 purpose strings and data minimization
Covered here:HealthKitHealth Connect
HealthKit data lives only on Apple devices — iPhone, Apple Watch, Vision Pro, and iPads on iPadOS 17 or later; Apple notes the framework also compiles for macOS and older iPadOS, but there isHealthDataAvailable() returns false — and 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. HealthKit's App Review gate, and every page on this site that covers HealthKit, are collected in the HealthKit API overview. Everything below is native iOS/Swift. If your app is React Native or Flutter instead, react-native-health vs @kingstinct/react-native-healthkit compares the two React Native bridges to HealthKit, and health_kit_reporter covers an iOS-only Flutter plugin.
Version note. API signatures below are long-stable public HealthKit API, but a few initializers are version-gated (for example
HKQuantityType(.stepCount)is iOS 15+ and watchOS 8+). Signatures and availability were checked against Apple's documentation on October 2, 2026; re-check them before shipping.
What you'll need#
- An Apple Developer account and Xcode. Apple's capability table lists HealthKit for free Apple Developer accounts as well as paid memberships; distributing on the App Store needs the paid Apple Developer Program.
- A device with a HealthKit store — iPhone, Apple Watch, Vision Pro, or an iPad on iPadOS 17 or later. HealthKit data isn't available on iPadOS 16 and earlier or on macOS, so guard against it at runtime, and test background delivery on a device: Apple says background queries aren't supported in the Simulator.
- The HealthKit capability added to your app target, plus two Info.plist usage-description strings (below). Missing the one your request needs crashes the app at the authorization call.
There is no app-registration or partner-approval step like a cloud fitness API — authorization happens on the user's device. The external gate is App Review: Apple calls the purpose strings an App Store requirement for any HealthKit app, App Review Guideline 5.1.1 asks that they clearly and completely describe your use of the data and that you request only data your core features need, and Apple warns it may reject apps that enable the Clinical Health Records capability without actually using health records.
The strings you will paste#
Every key and call in this table is copied from Apple's developer documentation, fetched on October 2, 2026. The quoted column is Apple's own wording, and the name in parentheses is the title Apple's documentation gives the item in Xcode.
| String | Kind (Xcode name) | Apple's description, verbatim | Apple reference |
|---|---|---|---|
com.apple.developer.healthkit | Entitlement ("HealthKit") | "A Boolean value that indicates whether the app may request user authorization to access health and activity data that appears in the Health app." | HealthKit Entitlement |
NSHealthShareUsageDescription | Info.plist key ("Privacy - Health Share Usage Description") | "A message that explains to people why the app requests permission to read samples from the HealthKit store." | NSHealthShareUsageDescription |
NSHealthUpdateUsageDescription | Info.plist key ("Privacy - Health Update Usage Description") | "A message to the user that explains why the app requested permission to save samples to the HealthKit store." | NSHealthUpdateUsageDescription |
com.apple.developer.healthkit.background-delivery | Entitlement ("HealthKit Observer Query Background Delivery") | "A Boolean value that indicates whether observer queries receive updates while running in the background." | com.apple.developer.healthkit.background-delivery |
requestAuthorization(toShare:read:) | HKHealthStore method, async, iOS 15+ | "Asynchronously requests permission to save and read the specified data types." | requestAuthorization(toShare:read:) |
requestAuthorization(toShare:read:completion:) | HKHealthStore method, completion handler, iOS 8+ | "Requests permission to save and read the specified data types." | requestAuthorization(toShare:read:completion:) |
Step 1: Add the HealthKit capability and Info.plist keys#
In Xcode, select your target, open Signing & Capabilities, and add the HealthKit capability. That is how the com.apple.developer.healthkit entitlement gets into your app; Apple's entitlement page says "To add this entitlement to your app, enable the HealthKit capability in Xcode."
Then add the two usage-description keys from the table: NSHealthShareUsageDescription if you read any health data, NSHealthUpdateUsageDescription if you write / share any. Their values are the text the system shows the user in the permission sheet. For projects created with Xcode 13 or later, Apple says to set these keys in the Target Properties list on the app's Info tab; projects created with Xcode 12 or earlier set them in the Info.plist file:
<!-- 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>
Apple's requestAuthorization reference is blunt about skipping this step: "You must set the usage keys, or your app will crash when you request authorization." Add the key for each operation 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 15+ 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. For targets older than iOS 15 you can build the step type with HKObjectType.quantityType(forIdentifier: .stepCount) instead of the HKQuantityType(.stepCount) initializer — but Apple's reference marks that class method for deprecation in iOS 27.2, so use it only where you have to.
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:) covers only the share (write) side — Apple: "This method checks the authorization status for saving data" — and tells you nothing about reads.
The consequence: requestAuthorization succeeding means the request was processed — Apple says the success value "doesn't indicate whether the user actually granted permission" — not that read was granted. Never gate your UI on read-authorization status. The documented behavior is that a denied read "simply appears as if there is no data of the requested type" (your own app's samples stay visible), so run the query and treat an empty result as "no data or no permission" — the two are indistinguishable by design. Build your UI to degrade gracefully when a query comes back empty.
Apple's authorization flow now adds a third case: a person can grant full access, access limited to a recent window of their history, or none. From iOS 27, earliestAuthorizedSampleDate(for:) returns the earliest readable date for each type under such a limit — the only authorization state Apple lets you identify — so start date-range queries no earlier than that date, and treat anything before it as unknown rather than 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). Apple documents stepCount samples as using count units and measuring cumulative values, which is why the sum is the right statistic and the result is read in HKUnit.count(). Not every type is cumulative, so check a type's aggregation style in HealthKit data types by group before you pick an option. Scope the query 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:). Call the observer's completion handler when you finish processing: Apple says that if you don't, HealthKit keeps retrying with a backoff and, after your app fails to respond three times, stops sending background updates. Apple also says to set up observer queries in application(_:didFinishLaunchingWithOptions:), so they exist before HealthKit delivers updates at launch.
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() // Call it, or HealthKit backs off and eventually stops.
}
healthStore.execute(observer)
healthStore.enableBackgroundDelivery(
for: stepType, frequency: .immediate) { success, error in
// HKUpdateFrequency: .immediate, .hourly, .daily or .weekly — a maximum, not a schedule.
}
}
Frequency is chosen from HKUpdateFrequency (.immediate, .hourly, .daily, .weekly), and it is a ceiling: Apple says the system wakes your app at most once per period, some sample types are capped at hourly, and on watchOS background updates share a budget with background app refresh tasks — four an hour, as long as the app has a complication on the active watch face. Design around eventual delivery, not guaranteed timing. If updates never arrive at all, work through HealthKit background delivery not working.
Two documented constraints sit on top of that. Apple's enableBackgroundDelivery reference says that for iOS 15 and watchOS 8 and later you must add the com.apple.developer.healthkit.background-delivery entitlement from the table above; without it, the call fails with an HKError.Code.errorAuthorizationDenied error. The same page notes that on iOS, stepCount samples have an hourly maximum frequency, so .immediate on steps still wakes your app at most once an hour.
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(); where it is false, Apple says other HealthKit methods fail witherrorHealthDataUnavailable(orerrorHealthDataRestrictedwhere HealthKit is restricted, for example in an enterprise environment). - Aggregate, don't hand-sum, for cumulative types. Apple says statistics queries merge the data from all of your sources before calculating, unless you ask for
.separateBySource; summing raw samples yourself counts overlapping iPhone and Apple Watch samples twice. - App Review scrutinizes health usage. Guideline 5.1.1 wants purpose strings that clearly and completely describe your use of the data, and access only to data relevant to your core functionality.
- Background delivery is throttled and entitled. Treat
HKObserverQueryplusenableBackgroundDeliveryas best-effort, add the background-delivery entitlement, and always call the completion handler. - Version-gate the volatile bits.
HKQuantityType(.stepCount)is iOS 15+; fall back toHKObjectType.quantityType(forIdentifier:)only on older targets (Apple marks it for deprecation in iOS 27.2), 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. What you need is an Apple Developer account to add the capability — Apple lists HealthKit for free accounts as well as paid ones — and the paid Apple Developer Program to distribute on the App Store.
- 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:) checks only whether your app may save a type, and a denied read simply looks like no data (apart from samples your own app wrote). Run your query and treat an empty result as no data or no permission. From iOS 27 the one read state you can detect is a limited history window, through earliestAuthorizedSampleDate(for:).
- 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. Apple says statistics queries merge the data from all of your sources before calculating, so an iPhone and an Apple Watch that both record steps are not simply added together. Verify version-gated details like the HKQuantityType(.stepCount) initializer, which is iOS 15+, against current Apple docs.
Keep reading
From the blog
Findings counted out of this site’s own datasets.
- Apple's Newest HealthKit Types Are a RoadmapApple ships the data type years before most apps ship the feature. The additions since iOS 16 are a public preview of what the platform expects to matter.
- 58 HealthKit Types Apple Never Explains58 of HealthKit's 240 identifiers carry zero words of discussion prose, and the median across all 240 is 15 words. What to do when the docs say nothing.
- HealthKit: 127 of 240 Types Are iOS 8.0HealthKit has 240 type identifiers and 127 shipped in iOS 8.0. Four are deprecated, none removed. The full version histogram, and what it costs you.
Elsewhere on the site
Pages that share this one’s concepts and sources, from other sections.
Next steps
Was this page useful?
Independent comparison, last reviewed October 2, 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