RejectProofRun the free check

Guideline 5.1.1 rejection: your app is missing a Privacy Manifest (PrivacyInfo.xcprivacy)

Missing or invalid PrivacyInfo.xcprivacy: what Apple checks, the five required-reason API categories, the SDK list, and the Expo and Xcode fix.

By RejectProof6 min read

A privacy manifest is a small property list named PrivacyInfo.xcprivacy that ships inside your app bundle and declares what data the app collects, which domains it uses for tracking, and why it calls a handful of APIs Apple considers fingerprinting risks. It is not the App Privacy questionnaire (the “nutrition label”) on App Store Connect: that is metadata; this is a file in the binary, checked mechanically at upload. If it is missing or inconsistent, the upload is refused with an ITMS email or, later, rejected by a reviewer under Guideline 5.1.1 (Data Collection and Storage).

The two dates that matter

  • May 1, 2024. Apple's documentation states: “Starting May 1, 2024, apps that don't describe their use of required reason API in their privacy manifest file aren't accepted by App Store Connect.” The email for this is ITMS-91053: Missing API declaration.
  • February 12, 2025. From that date, a new app that includes one of the SDKs on Apple's third-party SDK list, or an update that adds one, must ship that SDK with its own privacy manifest (and a signature when it is a binary dependency). The email for this is ITMS-91061: Missing privacy manifest.

So “missing privacy manifest” is really two checks: one on your app's manifest, one on the frameworks embedded in it.

Check 1: your app's manifest and the required-reason APIs

Apple maintains a short list of API categories that must be justified with an approved reason code. There are currently five:

  • NSPrivacyAccessedAPICategoryFileTimestampstat, fstat, lstat, getattrlist and friends; reading file creation/modification dates. Typical reason: C617.1 (files inside the app's own container).
  • NSPrivacyAccessedAPICategorySystemBootTimemach_absolute_time() and systemUptime. Typical reason: 35F9.1 (measuring elapsed time within the app).
  • NSPrivacyAccessedAPICategoryDiskSpacestatfs, statvfs and related calls. Typical reason: E174.1 (checking whether there is enough space to write).
  • NSPrivacyAccessedAPICategoryActiveKeyboardsactiveInputModes; almost only for custom keyboard apps.
  • NSPrivacyAccessedAPICategoryUserDefaultsUserDefaults / NSUserDefaults. Typical reason: CA92.1 (data only accessible to the app itself).

Nearly every real app hits at least UserDefaults and file timestamps — through your own code, the React Native or Flutter runtime, or a networking or image-caching library. Each category the binary uses must appear in NSPrivacyAccessedAPITypes with reason codes from Apple's list; the codes are fixed strings, do not invent one. A minimal app manifest:

PrivacyInfo.xcprivacy (app target)
<?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>NSPrivacyTrackingDomains</key>
  <array/>
  <key>NSPrivacyCollectedDataTypes</key>
  <array/>
  <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></array>
    </dict>
  </array>
</dict>
</plist>

Fill NSPrivacyCollectedDataTypes honestly if the app collects anything (email, user id, crash or usage data); it should match the nutrition label. If NSPrivacyTracking is true, NSPrivacyTrackingDomains must list the tracking domains, and requests to them fail until the user grants App Tracking Transparency permission.

Check 2: the SDKs embedded in your app

Apple's list of “commonly used” SDKs is longer than most people expect and is not limited to ad networks. It includes Alamofire, AFNetworking, the Firebase family (FirebaseCore, FirebaseAuth, FirebaseMessaging, FirebaseCrashlytics and others), the Facebook SDK (FBSDKCoreKit, FBSDKLoginKit), OneSignal, GoogleSignIn, SDWebImage, Kingfisher, Lottie, RealmSwift, RxSwift, Flutter and many Flutter plugins, Capacitor, Cordova, and — relevant to every React Native and Expo app — hermes. Any SDK that repackages a listed one counts too.

The manifest has to be inside the SDK, not merged into yours. Apple's guidance for ITMS-91061 is explicit: update to a version of the SDK that ships its own PrivacyInfo.xcprivacy, and do not copy its declarations into your app's manifest. For an iOS framework the file sits at the root of the .framework bundle; for the app, at the root of .app.

The fix in Xcode

  1. File → New → File, scroll to the Resource section, choose App Privacy, tick your app target. Xcode names it PrivacyInfo.xcprivacy and places it correctly.
  2. Add the required-reason categories your app uses. Product → Archive, then right-click the archive → Generate Privacy Report to see what Xcode found across all bundles.
  3. Update every listed SDK to a version that includes its own manifest (check the SDK's release notes; most major ones added it during 2024). Static .a libraries cannot carry resources, so a vendor that still ships one needs to move to a static or dynamic framework.

The fix in Expo (and Rork, a0, Bolt)

Expo generates the app manifest from app.json. Declare the categories under expo.ios.privacyManifests and rebuild with EAS:

app.json
{
  "expo": {
    "ios": {
      "privacyManifests": {
        "NSPrivacyAccessedAPITypes": [
          {
            "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryUserDefaults",
            "NSPrivacyAccessedAPITypeReasons": ["CA92.1"]
          },
          {
            "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryFileTimestamp",
            "NSPrivacyAccessedAPITypeReasons": ["C617.1"]
          }
        ]
      }
    }
  }
}

Two notes from the Expo privacy guide: keep Expo modules current with npx expo install --fix, because their manifests live inside the packages; and Apple does not correctly parse every manifest shipped by static CocoaPods dependencies, so you may need to add those libraries' reasons to your own manifest.

How to tell which check you failed

  • ITMS-91053 names the API category and the file path (YourApp.app/YourApp or Frameworks/X.framework/X). Fix the manifest of whichever bundle is named.
  • ITMS-91061 names the SDK. Update it; do not patch around it.
  • An email about an invalid manifest with unexpected keys or values means a manifest exists but contains something Apple does not recognise — usually a typo in a category or reason code, or a hand-edited file with extra keys.
  • A human reviewer citing 5.1.1 after a successful upload usually means the declarations do not match behaviour: the manifest says no tracking but the binary talks to an ad network, or the nutrition label and the manifest disagree.

All of this is visible in the .ipa before upload: whether PrivacyInfo.xcprivacy exists at the bundle root, which categories it declares, which required-reason symbols the executable references, and which embedded frameworks are on Apple's list but ship without a manifest. RejectProof's binary scan runs those comparisons in your browser and reports mismatches with the file and symbol as evidence.

← All articles