Skip to content
AF
Troubleshooting

HealthKit Authorization Denied: What It Means and What It Hides

Last verified August 11, 2026 · 10 min read

In HealthKit, a denied authorization is only visible on the write side. The status returned by authorizationStatus(for:) — notDetermined, sharingDenied, or sharingAuthorized — describes permission to save data, and Apple documents that your app cannot determine whether a user granted permission to read data, because a denied read simply looks like an empty store. If your app has share permission but not read permission, Apple states you see only the samples your own app wrote, and data from other sources stays hidden. The single exception is limited authorization: when someone grants a recent window of history instead of their full history, getEarliestAuthorizedSampleDate reveals that date, and Apple calls it the only authorization state your app can positively identify.

Covered here:HealthKitHealth Connect

There are two ways people arrive at "HealthKit authorization denied," and they are opposite mistakes about the same API. One developer calls authorizationStatus(for:), gets .sharingDenied, and disables the whole feature — including the reads, which that status never described. The other gets an empty array back, concludes the user denied them, and shows a "permission required" screen to someone who simply has no samples of that type. Untangling this starts with reading Apple's own description of what the status call covers.

What authorizationStatus(for:) actually reports#

Apple documents the method plainly: "This method checks the authorization status for saving data to the HealthKit store." Saving. Not reading. The three values it can return are all about the share side:

CaseApple's description
.notDetermined"The user has not yet chosen to authorize access to the specified data type."
.sharingDenied"The user has explicitly denied your app permission to save data of the specified type."
.sharingAuthorized"The user has explicitly authorized your app to save data of the specified type."

Even the enum name carries the warning: sharing, not access. Apple's overview for HKAuthorizationStatus says the same thing from the other direction — "This status indicates whether the user has authorized your app to save data of the given type."

So .sharingDenied is a complete answer to one question ("can I write this type?") and no answer at all to the other. Your reads may be fully granted alongside it. Read and share are separate permissions per type; Apple notes that "each data type has two separate permissions, one to read it and one to share it," and the permission sheet lets people toggle them independently within each category.

Read denial is invisible, and Apple says so explicitly#

This is the part that makes the symptom so confusing, and it is deliberate. Apple documents on authorizationStatus(for:):

To help prevent possible leaks of sensitive health information, your app cannot determine whether or not a user has granted permission to read data. If you are not given permission, it simply appears as if there is no data of the requested type in the HealthKit store. If your app is given share permission but not read permission, you see only the data that your app has written to the store. Data from other sources remains hidden.

The authorization article repeats it: "your app doesn't know whether someone granted or denied permission to read data. If they denied permission, attempts to read data from HealthKit return only samples that your app successfully saved to the HealthKit store."

That last clause is the detail people miss. A denied read is not an empty store — it is a store filtered down to your own writes. An app that saves workouts and reads them back will see its own data flowing perfectly while every other source stays invisible, which looks exactly like a working integration until a user with an Apple Watch complains.

The one authorization state you can positively identify#

There is a single exception, and it is newer than most HealthKit code. Apple documents that after the data-type screen, "a second screen prompts them to choose how much historical data to grant your app, either a recent limited window or their full history." When someone picks the limited window, your app is in a limited authorization state for those types, and that state is observable.

Call getEarliestAuthorizedSampleDate(for:completion:) after requesting authorization — Apple describes it as returning "the earliest date that the person permits your app to read samples for the given data types." The framing in Apple's article is worth quoting because it settles the whole question:

HealthKit intentionally prevents your app from distinguishing between full access and denied access to specific types; both cases return no entry in the result dictionary. Limited authorization is the only authorization state your app can positively identify.

No entry in the dictionary means either full access or denial. You still cannot tell those apart. What you gain is the ability to stop treating a short history as a data gap: Apple warns against "interpreting the absence of older samples as evidence they don't exist; the person's history may extend before the date the method returns," and shows passing the returned startDate into your query predicate rather than reaching for .distantPast.

