App Store Compliance
Resolving ITMS-91053: a complete guide to PrivacyInfo.xcprivacy for iOS
App Store Review rejected your build with ITMS-91053? Here's the systematic walkthrough — what the error actually means, how to find which Required Reason APIs your binary uses, and the exact xcprivacy declarations that resolve it.
You uploaded a build to App Store Connect. The processing-complete email arrives. You open it expecting a green check, and instead you see:
ITMS-91053: Missing API declaration — Your app’s code in the "YourApp" file references one or more APIs that require reasons, including the following API categories: NSPrivacyAccessedAPICategoryUserDefaults. While the API(s) are recognized, no API reasons have been provided.
You haven’t shipped this build. It’s stuck in App Store Connect with a blocker. Submission window slipping by the hour.
This is the systematic guide for getting unstuck — what ITMS-91053 actually means, how to identify which APIs in your binary trigger it, and the precise PrivacyInfo.xcprivacy declarations to ship.
What ITMS-91053 actually checks
Since iOS 17 / macOS 14, Apple requires every iOS app bundle to include a PrivacyInfo.xcprivacy file declaring which “Required Reason API” categories the app uses. There are exactly five categories:
NSPrivacyAccessedAPICategoryUserDefaults—NSUserDefaults/UserDefaultsreads and writesNSPrivacyAccessedAPICategoryFileTimestamp— file modification, creation, attribute timestampsNSPrivacyAccessedAPICategorySystemBootTime—systemUptimeand related boot-time APIsNSPrivacyAccessedAPICategoryDiskSpace— available / total disk space queriesNSPrivacyAccessedAPICategoryActiveKeyboards— current input method / keyboard list
When you submit a build, Apple’s static analyzer scans your bundled binary for symbols from those five categories. For every category it finds, it checks whether your manifest declares that category with a valid reason code. If not — ITMS-91053.
The related error is ITMS-91055: Invalid API reason declaration — same analyzer, same scan, but the manifest declared a reason code that’s invalid for the category. (Each category has its own set of reason codes; using a UserDefaults reason on a FileTimestamp declaration is a common mistake.)
Why this catches indie developers off guard
The static analyzer runs at submission time. You can’t catch the rejection in TestFlight, in your CI, or with xcodebuild. You only find out after upload, after the queue, when the email arrives. Indie developers regularly lose 1–2 days to this loop.
There’s no warning in Xcode. There’s no compiler error. Your build runs perfectly. The first time you find out is mid-release.
Step 1: Identify which APIs in your binary trigger the rejection
The rejection email lists the categories. Read it carefully. The example above mentions NSPrivacyAccessedAPICategoryUserDefaults — that’s your starting point.
To find where in your code (or in a third-party SDK) the API is used, run nm over your archived binary:
# After archiving, find your .app inside the archive
APP="/path/to/Archives/YYYY-MM-DD/YourApp.xcarchive/Products/Applications/YourApp.app/YourApp"
nm "$APP" | grep -E "(NSUserDefaults|UserDefaults\.standard|attributesOfItem|systemUptime|fileSystemAttributes|activeInputModes)"
You’ll see symbols like _OBJC_CLASS_$_NSUserDefaults or method names that confirm the API is in your binary. Sometimes the call is from your own code (easy to fix); sometimes it’s from a third-party SDK (need to check the SDK’s privacy manifest).
For SDKs specifically, check your Podfile.lock or Package.swift against the SDK vendor’s release notes. Most major SDKs have shipped privacy manifests since 2024 — Firebase, Sentry, Stripe, Mixpanel, RevenueCat, Adjust, Branch, etc. If you’re on an old SDK version, upgrading to the latest is usually the fix.
Step 2: Pick the right reason codes
Each category has its own Apple-defined set of reason codes. You declare which reasons apply.
NSPrivacyAccessedAPICategoryUserDefaults
| Code | Use this when… |
|---|---|
CA92.1 |
You read/write UserDefaults.standard for app-only data. Almost every app. |
1C8F.1 |
You read/write UserDefaults(suiteName:) for an App Group shared with other apps you control. |
C56D.1 |
You’re a third-party SDK wrapping UserDefaults calls — only for SDK authors, not host apps. |
If your app calls UserDefaults.standard.set(_:forKey:) anywhere — and almost every app does — you need CA92.1.
NSPrivacyAccessedAPICategoryFileTimestamp
| Code | Use this when… |
|---|---|
DDA9.1 |
You display a file’s modification date in your UI. The data cannot be sent off-device. |
C617.1 |
You read file timestamps for files inside your app container, app group, or app’s CloudKit container. Most common indie use. |
3B52.1 |
You read timestamps of files the user explicitly granted access to (document picker, file importer). |
0A2A.1 |
SDK wrapping file-timestamp APIs — for SDK authors only. |
If your app reads attributesOfItem(atPath:) or Date properties on URLResourceValues for app-managed files, you need C617.1.
NSPrivacyAccessedAPICategorySystemBootTime
| Code | Use this when… |
|---|---|
35F9.1 |
You measure elapsed time between events using boot time. Cannot be sent off-device. |
8FFB.1 |
You include boot time in optional bug reports the user explicitly submits. |
3D61.1 |
You use boot time for non-uptime calculations (e.g., feature timers). |
NSPrivacyAccessedAPICategoryDiskSpace
| Code | Use this when… |
|---|---|
85F4.1 |
You display available disk space in your UI. |
E174.1 |
You verify sufficient disk space before writing files. Common in download-heavy apps. |
7D9E.1 |
You include disk-space info in optional bug reports the user explicitly submits. |
B728.1 |
Health-research apps detecting low disk space affecting health-data collection. |
NSPrivacyAccessedAPICategoryActiveKeyboards
| Code | Use this when… |
|---|---|
3EC4.1 |
Your app is a custom keyboard. |
54BD.1 |
You customize UI based on user’s active keyboards (e.g., RTL/LTR layout switching). |
Step 3: Build the manifest file
Create a file named PrivacyInfo.xcprivacy (no extension on the inner part — Xcode adds .xcprivacy automatically). The structure for a typical iOS app:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypes</key>
<array>
<!-- one entry per data type your app collects -->
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<!-- UserDefaults, almost every app needs this -->
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<!-- File timestamps, if you read file metadata -->
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>
Get the NSPrivacyCollectedDataTypes declarations from your privacy nutrition label answers (App Store Connect’s App Privacy section). Each data type entry needs four keys: NSPrivacyCollectedDataType, NSPrivacyCollectedDataTypeLinked, NSPrivacyCollectedDataTypeTracking, NSPrivacyCollectedDataTypePurposes.
Apple’s TN3184 lists every valid data type and purpose constant. There are 33 data types and 6 purposes — the wrong constant produces ITMS-91055 rejections at submission.
Step 4: Add the manifest to your Xcode project
- Drag
PrivacyInfo.xcprivacyinto your Xcode project navigator. - In the Add to targets sheet, ensure the file is added to your app target. Add to additional targets (Watch app, App Clip, extensions) independently — each target needs its own manifest if it uses any required-reason API.
- Verify the file is in Build Phases → Copy Bundle Resources under your target.
- Build a fresh archive and re-submit.
Step 5: Verify before re-submitting
Before uploading the new build, double-check the manifest is in the bundle:
# After archiving
ARCHIVE="/path/to/Archives/YYYY-MM-DD/YourApp.xcarchive"
unzip -l "$ARCHIVE/Products/Applications/YourApp.app" | grep PrivacyInfo
You should see PrivacyInfo.xcprivacy listed at the root of the .app. If you don’t, the file isn’t in the Copy Bundle Resources phase — go back to step 4.
You can also validate the file syntactically before submitting:
plutil -lint PrivacyInfo.xcprivacy
plutil only checks XML syntax, not Apple’s enum values — so it’ll happily approve a manifest with bogus reason codes. For semantic validation against Apple’s allowlists, use a tool like OrbitKit (which validates at save time).
The fix that doesn’t work: declaring everything
A common temptation when stuck: declare every category with every reason code, hoping that “more declarations” makes the rejection go away. This produces ITMS-91055 (invalid declarations) instead of ITMS-91053 (missing). Apple checks that:
- Every category your binary uses is declared.
- Every category you declare is one of the five valid categories.
- Every reason code you declare is valid for its category.
Over-declaration with mismatched codes flips you from one error to another. Be precise.
Where third-party SDKs fit in
If you ship an SDK or include one that’s been updated for privacy manifests, the SDK has its own PrivacyInfo.xcprivacy inside its framework bundle. Apple aggregates the host app’s manifest with all SDK manifests for the static analysis. So:
- Your manifest declares what your code does.
- Each SDK’s manifest declares what that SDK’s code does.
- They don’t overlap; together they cover the whole binary.
If your bundle still gets ITMS-91053 after you’ve declared everything from your own code, an old SDK version is the most likely culprit. Update SDKs before adding fake declarations to cover their gaps.
Where OrbitKit fits in
OrbitKit’s Privacy Manifest Generator renders the file from a structured config:
- Pre-populates
NSPrivacyCollectedDataTypesfrom your privacy wizard answers — no double-entry. - Validates every reason code against its category at save time, so you can’t ship
ITMS-91055-bait by accident. - Cross-checks against the privacy nutrition label so the two stay consistent (App Review flags drift).
- Direct download endpoint at
/api/apps/:appId/privacy-manifest.xcprivacyyou can wire into a CI build script via the OrbitKit CLI.
If you’ve been hand-editing manifests and tired of the round-trip, give it a try — first app is free.