Your archive uploaded, the ingestion email returned ITMS-91056 with the phrase Invalid privacy manifest, and the body of the email named a file at Frameworks/Reachability.framework/PrivacyInfo.xcprivacy. The build now sits in the Invalid Binary state in App Store Connect. The question is which key or value inside that shipped manifest the audit refused, why the file ended up in your archive in the first place, and the smallest change that gets the next upload to a clean ingestion.
Short answer
App Store Connect returns ITMS-91056 when a PrivacyInfo.xcprivacy file shipped inside an embedded framework does not parse against Apple's schema for the four recognised top-level keys: NSPrivacyTracking, NSPrivacyTrackingDomains, NSPrivacyCollectedDataTypes, and NSPrivacyAccessedAPITypes. The ingestion email prints the path inside the .app, in this case Frameworks/Reachability.framework/PrivacyInfo.xcprivacy. The fix is to pull a fresh release of the framework whose manifest validates, replace the file in place with a minimal valid plist, or drop the dependency. An app-level manifest cannot override a malformed file nested inside a vendored framework. Apple's Privacy manifest files reference documents the recognised keys and value types.
What you should know
- The audit reads every PrivacyInfo.xcprivacy inside the .app, not only the one in your main target. A manifest nested under a third-party framework is validated on its own merits.
- The ingestion email names the file path, not the failing key. Identifying which entry the validator refused is a separate plist inspection step run with plutil or PlistBuddy.
- The Reachability name sits on Apple's list of SDKs that must ship a privacy manifest. Maintainers added a manifest defensively because of the name match, and a malformed first attempt is what trips the audit.
- Only four top-level keys are recognised. NSPrivacyTracking is a Boolean, NSPrivacyTrackingDomains is an array of strings, and the two API arrays are arrays of dictionaries with a fixed set of inner keys.
- The framework owner is responsible for the file inside the framework. Updating to a release where the maintainer corrected the manifest is cleaner than patching a vendored copy.
- The build number must change between uploads. Re-uploading at the same CFBundleVersion leaves the Invalid Binary record in place even after the underlying file is corrected.
Why does App Store Connect refuse a manifest the framework author already shipped?
App Store Connect parses every PrivacyInfo.xcprivacy in the upload through the same validator and compares each key and value against the schema published in Apple's Privacy manifest files reference. A typo in a top-level key, an unrecognised enum value under NSPrivacyAccessedAPIType, a category that no longer exists, or a value with the wrong plist type (a string where the schema expects a Boolean) is enough to fail the parse. The audit treats the file as one unit: a single invalid entry fails the whole manifest.
The mechanism matters because the ingestion email mentions only the path, not the line. Developers reported the same pattern on the Apple Developer Forums in thread 770148, where the email named the Reachability path while the main target manifest was clean. The file inside the framework was the one the audit refused, even though the app developer had never written it.
The reason the file appears at all sits one step earlier. Apple's published third-party SDK requirements list names Reachability. Several framework authors who ship under that name added a manifest defensively to keep the upload moving once the requirement took effect. When the defensive file does not parse, the audit fires on the shipped framework even though the app developer linked it for a one-line network reachability check.
Which keys and values does the audit actually accept?
The privacy manifest plist root is a dictionary. Only four top-level keys are recognised. Any other key, including a small typo on a recognised one, fails validation.
| Top-level key | Type | Notes |
|---|---|---|
| NSPrivacyTracking | Boolean | true or false. Always present, even when the rest of the manifest is empty. |
| NSPrivacyTrackingDomains | Array of strings | Domains contacted for tracking. Empty array is valid when tracking is false. |
| NSPrivacyCollectedDataTypes | Array of dictionaries | Each entry has NSPrivacyCollectedDataType (string), NSPrivacyCollectedDataTypeLinked (Boolean), NSPrivacyCollectedDataTypeTracking (Boolean), and NSPrivacyCollectedDataTypePurposes (array of strings). |
| NSPrivacyAccessedAPITypes | Array of dictionaries | Each entry has NSPrivacyAccessedAPIType (one of the five published category strings) and NSPrivacyAccessedAPITypeReasons (array of approved reason codes for that category). |
The five recognised values for NSPrivacyAccessedAPIType are NSPrivacyAccessedAPICategoryUserDefaults, NSPrivacyAccessedAPICategoryFileTimestamp, NSPrivacyAccessedAPICategorySystemBootTime, NSPrivacyAccessedAPICategoryDiskSpace, and NSPrivacyAccessedAPICategoryActiveKeyboards. A misspelt category (NSPrivacyAccessedAPICategoryUserDefault without the trailing s, for example) is one of the most common causes of ITMS-91056 in a vendored manifest. The approved reason codes follow the format four-character.one and are listed on Apple's Describing use of required reason API page.
To inspect the file the email named, open the archived .app in Finder, navigate to the framework path the email printed, and run plutil against the .xcprivacy file:
plutil -lint YourApp.app/Frameworks/Reachability.framework/PrivacyInfo.xcprivacy
plutil -p YourApp.app/Frameworks/Reachability.framework/PrivacyInfo.xcprivacy
The first command exits non-zero on a structural plist error. The second prints the file in a human-readable form so the offending key or value is visible. A category typo, a key the schema does not list, or a value with the wrong plist type tends to surface in seconds.
How do I tell which dependency pulled in Reachability.framework?
The path inside the .app names the framework, not the package that links it. Reachability.framework appears in several forms across the iOS package landscape: Tony Million's Objective-C port, the Swift port maintained by Ashley Mills under the product name ReachabilitySwift, several React Native bridges, and a handful of Flutter plugins. Each can ship the manifest at the root of the .framework, which is the path the audit named.
The fastest scan is a single grep across the lock files at the project root:
grep -i reachability Package.resolved Podfile.lock Cartfile.resolved 2>/dev/null
The output names the resolver entry and the version. Common parents that pull Reachability.framework in include the Flutter connectivity_plus plugin, the older AFNetworking, ReactiveCocoa extensions, and React Native bridges around network state. When the parent is a Flutter or React Native plugin, the manifest file lives inside the iOS subspec the plugin vendors, so updating the plugin to a release where its own manifest is corrected is the path with the smallest blast radius.
When your own project links Reachability.framework directly, the choice is between updating to a tag that ships a corrected manifest, switching to a maintained fork, or replacing the dependency with Apple's NWPathMonitor on iOS 12 and later. NWPathMonitor covers most cases the older library handled, removes the manifest issue at the source, and aligns with Apple's preferred path for the same functionality.
Why does the same error appear across other hybrid and network libraries?
Reachability is one name on a longer list of third-party libraries that trip ITMS-91056 in the months after the privacy manifest requirement took effect. The pattern repeats because the audit applies the same schema to every file in the .app, and maintainers across the package landscape shipped their first manifest in slightly different ways. Three concrete examples reported on public trackers tell the same story.
| Library | Path the audit named | Root cause |
|---|---|---|
| MapboxCommon | Frameworks/MapboxCommon.framework/PrivacyInfo.xcprivacy | A key or value in the first shipped manifest failed the schema check (issue 2232). |
| NewRelic | Frameworks/NewRelic.framework/PrivacyInfo.xcprivacy | The same class of schema mismatch in the React Native agent's iOS framework. |
| ReachabilitySwift | Frameworks/Reachability.framework/ReachabilitySwift.bundle/PrivacyInfo.xcprivacy | A typo or unrecognised value inside the nested bundle, tracked in Reachability.swift issue 421. |
In each case the resolution is the same. The framework owner ships an updated release with a corrected manifest, the app developer bumps the dependency, archives a new build, and re-uploads with an incremented build number. When the framework owner has not shipped a release yet, the in-place patch covered in the next section is the working stopgap.
What is the smallest fix that clears the Invalid Binary?
Three changes typically land together. None of them touches the main app manifest.
- Update the framework to a release whose manifest validates. Pull the latest tag of the dependency, regenerate the lock file (
pod install --repo-updatefor CocoaPods,xcodebuild -resolvePackageDependenciesfor SPM), clean the build folder, and archive a fresh build with an incremented build number. - If no release is available yet, replace the file inside the vendored framework with a minimal valid manifest. A two-key plist with
NSPrivacyTrackingset to false andNSPrivacyTrackingDomainsset to an empty array passes the audit when the library does not collect data or call a required reason API. Patching a vendored framework is a short-term step; the change has to be reapplied on every clean install until the maintainer ships a corrected file upstream. - Bump CFBundleVersion before re-uploading. App Store Connect keys the Invalid Binary record to the build number. Re-uploading at the same number returns the same email even after the underlying file is corrected.
For builders who want an external automated read across every embedded framework before the next upload, PTKD.com (https://ptkd.com) is one of the platforms that inspects compiled IPA bundles and flags PrivacyInfo.xcprivacy files whose keys, categories, or reason codes fall outside Apple's schema. It surfaces the offending entry before App Store Connect does, which shortens the loop between an Invalid Binary email and a clean ingestion.
What to watch out for
A few patterns turn a one-build fix into a multi-day loop.
- Patching a vendored manifest without pinning the dependency. A clean checkout or fresh pod install overwrites the edit. Pin the version in Podfile or Package.swift, and keep a note in the repo so the patch survives the next install.
- Editing the manifest in the project but not the one inside the archive. The audit reads the file that ends up in the .app, not the one in the source tree. Build, open the archive, and inspect the framework path the email named.
- Assuming the app-level PrivacyInfo.xcprivacy covers the nested one. It does not. The audit treats each manifest in the upload as its own unit.
- Treating ITMS-91056 as a signing problem. That is ITMS-91065. ITMS-91056 fires on the contents of the file, not its signature, and confusing them sends the next iteration into the wrong corner.
- Re-uploading at the same build number. App Store Connect returns the same error on the cached Invalid Binary record.
Key takeaways
- ITMS-91056 fires on the contents of a PrivacyInfo.xcprivacy file, and the email names the path inside the archive that failed the schema check.
- The Reachability.framework path appears because the Reachability name sits on Apple's published SDK list, and several maintainers ship the manifest at the framework root rather than a nested bundle.
- The four top-level keys (NSPrivacyTracking, NSPrivacyTrackingDomains, NSPrivacyCollectedDataTypes, NSPrivacyAccessedAPITypes) are fixed; anything else fails the parse.
- The smallest fix is usually a framework update plus a build-number bump, not a change to the main target manifest.
- Some teams outsource the pre-submission manifest sweep across every embedded framework to platforms like PTKD.com (https://ptkd.com), which surfaces malformed entries before App Store Connect rejects the upload.