let types: Set<HKObjectType> = [HKQuantityType(.stepCount)]
let authorizationDates = try await store.earliestAuthorizedSampleDate(for: types)
let queryStartDate = authorizationDates[HKQuantityType(.stepCount)] ?? .distantPast

Apple also suggests that if your app makes inferences on partial data, you consider telling people that granting full access improves the experience.

What getRequestStatusForAuthorization does and does not tell you#

This is the other method people reach for when they want a grant check, and it answers a different question again. Apple's abstract: it "indicates whether the system presents the user with a permission sheet if your app requests authorization for the provided types." Its three results are about the prompt, not the outcome:

  • .shouldRequest — "The application has not yet requested authorization for all the specified data types."
  • .unnecessary — "The application has already requested authorization for all the specified data types."
  • .unknown — "The authorization request status could not be determined because an error occurred."

.unnecessary means "asking again would show nothing," which is emphatically not "you were granted access." Treat it as a UI hint for whether to show your own pre-permission explainer, and nothing more.

It also explains a bug report you will eventually receive: calling requestAuthorization(toShare:read:) a second time appears to do nothing. Apple documents exactly that behaviour — "if the user has already chosen to grant or prohibit access to all of the types specified, HealthKit returns the request without prompting the user." You cannot re-prompt your way out of a denial.

Symptom to cause#

SymptomWhat it actually meansWhat to do
authorizationStatus(for:) returns .sharingDeniedWrites for that type are denied. Reads are unaffected and unknown.Disable saving for that type only; keep reading
authorizationStatus(for:) returns .notDeterminedYou never requested authorization, or never for this typeCall requestAuthorization(toShare:read:)
Query returns empty, but your own saved samples come backRead permission is denied for that typeRoute the user to Settings or the Health app
Query returns empty including your own writesNo data of that type exists, or plumbing is brokenIsolate with a write-then-read test
Samples appear, but history starts recentlyLimited authorization, not a gapUse the date from getEarliestAuthorizedSampleDate
requestAuthorization shows no sheetEvery requested type is already decidedSend the user to Settings; do not re-prompt
Save fails with errorAuthorizationDeniedThe user explicitly denied write for that typeStop saving; surface it in your UI honestly
Save fails with errorAuthorizationNotDeterminedYou never askedRequest authorization before saving

Denied writes, unlike denied reads, have real error codes#

Because the share side is observable, it also fails loudly. Apple's guidance is to check authorizationStatus(for:) before attempting a save, and documents the two failures you get if you skip that check: errorAuthorizationNotDetermined ("the app hasn't yet asked the user for the authorization required to complete the task") and errorAuthorizationDenied ("the user hasn't given the app permission to save data").

If your product treats "denied" as a single state, this asymmetry is the thing to encode instead: writes give you a status and an error code, reads give you neither.

The Info.plist keys that turn a permission bug into a crash#

Two usage-description keys govern the two sides, and Apple is unambiguous about what happens without them: "You must set the usage keys, or your app will crash when you request authorization."

  • NSHealthShareUsageDescription — Apple describes it as "a message that explains to people why the app requests permission to read samples from the HealthKit store." Required for reading.
  • NSHealthUpdateUsageDescription — "a message to the user that explains why the app requested permission to save samples to the HealthKit store." Required for writing.

For projects created with Xcode 13 or later, Apple says to set these in the Target Properties list on the app's Info tab; older projects set them in the information property list. If your "authorization denied" report is really a crash at the request call, this is where to look first, and the HealthKit integration guide covers the surrounding setup.

The correct UX pattern: you cannot fix this in code#

There is no API to toggle a permission on a user's behalf, so every honest recovery path ends in the system UI. Apple: "a person can change the permissions for your app at any time using either the Settings or Health app. After prompting for HealthKit authorization, your app appears in the Health app's Sources tab, even if the person didn't allow permission to read and share data."

