iOS Privacy Manifest & Required-Reason APIs: SDK Checklist

An iOS privacy manifest is a PrivacyInfo.xcprivacy plist that declares three things: what data your code collects, which internet domains you use for tracking, and which required-reason APIs you call paired with an Apple-approved reason code. Since May 1, 2024, App Store Connect rejects new and updated apps that use required-reason APIs without declaring approved reasons in that file. If you own an SDK, the manifest is now part of your shippable surface, not a submission afterthought.

Why this became a hard gate (and the two dates you can't ignore)

The manifest system grew out of WWDC23, but the part that stings is enforcement. Per Apple's developer news post from February 2024, "starting May 1: you'll need to include approved reasons for the listed APIs used by your app's code to upload a new or updated app to App Store Connect." That's the first date. No approved reason, no upload.

The second date is November 12, 2024. An Apple Developer Forums thread documents the ITMS-91061 warning: from that day, if a new app includes a privacy-impacting SDK, or an app update adds one, the SDK itself must ship a privacy manifest. So the gate moved from "your code" to "everything you bundle."

I've watched a lot of postbacks show up whenever they feel like it. Tuesday's install conversion landing Thursday afternoon, no apology. App Store rejections aren't like that. They arrive the second you hit upload, and they block the whole release. That asymmetry is the reason to treat the manifest as deterministic work you finish before you submit, not something you gamble on.

What the manifest actually contains

Open the file and you're looking at a plist with a small number of top-level dictionary keys. Picture the root as a dictionary. Three keys matter.

NSPrivacyCollectedDataTypes is an array. Each element is a dictionary describing one data type you collect, whether it's linked to identity, and whether you use it for tracking. NSPrivacyTracking is a Boolean sitting at the root. When it's true, NSPrivacyTrackingDomains becomes an array of the domains your code contacts for tracking. NSPrivacyAccessedAPITypes is an array where each entry names an API category and lists the reason codes you're claiming for it.

Apple's own privacy manifest documentation describes the tracking Boolean plainly: "a Boolean that indicates whether your app or third-party SDK uses data for tracking as defined under the App Tracking Transparency framework." Everything else hangs off these three keys. The file is small. Getting it wrong is easy.

The fingerprinting line nobody gets to cross

There's one rule the reason codes don't soften. Required-reason APIs may be used only for the reasons you declare, and fingerprinting is banned outright. From the WWDC23 session on this topic: "regardless of whether a user gives your app permission to track, fingerprinting is not allowed." ATT consent doesn't buy you a fingerprinting exemption. Declaring a reason code doesn't either. If you're reading device signals to stitch identity across apps, no manifest entry makes that legal.

Checklist Part 1 — Data-use declarations (NSPrivacyCollectedDataTypes)

For each data type your SDK touches, you declare a small dictionary. Run this per type:

  • Name the data type using Apple's collected-data-type constant (e.g. device ID, product interaction, crash data).
  • Set the linked-to-identity flag: is this data tied to a user's identity?
  • Set the used-for-tracking flag: does this feed tracking as ATT defines it?
  • Name the purposes (analytics, app functionality, and so on).

The trap is honesty about "linked." An install ID you store and later join to a user account is linked, even if it starts anonymous. Declare it as such. Reviewers cross-reference this array against your App Store product page privacy labels, and mismatches read as sloppiness at best.

Checklist Part 2 — Tracking domains

This pair ties directly to ATT. Set NSPrivacyTracking to true if any code path uses data for tracking, then enumerate every domain that does tracking work in NSPrivacyTrackingDomains. When the app can't get ATT permission, iOS blocks network calls to listed domains. That's the enforcement teeth, and it's why the list needs to be right.

Here's a doc-ambiguity flag. Apple says to list domains that "engage in tracking," but the boundary isn't defined crisply. What about a domain you call only before you'd ever fire a tracking event? The docs don't say. Testing shows a domain that's contacted only pre-consent still draws scrutiny if it's plausibly capable of tracking, so the safe move is to list any domain whose payloads could feed tracking and gate those calls on consent yourself. If you want the wider picture of how this fits an attribution stack, our reference architecture for privacy-first attribution walks the same boundary from the network side, and the ATT opt-in benchmarks piece covers what consent rates you're realistically gating against.

Checklist Part 3 — Required-reason APIs and their valid codes

This is the table you'll come back to. Apple organized the initial required-reason APIs into five categories. As Singular's October 2023 write-up put it, the list arrived "in 5 categories: file timestamp APIs, system boot time APIs, disk space APIs, active keyboard APIs, and user defaults APIs." Each category pairs with reason codes from a fixed list.

API Category Manifest key Approved reason code(s)
File timestamp NSPrivacyAccessedAPICategoryFileTimestamp 3B52.1 (see Apple's list)
System boot time NSPrivacyAccessedAPICategorySystemBootTime see Apple's list
Disk space NSPrivacyAccessedAPICategoryDiskSpace 7D9E.1 (see Apple's list)
Active keyboard NSPrivacyAccessedAPICategoryActiveKeyboard see Apple's list
User defaults NSPrivacyAccessedAPICategoryUserDefaults CA92.1 / 1C8F.1 (App Group)

The codes for UserDefaults, disk space, and file timestamp trace to Bugfender's November 2024 breakdown: "access to user preferences (NSPrivacyAccessedAPICategoryUserDefaults) with reason code CA92.1, monitoring of disk space usage (NSPrivacyAccessedAPICategoryDiskSpace) with reason code 7D9E.1, retrieval of file timestamps (NSPrivacyAccessedAPICategoryFileTimestamp) with reason code 3B52.1."

Watch the UserDefaults fork. As Donny Wals noted in his May 2024 guide, "in the case of UserDefaults, we're probably interested in one of two keys: CA92.1 or 1C8F.1 depending on whether you're building an app that uses an App Group." If your SDK reads and writes shared defaults across an App Group container, CA92.1 is the wrong code. Pick 1C8F.1. This is the single most common misfile I see.

Worked example: auditing an attribution SDK's manifest

Take a hypothetical attribution SDK. It does three ordinary things on-device.

At launch it reads an install-time flag from UserDefaults to decide whether this is a first run. Standard app-storage use, no App Group involved. That maps to NSPrivacyAccessedAPICategoryUserDefaults with code CA92.1.

Before writing a batch of queued events to disk, it checks available disk space so it doesn't fill the device. That's NSPrivacyAccessedAPICategoryDiskSpace with 7D9E.1. And it rotates its own log files, reading their modification timestamps to decide what to prune. That's NSPrivacyAccessedAPICategoryFileTimestamp with 3B52.1.

The resulting NSPrivacyAccessedAPITypes array holds three dictionaries. In words: entry one names the UserDefaults category and lists CA92.1; entry two names the DiskSpace category and lists 7D9E.1; entry three names the FileTimestamp category and lists 3B52.1. Three calls, three categories, three codes. Clean.

Now the failure mode. Suppose that same SDK actually stores the install flag in an App-Group container so a share extension can read it. If the manifest still says CA92.1, you've declared the wrong reason. The build might upload, since App Store Connect isn't checking your App Group config against the code, but you've made a false declaration. That's exactly the kind of thing that surfaces later when a reviewer or an audit tool notices the shared container. The fix is one string: swap CA92.1 for 1C8F.1. I've burned an afternoon on precisely this, chasing a "why does the share extension see stale data" bug that turned out to also be a manifest lie.

Auditing your dependencies (not just your own code)

The November 2024 wave is why your own clean manifest isn't enough. A third-party SDK you bundle can trigger ITMS-91061 and block your submission. So audit dependencies the way you'd audit your own binary.

Start by grepping the framework binaries for the symbol names behind the five categories: calls like stat, statfs, NSUserDefaults, boot-time reads. Then check whether each SDK ships its own .xcprivacy. For SDKs on Apple's published privacy-impacting list, per Apple's May 2024 developer post, you need "required reasons for each listed API, privacy manifests, and valid signatures when the SDK is added as a binary dependency." Verify the signature on those, not just the manifest's presence.

SDKs that already ship a compliant manifest and signature save you this entire pass. The Kixo iOS SDK, for instance, ships a compliant privacy manifest. When you're evaluating measurement vendors, ask for it up front. It's a fair proxy for whether they've done the rest of their homework. If you're comparing deep-linking providers on the same criteria, our deep linking platforms comparison covers what else to check.

Verify the merged output before you submit

Here's the step nobody emphasizes enough. Build the app, archive it, and open Xcode's Privacy Report on the archive. That report aggregates your manifest plus every dependency's manifest into one view. Read it as the source of truth.

Walk it top to bottom. Confirm every required-reason category that shows up has at least one reason code. Confirm every tracking domain is listed. Confirm the collected-data-types match your product page labels. The docs don't stress this, but testing shows the Privacy Report is the only place you see the merged reality, and it's the difference between a clean review and an ITMS-91061 email three days later. Your own manifest can be perfect while a bundled analytics SDK quietly drags in an undeclared disk-space call.

The copy-paste checklist

Run this list before every submission, not just the first one.

  • Enumerate data types you collect; set linked-to-identity and used-for-tracking flags per type in NSPrivacyCollectedDataTypes.
  • Set NSPrivacyTracking to true if any path tracks; list every tracking-capable domain in NSPrivacyTrackingDomains and gate those calls on ATT consent.
  • Grep your own binary for the five required-reason categories.
  • Map each call to its category and an approved code; check the UserDefaults CA92.1-vs-1C8F.1 App-Group fork.
  • Grep every bundled dependency for the same five categories.
  • Confirm each privacy-impacting SDK ships a manifest and a valid signature.
  • Archive, open Xcode's Privacy Report, and read the merged manifest end to end.
  • Confirm every category has a reason code and every tracking domain is listed before you upload.

Closing take

Manifests took Apple's scattered privacy rules and collapsed them into one auditable artifact. That's genuinely useful. Treat the PrivacyInfo.xcprivacy file as code you version, diff, and review on every SDK bump, because a dependency update can silently change your merged output. The nice part, and I mean this after years of debugging attribution plumbing, is that the manifest is deterministic. It says exactly what it says, every build, no drift. Your postbacks will still arrive whenever they feel like it. Your manifest, at least, tells the truth on schedule.