You archived a build, uploaded it to App Store Connect, and within a few minutes the ingestion email returned with ITMS-91053 and the phrase Missing API declaration sitting next to SystemBootTime. The previous release went through without a complaint, no first-party code asks for uptime, and the project compiles cleanly. The question is what App Store Connect actually saw inside the bundle, which symbols put SystemBootTime in front of you, and the smallest correction that lets the next upload finish processing.
Short answer
App Store Connect raised ITMS-91053 because the compiled binary references either systemUptime or mach_absolute_time, and no PrivacyInfo.xcprivacy in the upload declares a reason under NSPrivacyAccessedAPICategorySystemBootTime. The fix is one of three approved reason codes (35F9.1, 8FFB.1, or 3D61.1) 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
- Two API symbols populate the SystemBootTime category. The systemUptime property on NSProcessInfo and the BSD mach_absolute_time function each trigger the audit when present in the compiled bundle.
- Three approved reasons apply. 35F9.1 covers elapsed time inside the app, 8FFB.1 covers absolute timestamps for app events, and 3D61.1 is reserved for opt-in bug reports.
- The audit reads the Mach-O, not the source. App Store Connect scans the linked symbols inside the binary, which is why a clean grep across the repo can still fail the upload.
- Vendor SDKs are the common cause. FirebaseAnalytics, FirebaseCrashlytics, Sentry, and similar telemetry frameworks read boot time to measure session length and time crash events.
- Each target ships its own manifest. The app, every embedded framework, and every .appex extension is audited independently; a single manifest in the app target is a fallback, not a guarantee.
- The build number has to increment. Re-uploading the same number leaves the Invalid Binary in place, regardless of the manifest change.
Why does App Store Connect flag SystemBootTime in the first place?
The SystemBootTime category is one of four required reason API groups listed in Apple's privacy manifest specification, alongside file timestamp, disk space, and user defaults. Each group names a small set of APIs that can be used to fingerprint a device across reinstalls, since the boot time of an iPhone changes only at restart. App Store Connect ingestion compares the symbol table of the compiled binary against the published API list; if any symbol from the SystemBootTime group appears and no reason code is declared anywhere in the upload, the build is held with the status Invalid Binary and the email lists the SystemBootTime category by name.
The rejection often surprises developers because the audit operates one layer below the source code. systemUptime is a property on NSProcessInfo that many libraries read to log how long the app has been alive, and mach_absolute_time is a BSD call that any high-resolution timer reaches for. The symbol survives compilation whether the developer typed the call or an SDK author did. Apple's TN3183 technote describes the check as an API-level audit on the bundle, with no behavioural test attached, which is why the same code that built fine in April 2024 fails today.
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. Today the bundle stays in the Invalid Binary state until a fixed manifest ships under a new build number.
Which API calls populate the SystemBootTime category?
Two symbols carry the category. The first is NSProcessInfo.systemUptime, a Foundation property documented on the ProcessInfo systemUptime page. It returns the time interval since the device booted and is widely read by analytics SDKs to compute session windows. The second is mach_absolute_time, the BSD monotonic clock used by performance timers, profiling tools, and any code that needs sub-microsecond precision. Both symbols sit in dyld's symbol table once linked, so the App Store Connect audit can see them whether your code calls them directly or a framework calls them on your behalf.
Several higher-level APIs reach the same underlying clock. CACurrentMediaTime and DispatchTime.now both wrap mach_absolute_time on Apple platforms, which is why timing utilities, Bluetooth telemetry libraries, and Metal frame counters often pull the symbol into the bundle without an obvious mention in the host project. The Apple Required Reason API page lists the full set of audited APIs and the matching reason codes; the SystemBootTime block sits between the file timestamp and disk space groups.
How do I find the framework inside my IPA that triggers the audit?
Open the .ipa archive in Finder, choose Show Package Contents, and look inside the .app for a Frameworks directory. Each modern framework that touches a required reason API ships its own PrivacyInfo.xcprivacy at the bundle root; the frameworks missing the file are the candidates for the missing provider. A short shell pass confirms which framework ships the symbol:
xcrun nm -u YourApp.app/Frameworks/SomeSDK.framework/SomeSDK \
| grep -E "_systemUptime|_mach_absolute_time"
A non-empty result means the framework references one of the SystemBootTime APIs. Apple's adoption guide for privacy manifests describes the same inspection technique. A second signal comes from Apple's published list of SDKs commonly using required reason APIs. FirebaseAnalytics, FirebaseCrashlytics, FirebaseRemoteConfig, GoogleSignIn, Sentry, AppsFlyer, OneSignal, and Adjust all sit on that list, and any project pulling them in is a candidate for the SystemBootTime audit.
The Firebase community thread on system boot time in firebase-ios-sdk (issue 12557) is one example. The issue was opened in March 2024 against version 10.22.1, and subsequent releases shipped privacy manifests for FirebaseAnalytics and FirebaseCrashlytics so that host apps no longer carry the declaration on Firebase's behalf.
Which reason code matches my use case?
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, or drive timers | Boot time stays on device; elapsed-time intervals can be sent off-device |
| 8FFB.1 | Compute absolute timestamps for events that occurred inside your app | Absolute timestamps can be sent off-device; boot time itself cannot |
| 3D61.1 | Add boot time to an opt-in bug report shown to the user before submission | Off-device only with explicit user consent for bug investigation |
35F9.1 is the right answer for the typical analytics case. Counting how long a screen was open, measuring the time between two app events, or driving an interval timer all sit inside the app, so a single 35F9.1 entry covers them. The off-device clause matters: the elapsed interval can be sent to a server, but the raw boot time cannot.
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 sounds: the boot time is included in a bug report that the user reviews and sends voluntarily. Diagnostic logs that ship automatically with every crash do not fit 3D61.1, and a reason code that does not match the actual usage is something a human reviewer can later challenge.
App Store Connect does not police whether 35F9.1 was the most accurate choice when 8FFB.1 would have fit better. The ingestion audit only checks that one approved reason is present for the category. Picking the reason that actually matches the call is still the safer practice because the Privacy Nutrition Label drawn from declared data types is shown publicly, and a later reviewer can ask about a mismatch between what the manifest claims and what the binary does.
Should the manifest live in the SDK or in my app target?
The cleaner answer in most cases is to update the SDK. 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 SDKs followed that path after the May 2024 cutover, including FirebaseAnalytics, FirebaseCrashlytics, OneSignal, GoogleSignIn, AppsFlyer, and Sentry. The release notes usually call out the version that adds the manifest, and Apple's commonly used SDK list flags whether a binary signature is also required.
When the SDK has not shipped a manifest yet, App Store Connect accepts a declaration in the app target that covers calls made elsewhere in the binary. The ingestion audit only requires that one approved reason exists for the category somewhere in the upload. The trade-off is that the app maintainer owns the accuracy of that declaration as the SDK evolves; a 35F9.1 entry covering a vendor that later starts shipping absolute crash timestamps may need to grow to include 8FFB.1.
For builders who want an external automated scan across each 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 an Invalid Binary email and a clean ingestion.
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 or mach_absolute_time 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 SDK 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 SystemBootTime category covers two symbols, systemUptime and mach_absolute_time, and the audit fires whenever either one sits in the compiled bundle.
- 35F9.1 fits elapsed-time measurement, 8FFB.1 fits absolute event timestamps, and 3D61.1 is reserved for opt-in bug reports the user reviews before sending.
- Updating the SDK to a release that ships its own privacy manifest is the cleaner fix; an app-level entry is a fallback when the SDK lags.
- 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.