That last clause is the useful one to put in your copy. Your app is listed under Sources whether or not anything was granted, so "open the Health app, find this app under Sources, and turn on the types you want shared" is instructions a user can actually follow. Two rules that follow from everything above:

  • Never gate your UI on read-authorization status, because there isn't one. Run the query, render what comes back, and treat empty as a legitimate state rather than an error — the same discipline that makes an empty read a first-class HealthKit test case.
  • Do not nag. Re-calling requestAuthorization after a decision shows no sheet at all, so a "grant access" button that silently does nothing is worse than a line of text pointing at the Health app.

Edge cases that look like denial but are not#

  • Apple Vision Pro Guest User sessions. Apple documents that in a guest session the guest can read data the owner already authorized but cannot authorize additional types, the authorization sheet is not displayed so "any attempt to request authorization for HealthKit data types during a Guest User session fails silently," and writes fail with errorNotPermissibleForGuestUserMode (or errorHealthDataRestricted on apps running in iOS 17). Critically, authorizationStatus(for:) still reports the owner's grant, so your status check and your save disagree. Apple's advice is to silently ignore the error for passive or periodic saves, and only surface an alert when the guest took an action that obviously implies saving.
  • Managed devices. errorHealthDataRestricted also signals that "a Mobile Device Management (MDM) profile restricts the use of HealthKit on this device" — an organisational policy, not a user choice.
  • Required clinical record types. If you declare types under the NSHealthRequiredReadAuthorizationTypeIdentifiers key, Apple says to specify three or more, and that denying any of them fails authorization with errorRequiredAuthorizationDenied — "the system doesn't tell your app which record types the person denied access to."

Where to go next#

If your immediate problem is an empty query rather than the authorization model itself, the write-then-read isolation sequence is laid out in HealthKit returning no data. If you are designing a pipeline that has to survive this ambiguity at scale — distinguishing gaps from denials across many users — see handling missing data and gaps. And if you are shipping the same feature on Android, Health Connect makes the opposite trade: granted read permissions are queryable, and a missing one surfaces as an exception on the read rather than as silence. The two models are set side by side in Apple HealthKit vs Google Health Connect.

Frequently asked questions

Does sharingDenied mean my HealthKit reads will fail too?
No. Apple documents authorizationStatus(for:) as checking the status for saving data to the HealthKit store, and the enum describes whether the user authorized your app to save data of the given type. Read and share are separate permissions on every type, so a user can deny writes while allowing reads. Disable saving for that type and keep running your queries.
What does getRequestStatusForAuthorization actually tell me?
Apple describes it as indicating whether the system presents a permission sheet if your app requests authorization for the provided types. Its results are shouldRequest, meaning you have not yet requested all the specified types, unnecessary, meaning you already have, and unknown, meaning an error occurred. Unnecessary means asking again would show nothing — it is not confirmation that access was granted.
Why does calling requestAuthorization again show no permission sheet?
Apple documents that if the user has already chosen to grant or prohibit access to all of the types specified, HealthKit returns the request without prompting. A second call is a no-op once every requested type has been decided, so a grant-access button that re-requests will silently do nothing. Point the user at Settings or the Health app instead.
How do I know if a user granted only a limited window of health history?
Apple's authorization sheet includes a second screen where people choose between a recent limited window and their full history. Call getEarliestAuthorizedSampleDate(for:completion:) to get the earliest date you may read for each type and pass it into your query. Apple calls limited authorization the only authorization state your app can positively identify, and warns against treating the absence of older samples as proof they do not exist.
Why do HealthKit writes fail on Vision Pro when my status check says authorized?
In a Guest User session, Apple documents that an app's permissions do not change, so authorizationStatus(for:) still reports the owner's grant while any attempt to save fails with errorNotPermissibleForGuestUserMode, or errorHealthDataRestricted on apps running in iOS 17. The authorization sheet is not displayed either, so requests during a guest session fail silently. Apple suggests ignoring the error for passive or periodic saves and only alerting when the guest took an action that obviously implies saving.

Keep reading

From the blog

Findings counted out of this site’s own datasets.

  • The HealthKit Error That Never FiresHealthKit defines 17 error cases. The authorization-denied one is documented for saving, not reading — which is why an empty read is so hard to debug.

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 August 11, 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 troubleshooting · by AIFitnessAPI