Few things are as frustrating as a rejected App Store submission, especially when the rejection triggers are external dependencies. If you are integrating the Start.io (formerly StartApp) SDK into an iOS application targeting iOS 17 or later, you have likely encountered ITMS-91053 or ITMS-91056.
These warnings indicate missing or incorrect privacy manifest signatures. Since Spring 2024, Apple requires specific third-party SDKs—particularly those involved in advertising and analytics—to include a PrivacyInfo.xcprivacy file. This file must explicitly declare the data types collected and the specific "Required Reason APIs" accessed by the code.
This guide details the root cause of these validation errors within the context of the Start.io SDK and provides a rigorous, code-level solution to ensure your binary passes static analysis.
Root Cause Analysis: The Supply Chain Shift
To fix this efficiently, you must understand the mechanism behind the rejection. Apple no longer trusts developers to simply self-report privacy practices via App Store Connect. They have shifted to a "Supply Chain Security" model.
When Xcode compiles your application, it aggregates the main application binary and all linked frameworks (dynamic and static). The build system looks for a PrivacyInfo.xcprivacy resource file in every bundle.
The rejection occurs because the Start.io SDK interacts with APIs that Apple has flagged as high-risk for fingerprinting, specifically:
- UserDefaults: Used for tracking frequency capping and user sessions.
- File Timestamp APIs: Used for caching logic.
- System Boot Time: Used for session duration calculations.
If the Start.io SDK binary calls [NSUserDefaults standardUserDefaults] but does not contain a compiled PrivacyInfo.xcprivacy declaring why it calls that API, Apple's static analyzer flags the mismatch during the transporter upload phase.
The Solution: Implementing the Privacy Manifest
The cleanest solution is to ensure you are running a version of Start.io that bundles this file natively (v4.10.0+). However, build system caches (CocoaPods/SPM) or specific integration methods often result in the file being stripped or ignored during the "Embed Frameworks" build phase.
If you are facing rejections, you must manually ensure the correct definitions exist. Below is the strict XML configuration required for the Start.io SDK's typical operation.
Step 1: Verify or Create the File
In Xcode:
- File > New > File.
- Search for "App Privacy".
- Name it
PrivacyInfo.xcprivacy. - Ensure it is checked for Target Membership in your main app target.
Step 2: The Source Code Configuration
Open PrivacyInfo.xcprivacy as "Source Code" (Right-click > Open As > Source Code) and replace/merge the contents with the following XML. This covers the standard requirements for Ad Networks accessing defaults and timestamps.
<?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>
<!-- 1. Privacy Accessed API Types -->
<key>NSPrivacyAccessedAPITypes</key>
<array>
<!-- UserDefaults Usage -->
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<!-- CA92.1: App functionality (Frequency capping/Session state) -->
<string>CA92.1</string>
</array>
</dict>
<!-- File Timestamp Usage -->
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<!-- C617.1: Inside app or group container (Caching) -->
<string>C617.1</string>
</array>
</dict>
<!-- System Boot Time Usage -->
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<!-- 35F9.1: Measure time elapsed between events -->
<string>35F9.1</string>
</array>
</dict>
</array>
<!-- 2. Privacy Collected Data Types -->
<key>NSPrivacyCollectedDataTypes</key>
<array>
<!-- Device ID (IDFA/IDFV) -->
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeDeviceID</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<true/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<true/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising</string>
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
</array>
</dict>
<!-- Coarse Location -->
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeCoarseLocation</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<false/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<true/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising</string>
</array>
</dict>
<!-- Product Interaction (Ad Clicks) -->
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeProductInteraction</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<false/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<true/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising</string>
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
</array>
</dict>
</array>
<!-- 3. Tracking Domain Configuration -->
<key>NSPrivacyTracking</key>
<true/>
<key>NSPrivacyTrackingDomains</key>
<array>
<!-- Add specific Start.io tracking domains if network isolation is required -->
</array>
</dict>
</plist>
Step 3: Verifying the Aggregation
After adding the file, you must verify that Xcode is actually bundling it into the payload.
- Archive your project.
- Right-click the Archive in Organizer > Show in Finder.
- Right-click the
.xcarchive> Show Package Contents. - Navigate to
Products/Applications/YourApp.app. - Right-click the App > Show Package Contents.
Look for PrivacyInfo.xcprivacy at the root. If you are using CocoaPods and Start.io is integrated as a dynamic framework, navigate into the Frameworks/StartApp.framework folder inside the bundle. The manifest must reside inside the framework folder for dynamic linking, or at the app root for static linking.
Deep Dive: Required Reason APIs Explained
Why specifically CA92.1 and C617.1? Selecting the wrong code will result in a binary rejection even if the manifest exists.
NSPrivacyAccessedAPICategoryUserDefaults (CA92.1)
Ad SDKs use UserDefaults to persist simple state, such as:
- "Has this user seen this interstitial ad today?"
- Generating and storing a session UUID.
Apple strictly forbids using UserDefaults to read data written by other apps or libraries to build a fingerprint. Code CA92.1 explicitly tells Apple: "I am only reading/writing data required for my app's own functionality, restricted to my sandbox."
NSPrivacyAccessedAPICategoryFileTimestamp (C617.1)
Start.io caches video and image assets to reduce bandwidth. To determine if an asset is stale, it checks the file creation date. This triggers the File Timestamp API usage. Code C617.1 declares that this access is strictly for managing files inside the app's container.
Handling Edge Cases and Pitfalls
1. The "Missing Signature" Error (CocoaPods)
If you see ITMS-91056: Invalid Privacy Manifest, it often means the manifest is present but the framework signature is broken.
If you manually modified a framework inside Pods/, the code signature is invalid. You must re-sign the framework during the build script, or preferably, use a post_install hook in your Podfile to inject the resource before signing occurs.
# Podfile post_install hook example to ensure privacy resources are copied
post_install do |installer|
installer.pods_project.targets.each do |target|
if target.name == 'StartApp'
target.build_configurations.each do |config|
config.build_settings['GENERATE_INFOPLIST_FILE'] = 'YES'
end
end
end
end
2. Nutrition Label Mismatch
The PrivacyInfo.xcprivacy file does not automatically update your App Store Connect "App Privacy" section (the Nutrition Label). You must manually ensure they match.
If your manifest says you collect NSPrivacyCollectedDataTypeDeviceID (which you do, for ads), but your App Store Connect label says "Data Not Collected," Apple will flag this discrepancy.
3. Static vs. Dynamic Linking
If Start.io is linked statically (common in Unity builds exported to iOS), the SDK code is merged into your main binary executable. In this case, your app's main PrivacyInfo.xcprivacy must contain the SDK's declarations. The separate SDK manifest is effectively dissolved during the linker phase.
Conclusion
The introduction of PrivacyInfo.xcprivacy is not a suggestion; it is a hard gate for App Store distribution. For ad networks like Start.io, which inherently rely on user data and device signals, precise configuration is mandatory.
By explicitly defining NSPrivacyAccessedAPICategoryUserDefaults and NSPrivacyCollectedDataTypeDeviceID with the correct reason codes, you satisfy the static analyzer's requirements and prevent ambiguous rejection notices. Always cross-reference your XML configuration with the actual API calls in your dependency tree to ensure long-term compliance.