Most iOS privacy manifest conflicts come from packaging, not from the declarations themselves. The common one is the Xcode error “Multiple commands produce …/PrivacyInfo.xcprivacy”: a static library or CocoaPods dependency copies its manifest into the app bundle root, where it collides with yours. Fix it by shipping each dependency’s manifest inside its own resource bundle or framework, then check the archive and generate Xcode’s privacy report. Separately, make sure every required-reason API your code and SDKs call, including protection and diagnostics code, is declared with an approved reason.
For the product context, see data loss prevention and iOS app security.
Where manifests belong
A privacy manifest is a property list named PrivacyInfo.xcprivacy. On iOS, the app’s manifest sits at the root of the app bundle, and a framework’s manifest sits at the root of that framework bundle. Each bundle has its own file with the same name, which is legitimate. Xcode only fails when two build steps try to write a file to the same destination path.
| Symptom | Likely cause | Fix |
|---|---|---|
| “Multiple commands produce …/PrivacyInfo.xcprivacy” | A dependency’s manifest is copied as a plain resource into the app bundle root | Move the dependency’s manifest into its own resource bundle or framework |
| ITMS-91053 “Missing API declaration” after upload | A required-reason API is called but not declared in the manifest of the code that calls it | Add the category and an approved reason to the right manifest |
| Invalid manifest reported for a bundle | Wrong keys or value types in that file | Correct the file, or get a fixed release from the SDK vendor |
| Privacy report misses an SDK | The manifest is not in the archive, or is in the wrong place | Check the bundle path in the archive |
Fix “Multiple commands produce PrivacyInfo.xcprivacy”
This error appears most often with static libraries and with CocoaPods using static linkage. A static library (.a) cannot carry resources, so its files are copied into whatever target links it, which is your app. If both your app and the dependency provide PrivacyInfo.xcprivacy, two copy steps target the same path.
For CocoaPods dependencies
A pod that lists its manifest under resources copies it straight into the app bundle. The fix belongs in the podspec: ship the manifest under resource_bundles, so it lands in its own .bundle inside the app.
s.resource_bundles = {
'ExampleSDK_Privacy' => ['Sources/PrivacyInfo.xcprivacy']
}
Most maintained pods have already made this change, so update the dependency first. If a vendor has not, raise it with them. As a stopgap, copy the dependency’s declarations into your app’s manifest and remove the duplicate from the copy step, and record that you now own those declarations.
For static libraries you build
Apple’s guidance is to create a static framework target containing the library’s sources and resources, and add the manifest to that target. A framework bundle has its own root, so its manifest no longer collides with the app’s.
For Swift packages and XCFrameworks
Declare the manifest as a resource in the package, because Xcode does not treat it as one by default. For XCFrameworks, check that each platform slice you ship contains its own manifest.
Generate and read the privacy report
After archiving, open Xcode’s Organizer, Control-click the archive and choose Generate Privacy Report. Xcode collects the manifests from the app and its bundles into one PDF. Use it to check three things:
- Every SDK you expect appears in the report. A missing SDK means its manifest is missing or misplaced.
- Data types and purposes match what the app and its SDKs really collect.
- Required-reason API categories are present for every SDK that calls them.
To inspect a specific file, use Show in Finder on the archive, then Show Package Contents, and follow the path to the bundle. Do not edit the manifest inside an archive: changing it breaks the signature. Fix the source and archive again.
Required-reason APIs in protection code
Some APIs can be misused for fingerprinting, so Apple requires a declared reason for each category used. App Store Connect has rejected uploads that call these APIs without a declaration since 1 May 2024. Security and diagnostics code often relies on them:
| Category key | Typical use in protection or diagnostics code |
|---|---|
NSPrivacyAccessedAPICategoryFileTimestamp | stat, getattrlist and file-date APIs used for integrity and environment checks |
NSPrivacyAccessedAPICategorySystemBootTime | systemUptime and mach_absolute_time used for timing and anti-debugging checks |
NSPrivacyAccessedAPICategoryDiskSpace | Free-space queries in diagnostics and crash reporting |
NSPrivacyAccessedAPICategoryUserDefaults | Storing flags and configuration |
Declare the reason that matches how the code uses the API, chosen from Apple’s list for that category, and put it in the manifest of the bundle that contains the code. An SDK declares its own use; your app’s manifest does not cover a framework’s calls. Ask every SDK and protection vendor which categories their code uses and which reasons they declare, and confirm their manifest appears in your privacy report.
Otukan Application Security fits your existing iOS build and release pipeline, and signing keys stay with you. Include the protected build in the same archive and privacy-report review as the rest of the release.
Release checklist
- The app and every framework or resource bundle have exactly one manifest each.
- The archive builds without “Multiple commands produce” errors.
- The privacy report lists every SDK and matches real data collection.
- Required-reason APIs in your code and protection code have approved reasons in the right manifest.
- App extensions are checked as separate bundles.
- Install, update, login and the sensitive journey pass on the signed build on a physical device.