App Store Connect privacy manifest invalid errors should be debugged from the submitted xcarchive, not from the main app source folder. Read the rejected Bundle path first, locate that exact PrivacyInfo.xcprivacy, then validate its structure, declared reasons, SDK ownership, Target membership, and signature. After the fix, create a new Archive and complete a real upload.
This applies when you receive ITMS-91056 or a similar privacy-manifest rejection, need to restore TestFlight or App Store submission, or build through a remote Mac or CI system. It is also for teams using Swift Package Manager, CocoaPods, Frameworks, or XCFrameworks that cannot tell whether the failure belongs to their code or a dependency.
01 The rejection path
A typical failure email may say that PrivacyInfo.xcprivacy is invalid and include a path such as:
Payload/Example.app/Frameworks/SomeFramework.framework/PrivacyInfo.xcprivacy
Treat the path as evidence. Do not assume the main application manifest is responsible. The path can point to a nested Framework, an Extension, an App Clip, a Catalyst product, or another Bundle inside the submitted product.
First record these fields from the email:
- The App Store Connect error code, such as
ITMS-91056. - The exact filename.
- The complete relative Bundle path.
- The reported reason, such as invalid structure, missing declaration, unsupported value, or SDK-related failure.
- The build and Archive identifier used for the upload.
Apple’s TN3181 debugging guidance for invalid privacy manifests separates several failure classes that can look similar in the web interface. An invalid manifest is not the same problem as a missing manifest, an incorrect Required Reason API entry, or an SDK signature issue.
Keep the original rejected Archive unchanged. Copy it to a separate inspection location before extracting or modifying anything. Your goal is to preserve the evidence that produced the rejection.
Key takeaway: the error path identifies the responsible product more reliably than a search for every manifest in the repository.
02 The Archive evidence
An Xcode project can contain a correct-looking file that never reaches the submitted product. A build phase may omit it. A package resource may be declared for the wrong Target. A script may copy it into an unrelated directory. A dependency may inject another manifest later in the build.
Inspect the submitted Archive rather than relying on the project navigator.
Use a placeholder path in your shell session:
ARCHIVE="/path/to/YourApp.xcarchive"
find "$ARCHIVE/Products" -name "PrivacyInfo.xcprivacy" -print
Then inspect the product structure:
find "$ARCHIVE/Products" \
\( -name "*.app" -o -name "*.appex" -o -name "*.framework" -o -name "*.xcframework" \) \
-print
These commands read the Archive. They do not change the original product. If you need to extract files for review, work on a copy:
cp -R "/path/to/YourApp.xcarchive" "/path/to/YourApp-inspection.xcarchive"
Follow the App Store Connect path into the copied Archive. Confirm whether the file belongs to:
- The main iOS application.
- A Notification, Share, Widget, or other Extension.
- An embedded dynamic Framework.
- A Swift Package or CocoaPods product.
- An App Clip.
- A Mac Catalyst Bundle.
- A separate macOS application or helper.
Apple’s privacy manifest placement documentation explains that manifests can belong to an app or a third-party SDK. That distinction matters because the owner of the code using a sensitive API may not be the owner of the top-level app.
Check the Xcode privacy report as a second evidence source. Apple describes how privacy reports represent declared data use in its privacy report documentation. The report cannot replace Archive inspection, but a mismatch between the report and the rejected path is a useful signal that you are examining the wrong Target or build product.
03 The property-list structure
The next metric is syntax and data type. Use both the Xcode property list editor and plutil. Neither should be treated as the complete App Store Connect validator.
For a copied manifest, run:
MANIFEST="/path/to/copied/PrivacyInfo.xcprivacy"
plutil -lint "$MANIFEST"
plutil -p "$MANIFEST"
For an archived file:
ARCHIVED_MANIFEST="/path/to/copied.xcarchive/Products/Applications/YourApp.app/PrivacyInfo.xcprivacy"
plutil -lint "$ARCHIVED_MANIFEST"
plutil -p "$ARCHIVED_MANIFEST"
The expected result is a readable property list with a root dictionary. Check the following items manually:
- The root object is a dictionary, not an array or scalar.
- Arrays contain the expected string or dictionary elements.
- Dictionaries use the required key names.
- Boolean values are actual Boolean values, not strings such as
"true". - Required arrays are not empty when the declaration requires an entry.
- There are no accidental custom keys copied from an internal configuration file.
- The archived file is the same file type and content you intended to ship.
Do not edit the original Archive while testing. A successful plutil -lint result only proves that the file can be parsed as a property list. It does not prove that every key, value, reason code, API category, or SDK declaration meets Apple’s submission rules.
If the source file passes but the archived file fails, compare them directly:
diff -u \
"/path/to/source/PrivacyInfo.xcprivacy" \
"/path/to/copied/PrivacyInfo.xcprivacy"
A difference usually points to a build script, resource-copy phase, dependency generation step, or a second manifest with the same filename.
04 The Required Reason API declarations
A privacy manifest must describe the APIs that are actually used. Do not select an approved reason merely because it makes the upload pass.
Use code search to establish ownership:
grep -R \
-E "UserDefaults|fileTimestamp|systemBootTime|diskSpace|activeKeyboard" \
"/path/to/source" \
--exclude-dir=DerivedData
The search terms above are examples only. Match them to the API categories reported by your build tools and the code used by your application or dependencies. Then inspect:
- Your own Swift or Objective-C source.
- Generated source.
- Package dependency source.
- CocoaPods source.
- Framework release notes.
- The dependency lock file.
- The Xcode privacy report.
- The final manifest inside the Archive.
Apple’s Required Reason API configuration note explains how to add entries. The official Required Reason API reference defines the declaration model and approved reasons.
The test is semantic:
- If the app uses the API for an approved purpose, declare that purpose.
- If the API is only called by an SDK, investigate the SDK’s own manifest and release.
- If the selected reason does not describe the real feature, stop and redesign the declaration.
- If the API is no longer needed, remove the call and rebuild rather than hiding it with a declaration.
- If the same API is used for multiple legitimate purposes, verify that the manifest supports the actual usage rather than guessing.
The main app’s manifest does not become a universal substitute for a third-party SDK manifest. Apple can inspect the nested product that performs the API access.
05 Dependency ownership
The dependency source determines the correct fix. Identify how the responsible Bundle entered the build.
Swift Package Manager
Inspect Package.resolved, the package manifest, and the package resource declaration. Confirm that the package version includes its privacy manifest and that the resource is copied into the product that ships. A package can contain the file in its repository while the selected product omits it from the Archive.
If an upgrade is required, update the package deliberately. Preserve the new lock-file revision in version control. Do not update every dependency during an urgent release unless you can reproduce the resulting Archive.
CocoaPods
Check the Pod version, generated project, resource phases, and the actual Framework or Bundle under Products. A Pod may provide a manifest through a resource bundle or embedded Framework. The source checkout alone does not prove that the file is packaged correctly.
Use the dependency lock file to reproduce the exact installation. If the vendor has released a version that corrects the manifest, prefer that release over a manual patch.
Dynamic Frameworks and XCFrameworks
For a dynamic Framework or binary XCFramework, identify the vendor-provided manifest and inspect the shipped binary. Also check its code signature:
codesign --verify --deep --strict --verbose=2 \
"/path/to/copied/SomeFramework.framework"
The command is an inspection step. It does not repair the signature.
Apple’s third-party SDK requirements impose additional obligations on certain SDKs. For an SDK listed by Apple, verify both the privacy manifest and the required signature condition. Do not assume that adding a manifest to the host app satisfies a requirement attached to the SDK itself.
Decision rule:
- If the vendor has a release with a valid manifest, upgrade and rebuild.
- If the SDK is replaceable and its maintainer has not corrected the issue, replace it before the next release.
- If the SDK is essential and no corrected release exists, contact the maintainer and document the temporary risk.
- If you edit an embedded dependency inside an Archive, treat it as an emergency artifact operation, not a normal build fix.
Changing a signed Framework or executable invalidates its code signature. A modified Archive may require correct re-signing of the changed product and its containing application. The exact signing process depends on entitlements, provisioning, distribution method, and nested code. If you cannot reproduce that process, do not upload the edited Archive.
06 Target and Bundle placement
A source file is not a shipped resource until the correct Target includes it. This is the most common reason a developer sees a valid PrivacyInfo.xcprivacy in Xcode while App Store Connect rejects another file.
For every affected product, check:
- Target membership in Xcode.
- Copy Bundle Resources or package resource configuration.
- Build settings that alter resource paths.
- Script phases that copy or transform manifests.
- Extension-specific resource membership.
- Framework embedding and signing phases.
- Catalyst and macOS product settings.
- App Clip packaging.
- The final path in the Archive.
Do not blindly add the same file to every Target. A shared manifest may be appropriate for shared code, but each shipped Bundle must reflect the APIs used by that Bundle and its dependencies.
For multiple Targets, use this branch:
- If the rejection path points to the main
.app, repair the main app’s resource and declarations. - If it points to an
.appex, inspect that Extension’s source, dependencies, and resources. - If it points to a
.framework, investigate the Framework owner before changing the host app. - If it points to a Catalyst or macOS Bundle, validate that product separately.
- If the file exists only in the source tree, add it to the correct build product and verify the new Archive.
The final acceptance condition is not “the file appears in the project.” It is “the file appears at the expected path in the submitted Archive, with valid content and a valid signature.”
07 Clean rebuild and upload acceptance
A repair is incomplete until the new artifact survives the same pipeline that failed.
Use this sequence:
- [ ] Preserve the rejected email, error path, build identifier, and original Archive.
- [ ] Identify the responsible Bundle in the copied Archive.
- [ ] Trace that Bundle to its Target, package, Pod, Framework, or XCFramework owner.
- [ ] Validate the manifest with
plutiland the Xcode editor. - [ ] Confirm API categories and approved reasons against the actual code or dependency.
- [ ] Check the Xcode privacy report for the new build.
- [ ] Update or replace the dependency if it owns the invalid manifest.
- [ ] Create a completely new Archive.
- [ ] Inspect the new Archive at the exact rejected path.
- [ ] Verify signatures after any dependency or resource change.
- [ ] Upload the new Archive.
- [ ] Confirm that App Store Connect accepts the upload and completes processing.
Apple’s Xcode distribution documentation covers the standard distribution flow. Local validation, Archive creation, signature verification, upload reception, and background processing are separate states. Passing one does not prove the next.
For a remote Mac or CI build, retain the dependency lock file, Xcode build settings, Archive identifier, build log, privacy report, and upload result. This evidence lets you reproduce the same commit. It also prevents a false fix caused by an untracked local dependency update.
08 FAQ
The detailed answers below cover the most common paths without treating every privacy error as the same failure.
ITMS-91056 and invalid manifests
If the rejection is ITMS-91056, begin with the Bundle path in the message. Inspect that exact manifest inside the Archive. Check syntax first, then semantic values, API reasons, Target ownership, and SDK requirements. Do not add a generic manifest to the main app until you know the rejected file belongs there.
Manifest location inside an Archive
A manifest may be under the main app, an Extension, a Framework, an App Clip, or a Catalyst or macOS product. Search the Products directory of the submitted Archive. The path in the App Store Connect email is the authoritative starting point for selecting which copy to inspect.
Editing an SDK manifest
Avoid modifying a third-party manifest as a permanent solution. Upgrade to a corrected SDK release when possible. If you must alter an Archive for a controlled emergency test, assume that signing is affected. Re-signing must cover the changed nested code and the containing product, and the change should be replaced by a reproducible dependency fix.
Passing plutil but failing upload
plutil checks parseability. App Store Connect also evaluates allowed keys, data types, reason values, SDK obligations, Bundle placement, and signatures. Therefore, a clean syntax result is only the first gate. Compare the archived file with the source file and inspect the privacy report before changing unrelated project settings.
Multiple Targets and Extensions
Review each shipped Bundle separately. An Extension or Framework may have its own APIs and dependencies. The main app’s manifest cannot automatically represent those products. Confirm Target membership, Archive placement, and the final report for each responsible Bundle before creating the replacement Archive.
09 Choosing a reproducible release environment
If your current setup is a personal Mac with limited disk space, an unstable dependency cache, or a CI runner that discards Archives, the immediate weakness is not only the manifest. You also lack durable evidence for the build that App Store Connect rejected.
A clean remote Mac can provide a persistent workspace for the Archive, dependency lock file, build log, privacy report, and upload result. CALMVPS can be considered when you need a temporary macOS release environment without buying another Mac. Review the available Mac rental plans only after confirming that your workflow needs a real Mac, Xcode, signing credentials, and retained build artifacts.
10 Current setup versus a remote Mac
A local or disposable CI setup can leave you with three concrete problems: the rejected Archive is deleted, dependency versions drift between attempts, and a successful local plutil check cannot be compared with the artifact actually uploaded. It may also force you to repeat the entire environment setup before every urgent TestFlight submission.
A CALMVPS Mac environment does not remove the need to understand manifests or signing. It gives you a place to keep the Archive, lock files, reports, and upload logs together while you reproduce the fix. For a short release recovery, dependency upgrade test, or remote publishing run, that evidence trail can be more useful than purchasing hardware that sits idle between releases.
If your team needs sustained heavy builds, physical device access, or permanent local peripherals, buying and maintaining a dedicated Mac may be the better choice. If you need temporary, repeatable macOS access for a clean Archive and upload verification, start with the CALMVPS access options and keep the release evidence with the project.