●MAX — Rork Max generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage●PUBLISH — Rork Max ships 2-click App Store publishing and runs $200/month●RN — The standard Rork builds native iOS/Android apps with React Native (Expo) — the quicker path to a working app●PRICE — Rork is free to start, with paid plans from $25/month●FUND — Rork raised $2.8M from a16z; the platform now sees 743k+ monthly visits with 85% growth●FLOW — Describe your app in plain English and Rork generates deployable code that can use the camera, notifications, and more●MAX — Rork Max generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage●PUBLISH — Rork Max ships 2-click App Store publishing and runs $200/month●RN — The standard Rork builds native iOS/Android apps with React Native (Expo) — the quicker path to a working app●PRICE — Rork is free to start, with paid plans from $25/month●FUND — Rork raised $2.8M from a16z; the platform now sees 743k+ monthly visits with 85% growth●FLOW — Describe your app in plain English and Rork generates deployable code that can use the camera, notifications, and more
Make ITMS-91053 Stop Catching Your Rork Max App — A Release-Proof Privacy Manifest Workflow
Fixing ITMS-91053 once doesn't keep it away — every new dependency can bring it back. Here's a field-tested workflow to audit your dependency tree, generate declarations, and catch the rejection locally before you upload.
The truly annoying thing about ITMS-91053 in App Store Connect validation isn't the first rejection — it's that it comes back. You add one PrivacyInfo.xcprivacy, slip past the rejection, and then the next release, the moment you add one useful-looking library, you're bounced to the same place with a different reason code. As an indie developer who ships several apps to the App Store, I lost real time to this loop early on. You upload, you get the rejection email half a day later, you investigate, you rebuild — and that single cycle can easily eat a whole day.
So this piece isn't about "how to write PrivacyInfo.xcprivacy." It's about never hitting ITMS-91053 after upload again. There are three jobs: mechanically identify the affected APIs by auditing your dependency tree, aggregate the declarations without gaps, and catch problems locally before you upload. Build those three into a system and your Rork Max project stops getting stuck at review even as you add new libraries.
Why It Comes Back After You "Fixed" It
Apple's Required Reason API policy requires you to declare why in PrivacyInfo.xcprivacy whenever code in your binary accesses certain API categories — UserDefaults, file timestamps, available disk space, system boot time, and a few others. The key point: it doesn't matter whether your own code calls them. A Rork Max project bundles plenty of libraries for storage, notifications, analytics, and billing, and if any of them touches a covered API internally, the declaration obligation is yours.
So "fixing it once" only covers the libraries bundled at that moment. Your dependency tree shifts a little with every release — a new package, a major bump on an existing one, a swapped-out transitive dependency. Each of those changes the set of APIs in the binary. As long as you treat the declaration as something you write once at first submission, the drift is structural and recurs. The only way to stop it is to promote the manifest to something you regenerate and validate on every release.
Step 1: Find Which Dependencies Pull in a Required Reason API
The first move isn't hunting for a culprit — it's taking inventory. Apple's covered categories have fixed internal identifiers, so you can scan your build output and the PrivacyInfo.xcprivacy files bundled with each library to see which categories are in play. Run this against the ios/ directory Rork Max emits and your node_modules.
#!/usr/bin/env bash# scan-required-reason.sh# Inventory the Privacy Manifests and covered-category references in the treeset -euo pipefailPROJECT_ROOT="${1:-.}"echo "== PrivacyInfo.xcprivacy shipped by libraries =="find "$PROJECT_ROOT/node_modules" -name "PrivacyInfo.xcprivacy" 2>/dev/null \ | while read -r f; do pkg=$(echo "$f" | sed -E 's#.*/node_modules/(@[^/]+/[^/]+|[^/]+)/.*#\1#') printf '%-45s %s\n' "$pkg" "$f" doneechoecho "== Covered API categories referenced in source/config =="grep -rhoE "NSPrivacyAccessedAPICategory[A-Za-z]+" \ "$PROJECT_ROOT/ios" "$PROJECT_ROOT/node_modules" 2>/dev/null \ | sort | uniq -c | sort -rn
The top block shows libraries that carry their own manifest; the bottom block shows which categories are referenced across the whole project and how often. The frequently appearing categories are exactly the ones you must declare in your app-level manifest. In Rork Max projects I usually see @react-native-async-storage/async-storage and react-native-mmkv pull in UserDefaults, while cache and sync libraries pull in file timestamps.
If you have the rejection email, use the NSPrivacyAccessedAPICategory... values it lists as a confirmed minimum set. Cross-checking the script's inventory against the email leaves almost no gaps.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A shell script that mechanically finds every library in your tree that touches a Required Reason API
✦How to aggregate declarations correctly given app-level vs library-level manifest precedence
✦A local validation flow that catches ITMS-91053 before upload, so you never lose a day to review
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Once you know the categories, assign each one a reason code that matches reality. Misaligning this is itself a policy violation and turns into a different kind of problem. Here are the four categories that come up most in Rork Max apps with their typical sources and reason codes.
API category
Typical source
Common reason code
Meaning
UserDefaults
Persisting settings, tokens, state (AsyncStorage, etc.)
CA92.1
Read/write for the app's own functionality
FileTimestamp
Cache management, sync checks, download control
C617.1
Accessing files inside the app container
DiskSpace
Checking free space before a download
E174.1
Verifying capacity to avoid write failures
SystemBootTime
Measurement, timers, analytics libraries
35F9.1
Measuring elapsed time for in-app events
The principle worth holding onto: don't pick a reason code that "looks about right" — pick the one that matches how your app actually uses it. If an ad SDK is the only thing pulling in DiskSpace and your own code never checks free space, confirm whether E174.1 (a pre-write check) genuinely fits. If it doesn't, look up the reason code that corresponds to the real use in Apple's documentation.
Step 3: Aggregate Declarations in the App-Level Manifest
Even when libraries ship their own PrivacyInfo.xcprivacy, the app-level manifest is the source of truth. Review looks at the set of declarations in the final binary, so it's safest to explicitly declare in the app manifest any category pulled in by your own code or by older libraries that lack a manifest. Aggregate them in ios/PrivacyInfo.xcprivacy like this.
Because Rork Max emits an Expo-based project under the hood, it's better to declare this as an Expo config plugin than to drop the file into ios/ by hand each time — that way it regenerates in the right place on every prebuild. Add the following to app.config.js and your declarations survive even when the native directory is rebuilt.
The advantage of keeping it in config is that the declaration stays under source control even if you .gitignore the ios/ directory. A hand-written PrivacyInfo.xcprivacy tends to get overwritten or wiped on prebuild, so anchoring the declaration in the config file is the more durable choice.
Step 4: Catch It Yourself Before You Upload
This is where the workflow pays off most. ITMS-91053 hurts because it's detected half a day after upload. Pull detection forward to your machine and the round-trip vanishes. There are two ways, and wiring up both is reassuring.
The first is to inspect the aggregated manifest in the .ipa/.xcarchive you built. The script below compares the covered categories referenced in the archive against the categories already declared in your app manifest, and fails if any are undeclared.
#!/usr/bin/env bash# preflight-privacy.sh ARCHIVE_OR_IPA APP_MANIFESTset -euo pipefailTARGET="$1"; MANIFEST="$2"# Categories referenced on the binary sideused=$(grep -rhoE "NSPrivacyAccessedAPICategory[A-Za-z]+" "$TARGET" 2>/dev/null | sort -u)# Categories already declared in the app manifestdeclared=$(grep -oE "NSPrivacyAccessedAPICategory[A-Za-z]+" "$MANIFEST" | sort -u)missing=$(comm -23 <(echo "$used") <(echo "$declared"))if [ -n "$missing" ]; then echo "❌ Undeclared categories found:"; echo "$missing" exit 1fiecho "✅ No missing declarations"
The second is to run Apple's own validation before upload. Use "Validate App" in Xcode Organizer, or xcrun altool --validate-app from the CLI, to run the pre-distribution checks that include ITMS-91053. Inserting this step before eas submit stops the bulk of rejections on your machine.
# Validate locally before distribution (credentials are placeholders)xcrun altool --validate-app \ -f ./build/YourApp.ipa \ -t ios \ --apiKey "YOUR_ASC_KEY_ID" \ --apiIssuer "YOUR_ASC_ISSUER_ID"
If rejections keep repeating after you changed the config, your build cache may still hold the old declaration. Rebuild with eas build --platform ios --clear-cache and validate against the fresh artifact.
Step 5: Turn "Prove It Every Release" Into a System
Finally, make these three jobs run automatically on every release instead of being a one-off. What I actually do on my own indie apps is bind the preflight check to the release build profile. Wire preflight-privacy.sh into an EAS post-build hook or your CI release job, and stop the release if even one category is undeclared.
On top of that, include the privacyManifests block in app.config.js in code review, and make "did you run the Step 1 inventory script?" a checklist item on any PR that adds a new library. That way the person adding the dependency notices the drift on the spot. Running npx expo-doctor before release also helps, since it surfaces missing library-side manifests early.
Once it's a system, ITMS-91053 changes character — from "an occasional nuisance" to "a check that always turns green before upload." Apple has signaled it will keep expanding the Required Reason API scope, so the more you lean on regenerating and re-validating declarations every release, the more automatically you'll keep up with future requirements.
There's one thing you can do today: run scan-required-reason.sh on your project and check that every category it surfaces is present in the privacyManifests block of app.config.js. Any difference you find is the seed of your next rejection — better to pull it now. Thanks for reading.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.