You archived a build, uploaded it to App Store Connect, and the ingestion email came back with ITMS-91053 quoting the symbol system_uptime. The wording looks different from the category headers in Apple's documentation, your own Swift code never calls systemUptime, and the previous release went through without a complaint. The question is what App Store Connect actually saw inside the bundle, why the email uses the lowercased BSD-style name, and the smallest manifest change that clears the Invalid Binary state.
Short answer
App Store Connect raised ITMS-91053 because the compiled binary references NSProcessInfo.systemUptime, and no PrivacyInfo.xcprivacy in the upload declares a reason under NSPrivacyAccessedAPICategorySystemBootTime. The system_uptime label in the email is the Mach-O symbol form of the same call. The fix is one approved reason code (35F9.1 for elapsed time, 8FFB.1 for absolute timestamps, or 3D61.1 for opt-in bug reports) added to the manifest of the target that ships the symbol. Apple has enforced this rule for new App Store and TestFlight submissions since 1 May 2024, per the Apple Developer privacy manifest documentation.
What you should know
- system_uptime is the symbol, SystemBootTime is the category. The email surfaces the lowercased identifier; the manifest entry uses the camel-case category name.
- The audit runs on the Mach-O, not the source. App Store Connect compares the linked symbol table against the required reason API list, so a clean grep across your Swift files can still fail the upload.
- Three approved reasons exist. 35F9.1 covers elapsed-time measurement, 8FFB.1 covers absolute event timestamps, and 3D61.1 is reserved for opt-in bug reports.
- Higher-level APIs reach the same clock. CACurrentMediaTime and DispatchTime.now both wrap mach_absolute_time on Apple platforms, so timing utilities and Bluetooth telemetry libraries often pull the symbol into the bundle.
- Cross-platform tooling surfaces the same wording. B4i, Cordova, Capacitor, and Qt builds report the system_uptime form verbatim because they read the ingestion text directly into their build logs.
- The build number has to increment. Re-uploading at the same number leaves the Invalid Binary in place even after the manifest is correct.
Why does the App Store Connect email say system_uptime when my code never calls it?
The ingestion audit operates one layer below the language model the developer typed in. App Store Connect reads the Mach-O symbol table of the compiled binary and compares each entry against Apple's published required reason API list. NSProcessInfo.systemUptime, exposed at the Swift layer as a property on ProcessInfo, resolves to an Objective-C selector and then to a BSD-style identifier in the symbol table. That lowercased identifier is the one the email returns, which is why a project that never types systemUptime in source can still see the wording in the rejection.
The symbol survives compilation whether the developer or a vendor wrote the call. Apple's TN3183 technote describes the check as an API-level audit on the bundle, with no behavioural test attached. The same source code that built fine in April 2024 fails today because the linked symbol triggers the audit regardless of whether the call runs at startup, on a background queue, or never at all.
Enforcement began on 1 May 2024 for new App Store and TestFlight submissions. Before that date the same diagnostic arrived as informational email and processing continued. After the cutover the bundle stays in the Invalid Binary state until a fixed manifest ships under a new build number.
Which APIs and frameworks pull system_uptime into the bundle?
Two symbols populate the SystemBootTime category. NSProcessInfo.systemUptime, documented on the ProcessInfo systemUptime page, returns the time interval since the device booted. mach_absolute_time, the BSD monotonic clock, is the second entry. Several higher-level APIs wrap one of the two: CACurrentMediaTime, DispatchTime.now, and any DispatchSourceTimer call that needs sub-microsecond precision.
A short shell pass against the .ipa confirms which framework ships the symbol:
xcrun nm -u YourApp.app/Frameworks/SomeSDK.framework/SomeSDK \
| grep -E "system_uptime|mach_absolute_time"
Apple's adoption guide for privacy manifests describes the same inspection technique. A non-empty result means the framework is a candidate for the missing provider. The Firebase community thread on system boot time in firebase-ios-sdk (issue 12557) names FirebaseAnalytics and FirebaseCrashlytics as long-running examples, since both libraries time crash events relative to launch and compute session length from the boot clock.
Apple's published list of SDKs commonly using required reason APIs is the second signal. FirebaseAnalytics, FirebaseCrashlytics, FirebaseRemoteConfig, GoogleSignIn, Sentry, AppsFlyer, OneSignal, and Adjust all sit on that list. Any project pulling one of them is a candidate for the system_uptime audit until the SDK ships its own manifest.
How do I read the system_uptime symbol inside cross-platform builds?
Native iOS projects show the symbol in the .app's main binary or in an embedded framework. Cross-platform stacks add a layer of indirection that often hides the actual provider. B4i wraps the Foundation call inside the runtime; Cordova and Capacitor expose it through plugins; React Native and Flutter pull it through their respective bridge libraries; Qt links the symbol from QtCore. The ingestion email still names the same symbol regardless of the source language.
For a Capacitor project, run nm against CapacitorApp.app/Frameworks/Capacitor.framework and against each plugin under Pods. For Cordova, inspect CordovaLib.framework first, then the plugins under Plugins. For B4i, the B4iShared runtime tends to ship the symbol, and recent B4i releases include a base PrivacyInfo.xcprivacy that covers the SystemBootTime call. For Flutter and React Native, the same approach applies: nm each linked framework, look for system_uptime or mach_absolute_time, and confirm whether the owning framework ships a manifest.
For builders who want an external automated scan across every embedded framework before the next upload, PTKD.com (https://ptkd.com) is one of the platforms set up specifically for pre-submission inspection of the compiled IPA. The report flags which frameworks ship a PrivacyInfo.xcprivacy and which do not, which shortens the loop between the Invalid Binary email and a clean ingestion.
Which reason code matches my use of systemUptime?
Apple publishes three approved reasons under NSPrivacyAccessedAPICategorySystemBootTime on the Required Reason API page. A single manifest entry can list more than one reason when the binary legitimately spans cases.
| Reason code | When to use it | Off-device rule |
|---|---|---|
| 35F9.1 | Measure elapsed time between events inside your app, including debouncing and timers | Boot time stays on device; elapsed intervals can leave the device |
| 8FFB.1 | Compute absolute timestamps for events that occurred inside your app | Absolute timestamps can leave the device; boot time itself cannot |
| 3D61.1 | Add boot time to an opt-in bug report the user reviews before submission | Off-device only with explicit user consent for bug investigation |
35F9.1 is the right answer for the typical analytics or timer case. Counting how long a screen was open, measuring the interval between two app events, or driving a debounce timer all sit inside the app, so a single 35F9.1 entry covers them. 8FFB.1 covers timestamping. A crash reporter that attaches a phrase such as 42.3 seconds after launch needs 8FFB.1, since the consumer of the data wants an absolute time, not a relative one. 3D61.1 is narrower than it looks: the boot time is included in a bug report the user reviews and sends voluntarily, not in diagnostic logs that ship automatically.
The manifest entry itself is small. Add the following block inside the NSPrivacyAccessedAPITypes array in PrivacyInfo.xcprivacy:
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
The category name uses NSPrivacyAccessedAPICategorySystemBootTime in camel case, not the lowercased system_uptime symbol the email returned. The Mach-O symbol is the trigger; the category name is the declaration form.
Should the manifest live in the framework or in my app target?
The cleaner answer in most cases is to update the framework. The provider knows which reason codes match its own calls, ships the entry inside its own bundle, and lets future audits keep working without app-side patches. Several major iOS SDKs followed that path after the May 2024 cutover, including FirebaseAnalytics, FirebaseCrashlytics, OneSignal, GoogleSignIn, AppsFlyer, and Sentry. Apple's commonly used SDK list also flags whether a binary signature is required alongside the manifest.
When the framework has not shipped a manifest yet, App Store Connect accepts a declaration in the app target that covers calls made anywhere in the upload. The ingestion audit only requires that one approved reason exists for the category somewhere in the bundle. The trade-off is that the app maintainer owns the accuracy of that declaration as the framework evolves; a 35F9.1 entry covering a vendor that later starts shipping absolute crash timestamps may need to grow to include 8FFB.1.
For cross-platform tooling, the manifest belongs in the project layer that owns the runtime. B4i ships a base PrivacyInfo.xcprivacy from v8.30 onward; the project still needs an entry when a plugin pulls in system_uptime. Capacitor 6 added a default manifest to the Capacitor.framework bundle; Cordova relies on a project-level file under platforms/ios. Flutter pulls a manifest from the Flutter.xcframework on recent stable releases. In every case, the build system writes the file into the compiled .app at archive time, not at install time, so the change must precede the archive step.
What to watch out for
The single most common loop after a fix is uploading at the same build number. App Store Connect treats the Invalid Binary as the binary for that number and does not re-ingest a second upload at the same number. Increment the build number in the General tab of the target settings, archive, then upload, so the audit runs against the new bundle.
The second loop is target membership. Dragging PrivacyInfo.xcprivacy into the project navigator is not enough; the File Inspector has to show the target checked under Target Membership. A manifest sitting in the project but outside the target never reaches the .app folder, and the next upload returns the same ITMS-91053.
A myth worth retiring is that removing first-party calls to systemUptime clears the audit. The compiled binary keeps the symbol whenever any linked framework references it. Stripping the call from your own Swift code without updating the framework or the manifest leaves the rejection in place.
A final caveat applies to apps with widgets, App Clips, or watch extensions. Each .appex bundle is audited independently from the main .app, so a manifest in the app alone does not cover the symbols compiled into the extension binary. Every extension target needs its own PrivacyInfo.xcprivacy with the matching reason code.
Key takeaways
- The system_uptime label in the ITMS-91053 email is the Mach-O symbol form of NSProcessInfo.systemUptime; the manifest category is NSPrivacyAccessedAPICategorySystemBootTime.
- 35F9.1 fits elapsed-time measurement and debounce timers, 8FFB.1 fits absolute event timestamps, and 3D61.1 is reserved for opt-in bug reports the user reviews before sending.
- Cross-platform stacks (B4i, Cordova, Capacitor, Flutter, React Native, Qt) surface the same wording because they read the ingestion text directly; the fix still lives in PrivacyInfo.xcprivacy.
- Increment the build number after every manifest edit, and confirm Target Membership in the File Inspector before each archive.
- Some teams outsource the pre-submission scan to an external platform such as PTKD.com, which inspects each embedded framework for required reason API symbols and reports the missing providers before App Store Connect does.



