You uploaded an iOS build to App Store Connect, the processing screen sat for a minute, and an email landed in your inbox titled ITMS-91053. The body of the email lists one of five API categories: file timestamp, system boot time, disk space, user defaults, or active keyboard. Apple is telling you that your app called a system function on the required reason API list without a matching reason code in your privacy manifest. This page is the full read of that list, the approved codes for each category, and what you write inside PrivacyInfo.xcprivacy to satisfy the upload check.
Short answer
The required reason API list contains five categories: file timestamp APIs, system boot time APIs, disk space APIs, user defaults APIs, and active keyboard APIs. Each category has a small set of approved reason codes Apple defines. Declarations sit inside the NSPrivacyAccessedAPITypes array of PrivacyInfo.xcprivacy, one dictionary per category, each carrying an NSPrivacyAccessedAPIType string and an NSPrivacyAccessedAPITypeReasons array. According to Apple's news announcement on privacy updates for App Store submissions, the upload check has been mandatory since May 1, 2024.
What you should know
- The list has five categories. File timestamp, system boot time, disk space, user defaults, and active keyboards. Apple describes the list as open, so new categories can be added, but none have been added since the original cut in February 2024.
- Each category needs at least one reason code. A reason code is a short alphanumeric string like
CA92.1. Each code corresponds to a permitted use case. You pick the codes that match the call sites your binary actually contains. - The check runs on upload, not at runtime. App Store Connect inspects your binary and
PrivacyInfo.xcprivacytogether. The rejection email is automated and arrives within minutes. - Third-party SDKs declare their own. Per the App Store privacy manifest documentation, each SDK on Apple's published list ships its own
PrivacyInfo.xcprivacyat the root of its bundle. - The ITMS-91053 email names the category. The body of the email contains the exact
NSPrivacyAccessedAPICategorystring the upload check could not match. That string tells you which dictionary is missing. - Many false positives come from CocoaPods. Per the Expo privacy manifest guide, Apple does not always parse static CocoaPods sub-manifests correctly, so codes from dependencies often have to be aggregated into the app's own manifest.
- The codes are not interchangeable. Picking the wrong reason can pass the upload check but still violate Apple's rules of use for the API, which surfaces in later review.
Why did Apple introduce the required reason API list?
The required reason list addresses a tracking technique called fingerprinting. Per the Apple privacy manifest files documentation, a handful of system APIs return values that vary enough across devices (for example, the timestamp of the last system boot, or the creation date of a long-lived system file) that, combined, they can reconstruct a stable identifier without any access to the Identifier for Advertisers. The list groups the APIs Apple's privacy team observed being used this way in practice.
The mechanism does not forbid the calls. It forces the developer to declare a non-tracking use case in advance. If the call sites match a declared reason, the build passes. If a call site exists with no declaration, the build is rejected at upload time. The check is enforced at upload rather than at runtime because the runtime cannot tell a fingerprinting use of, say, Date.modificationDate from a legitimate one.
Per Apple's news post from February 29, 2024, Apple began sending warning emails on March 13, 2024, and the upload block began on May 1, 2024. Apps that shipped before that date keep working, but any new build (including an unrelated bug fix to a live app) has to pass the check.
Which five API categories sit on the required reason list?
The five categories carry these exact identifier strings inside NSPrivacyAccessedAPIType. The identifier is the value you put in the privacy manifest dictionary; it is also the string the ITMS-91053 email returns.
| API category | Manifest identifier | What it covers |
|---|---|---|
| File timestamp | NSPrivacyAccessedAPICategoryFileTimestamp | Reading creationDate, modificationDate, attributesOfItem, file attribute methods on FileManager, and the stat() family. |
| System boot time | NSPrivacyAccessedAPICategorySystemBootTime | mach_absolute_time, systemUptime, clock_gettime(CLOCK_UPTIME_RAW), and similar uptime sources. |
| Disk space | NSPrivacyAccessedAPICategoryDiskSpace | volumeAvailableCapacityKey, statfs, and any volume free-space query. |
| User defaults | NSPrivacyAccessedAPICategoryUserDefaults | Any read or write through UserDefaults, NSUserDefaults, or CFPreferences. |
| Active keyboards | NSPrivacyAccessedAPICategoryActiveKeyboards | UITextInputMode.activeInputModes and any call returning the user's installed keyboards. |
Per Apple's documentation on required reason API entries, the list is described as open. Apple can add categories in the future. Since the cut on February 29, 2024, no additional category has been added.
What are the approved reason codes for each category?
Each category carries a short list of reason codes. You pick the code that matches your call site. Picking more than one is allowed and expected when a single category covers several distinct use cases inside the app. The codes most builds end up using are below.
| Category | Reason code | Permitted use |
|---|---|---|
| File timestamp | DDA9.1 | Display file timestamps to the device user. Information accessed for this reason cannot leave the device. |
| File timestamp | C617.1 | Inspect file metadata inside the app's container, app group container, or CloudKit container. |
| File timestamp | 3B52.1 | Use timestamps for file access policy or caching logic, on the device only. |
| File timestamp | 0A2A.1 | Vendor declaration: a bundled third-party SDK uses the value internally and does not return it to your app process. |
| System boot time | 35F9.1 | Calculate intervals between user events on the device. |
| System boot time | 8FFB.1 | Read uptime inside an SDK that returns the value only to the app process. |
| System boot time | 3D61.1 | Compute event timing for on-device analytics that does not leave the device. |
| Disk space | 85F4.1 | Write content to disk only if enough space is available. |
| Disk space | E174.1 | Display disk space to the user. |
| Disk space | 7D9E.1 | Detect a low-storage condition before a large download. |
| Disk space | B728.1 | Verify cache eviction thresholds inside the app. |
| User defaults | CA92.1 | Read or write defaults that belong to the app only, no App Group involved. |
| User defaults | 1C8F.1 | Read or write defaults shared via an App Group with apps the same team controls. |
| User defaults | C56D.1 | Read defaults the system makes available globally. |
| User defaults | AC6B.1 | Read or write defaults for managed configuration in MDM-deployed builds. |
| Active keyboards | 3EC4.1 | Provide a custom keyboard inside the app. |
| Active keyboards | 54BD.1 | Adjust UI based on the user's active keyboard, on the device only. |
The canonical list lives on Apple's developer site under "Describing use of required reason API". Each code is paired with a usage statement that constrains what the data can be used for after it is read. Reading the statement matters: picking DDA9.1 (display to the user) and then sending the timestamp to an analytics endpoint violates the declared use even though the upload check accepts it. App Review can flag the mismatch later.
How do you declare each entry inside PrivacyInfo.xcprivacy?
The file is a property list at the root of the app bundle: MyApp.app/PrivacyInfo.xcprivacy. The structure for the required reason section is a single NSPrivacyAccessedAPITypes key whose value is an array of dictionaries, one dictionary per category.
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
<string>DDA9.1</string>
</array>
</dict>
</array>
For builds produced by Expo Application Services, the equivalent lives in app.json under expo.ios.privacyManifests. Per the Expo privacy manifest guide, the build pipeline emits the property list from that configuration during prebuild. For Capacitor projects, the file ships inside ios/App/App/PrivacyInfo.xcprivacy and Capacitor's project template includes a starter version. For a vanilla Xcode project, add the file through File, New, File, Privacy, and add the resulting file to the target's Copy Bundle Resources phase.
Reason codes do not need to be listed in any particular order. Duplicates are accepted but ignored. The build check looks for at least one valid code per category your binary calls into.
What if a third-party SDK does not include its own manifest?
This is the most common failure mode at upload time. The category named in the ITMS-91053 email is being called by an SDK, not by code the developer wrote. Per Apple's third-party SDK requirements page, each SDK on Apple's published list (Firebase, Reachability.swift, Alamofire, OneSignal, and many others) is required to ship its own PrivacyInfo.xcprivacy at the root of its binary. SDKs added through Swift Package Manager and dynamic frameworks usually parse correctly. Static CocoaPods are the case where Apple does not always pick up the sub-manifest.
The recovery is to add the SDK's declared reason codes into the app's own privacy manifest. Open the SDK's manifest inside node_modules/<package>/ios/PrivacyInfo.xcprivacy or inside the framework bundle, copy the dictionaries, and merge them into the app's NSPrivacyAccessedAPITypes array. The upload check is forgiving about duplication: aggregating codes from multiple sources into the app manifest is the recommended path.
For developers shipping builds from no-code or AI-coded toolchains, the same logic applies. The compiled IPA contains whatever SDKs the builder injects, and the privacy manifest the builder generates may not enumerate every call site those SDKs use. PTKD.com (https://ptkd.com) is one of the platforms that scan an uploaded IPA against Apple's required reason list and report missing categories before the build reaches App Store Connect, which is useful for builders (FlutterFlow, Bubble, Capacitor wrappers around Replit Agent or Lovable exports) that do not surface their bundled SDKs in a readable way.
What to watch out for
Two patterns cause repeat rejections after a developer thinks the manifest is fixed.
The first is reason-code drift. A developer adds CA92.1 for user defaults, ships, then later adds an App Group for shared widgets. The new call site needs 1C8F.1. The build passes locally but the upload check now sees a call without a matching reason. The fix is to read each code's usage statement on the canonical Apple page when you add a feature, not when you first set up the file.
The second is sub-manifest aggregation. After a Pod update, an SDK adds a new call site under an existing category. The SDK's own manifest is updated, but if the build uses static CocoaPods, Apple's parser may not see the new code. The upload fails with the same category name as before. Per the Expo privacy manifest guide, the recommendation is to aggregate codes upward into the app manifest rather than rely on SDK manifests being parsed transitively.
Two myths to reject. The required reason list does not extend to data collection types like location, contacts, or photos. Those sit in a separate key, NSPrivacyCollectedDataTypes, with their own rules. And declaring a category in the manifest does not exempt the call from App Tracking Transparency: if the data participates in cross-app tracking, ATT consent still applies separately.
Key takeaways
- The required reason list is five categories: file timestamp, system boot time, disk space, user defaults, active keyboards. Each category has between three and four approved reason codes you select from.
- The check runs at App Store Connect upload, not at runtime. The rejection email (ITMS-91053) names the category. Match it to a dictionary inside
NSPrivacyAccessedAPITypes, re-upload, and the check passes. - Pick the reason code by reading its usage statement on Apple's documentation page, not by guessing. Picking the wrong code passes the upload check but breaks Apple's rules of use, which surfaces later in App Review.
- For builds where the developer cannot inspect every SDK source (FlutterFlow, Bubble, Capacitor wrappers around AI-coded outputs), aggregating codes upward into the app manifest is the path that survives Pod and SDK updates.
- For an automated read of which categories a compiled IPA actually calls before submission, PTKD.com (https://ptkd.com) is one of the platforms focused on pre-submission scanning aligned with OWASP MASVS, useful when an SDK list is not transparent in the builder output.




