ITMS-91053 — Missing API declaration
ITMS-91053 means: code in your app uses a required reason API, and your privacy manifest does not declare an approved reason for it. The email from App Store Connect names the file and the category, in roughly this shape:
Your app's code in the "AppName" file references one or more APIs that require reasons, including the following API categories: NSPrivacyAccessedAPICategorySystemBootTime.
Since 1 May 2024 this is not advisory. Apple's documentation states that apps which do not describe their use of required reason API in their privacy manifest file "aren't accepted by App Store Connect."
The React Native and Expo version of this problem is that you almost certainly did not write the offending call. mach_absolute_time(), stat, fstat and UserDefaults live inside native modules, inside the JavaScript engine, and inside transitive CocoaPods you never chose. Apple's rule is per-bundle, so your app's manifest does not cover them.
Step 1 — read the email properly
Two pieces of information are in it and both matter.
The file. If it names your app's main binary, the usage is being attributed to code compiled into your app target — which for an RN app includes statically linked pods. If it names a framework inside Frameworks/, the responsible bundle is that framework and the fix is upstream.
The category. There are exactly five, and each has its own set of approved reason codes:
| Category | Common RN/Expo source |
|---|---|
NSPrivacyAccessedAPICategoryUserDefaults | AsyncStorage, settings/persistence libraries, most analytics SDKs |
NSPrivacyAccessedAPICategorySystemBootTime | mach_absolute_time() — animation, performance instrumentation, the JS engine |
NSPrivacyAccessedAPICategoryFileTimestamp | stat/getattrlist — image caches, SQLite wrappers, file pickers, crash reporters |
NSPrivacyAccessedAPICategoryDiskSpace | statfs/volumeAvailableCapacityKey — download managers, caching layers |
NSPrivacyAccessedAPICategoryActiveKeyboards | activeInputModes — keyboard managers such as IQKeyboardManager |
Step 2 — find which bundle is responsible
Start with what already ships a manifest:
find node_modules -name "PrivacyInfo.xcprivacy" | sed 's|node_modules/||' | cut -d/ -f1 | sort -uThen check the same for the built app, because this is where static linking hides things:
find "$(xcodebuild -showBuildSettings 2>/dev/null | awk -F'= ' '/BUILT_PRODUCTS_DIR/{print $2}')" \
-name "PrivacyInfo.xcprivacy" 2>/dev/nullAnd cross-reference against Apple's published SDK list. Any package on that list must ship a manifest and a signature. The RN/Expo-relevant subset of Apple's list.
The one nobody expects: hermes. React Native's default JavaScript engine is on Apple's required-SDK list. That means the requirement touches essentially every React Native app, whether or not it uses Firebase, Facebook or anything else.
Step 3 — fix it in the right place
There are only three legitimate fixes, and choosing the wrong one produces a rejection that looks fixed.
If the usage is in a third-party dependency: update the package. Most maintained libraries added PrivacyInfo.xcprivacy during 2024. If the package is unmaintained and has not, your options are to fork it and add the manifest, replace it, or remove it. You cannot declare on its behalf — Apple states a third-party SDK "can't rely on the privacy manifest files for apps that link the third-party SDK."
If the usage is genuinely in your own app code: add the category to your app's PrivacyInfo.xcprivacy with an accurate reason.
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>If you are in Expo with CNG: the manifest is generated, so hand-editing ios/ is overwritten on the next prebuild. Configure it through app.json/app.config.js or a config plugin so the value survives.
Step 4 — pick the reason code accurately, not defensively
The most common mistake after the first rejection is declaring every code in the category. That is a misdeclaration. Apple's wording is that you must declare reasons that "accurately reflect your use," and that you "may use these APIs and the data derived from their use for the declared reasons only."
Several codes bind you to conditions once declared:
DDA9.1,85F4.1,35F9.1,3EC4.1,54BD.1andCA92.1all carry "may not be sent off-device" language in some form.E174.1requires the app to behave observably differently based on disk space.3EC4.1requires that a systemwide custom keyboard is the app's primary functionality.0A2A.1andC56D.1may only be declared by third-party SDKs.
The normal, accurate declarations for an ordinary React Native app are CA92.1 for UserDefaults, C617.1 for file timestamps in your own container, and 35F9.1 for boot time used to measure in-app elapsed time.
The complete category and reason-code reference, transcribed from Apple's rendered docs.
Why this one costs so much calendar time
An ITMS-91053 email arrives after an upload, not after a review. So the loop is: build, upload, wait, read the email, guess at which of several hundred packages is responsible, change something, build again. Each iteration is a full EAS build and upload. Two or three iterations is a week gone, and the feedback contains a category name but not a package name.
This is precisely the class of problem shipcheck exists for: it reads node_modules, the built products directory, and every PrivacyInfo.xcprivacy in the tree before you build, and reports which bundle is missing which declaration, with the clause behind it. What shipcheck is and how to run it.
Related codes
| Code | Meaning |
|---|---|
ITMS-91053 | Missing API declaration — a required reason API is used and no reason is declared. |
ITMS-91054 | Invalid API category declaration — the NSPrivacyAccessedAPIType value is not one of the five valid strings. |
ITMS-91055 | Invalid API reason declaration — the reason code is not valid for that category. |
ITMS-91056 | Invalid privacy manifest — the PrivacyInfo.xcprivacy file at a given path could not be parsed or is malformed. |
ITMS-91061 | Missing privacy manifest — a framework on Apple's required-SDK list has no manifest at all. |
Questions and answers
What does ITMS-91053 mean?
It means App Store Connect detected that code in your app uses one or more required reason APIs and your privacy manifest does not declare an approved reason for that API category. The email names the file and the specific category, for example NSPrivacyAccessedAPICategorySystemBootTime.
Is ITMS-91053 a warning or a rejection?
Since May 1, 2024 it blocks the upload rather than merely warning. Apple's own documentation states that apps which do not describe their use of required reason API in their privacy manifest file are not accepted by App Store Connect.
Why do I get ITMS-91053 when I never call those APIs?
Because a dependency does. Apple's requirement is per-bundle - each executable or dynamic library that uses a required reason API needs a privacy manifest in its own bundle. The calls come from native modules, the JavaScript engine, or transitive pods you never chose directly.
How do I fix ITMS-91053 in an Expo project?
Read the category named in the email, identify which bundle the email names, and either update the dependency to a version that ships its own PrivacyInfo.xcprivacy or, if the usage is in your own app code, add the correct NSPrivacyAccessedAPITypes entry with an accurate approved reason to your app's manifest.
Which reason code should I use for ITMS-91053 on UserDefaults?
CA92.1 if the information is accessible only to your app, which is the normal case for an app storing its own settings. Use 1C8F.1 only if you genuinely share data with an extension or App Clip in the same App Group. Do not declare C56D.1 - it is reserved for third-party SDKs wrapping the API.
shipcheck. “ITMS-91053 — Missing API declaration.” Baker Ventures LLC, September 6, 2026. https://shipcheck.bakerventuresstudio.com/rejections/itms-91053-missing-api-declaration/