App Store Rejections: ATT 2.1, Kids Category, ITMS-90683, First IAPs
Claude (Opus 5.5)
Written by Claude (Opus 5.5) and checked by Claude (Fable 5.2), from the repos, their notes and the session transcripts. Earlier Claude sessions built, submitted and fixed the apps; dested made the calls and edited. The first person below is dested's voice.
Marble Hop sat in App Review for 25 days. Then Apple wrote back:
The app uses the AppTrackingTransparency framework, but we are unable to locate the App Tracking Transparency permission request.
The prompt existed. It just never showed up where the reviewer was looking.
This autumn I sent five small Expo apps to Apple: Store Brand Puzzles, Kid Size, Marble Hop, Paint Paws and Squiggletale. Two were rejected, four builds were refused before they reached a reviewer, and App Store Connect's API refused to attach in-app purchases for three of the apps. Both rejected apps were resubmitted and were still waiting on November 8.
Every rejection, its real cause and the fix
| What came back | Guideline or code | Real cause | Fix |
|---|---|---|---|
| Marble Hop: ATT prompt not found | 2.1 Information Needed | ATT fired after the first finished run, and never in the ad-free mode the review notes sent reviewers into. AdMob initialised and preloaded ads at launch, before ATT | Consent, then ATT, then AdMob, on first launch |
| Squiggletale: four data questions | 2.1.0 App Completeness (Kids Category, 1.3) | A Kids Category app whose notes didn't answer them | Answered from the code, same build resubmitted |
| Squiggletale builds 1, 2 and 4: processing failed | ITMS-90683 | No NSPhotoLibraryUsageDescription, pulled in by a dependency |
One honest purpose string |
| Paint Paws build 6: upload refused | No code recorded | No NSMicrophoneUsageDescription. A library references the mic, though the game never uses it |
Added the string |
| First-time IAPs won't attach | API 409 | The API can't submit a first in-app purchase | The web UI's draft submission |
The ATT prompt was there, on a path the reviewer never took
Marble Hop asked for tracking permission after the first finished run, and never in its ad-free mode. My review notes told reviewers to switch that mode on first. So they never saw the prompt, while AdMob was already loading ads behind them.
Apple asked for a recording from a physical device, on a fresh install, showing the prompt before any tracking data is collected.
The fix moves ATT to launch and puts it in front of the ad SDK:
canRequest = (await g.AdsConsent.gatherConsent()).canRequestAds
await askTracking() // waits for AppState 'active' first
if (!canRequest) return false
await mobileAds.initialize() // nothing loads before this lineThe wait matters. If you request ATT before the app is active, iOS answers "denied" and shows no dialog. Paint Paws also asked after the first win. Apple approved it that way, and 1.0.1 moved ATT to launch anyway.
Kids Category apps get four questions before anyone reviews them
Squiggletale is in the Kids Category. About 14 hours after submit, an automated message asked four things and marked the version Rejected, though nothing was wrong with the build:
- Do you use third-party analytics?
- Do you use third-party advertising? If so, link the network's kids policy.
- What data do you share with third parties, why, and where is it stored?
- What other user or device data do you collect, and how is it used?
I answered each one from the code, put the answers in the review notes and resubmitted build 5 unchanged. Name the real third party. Squiggletale sends a story's words to Anthropic to draw it, so the answer says Anthropic, not "an AI service".
ITMS-90683 came from a framework, not my code
Squiggletale only wanted to save a video to Photos. Builds 1 and 2 failed processing: expo-media-library links the full photo-library API, and its photosPermission: false had dropped the read key. Build 3 added it back.
Then I cut the Photos feature for 1.0, removed expo-media-library, and made the archive script fail if any NSPhotoLibrary key came back. Build 4 failed with ITMS-90683 again. ExpoFileSystem.framework, precompiled, references PHPhotoLibrary and PHAsset, and Apple checks the binary, not your intent.
The fix is a string that tells the truth:
infoPlist: {
// ExpoFileSystem links PHPhotoLibrary/PHAsset; processing rejects the binary without this key.
NSPhotoLibraryUsageDescription: `${BRAND.name} never opens your photo library.`,
},The API can't attach a first-time in-app purchase
Paint Paws went in on October 22 with all 8 IAPs. A resubmission the next day carried only the version, while the review notes still said eight were attached. I pulled it to fix that, and inAppPurchaseSubmissions answered 409, "has no pending version for submission", with a draft submission open. Apple then approved 1.0.0 with all 8 left out, so 1.0.1 shipped them.
Squiggletale got FIRST_CONSUMABLE_MUST_BE_SUBMITTED_ON_VERSION from the same endpoint, and its version page had no IAP section at all. What works, in the web UI:
- On each IAP's page, press "Add for Review". The later ones offer "Draft Submission (n)".
- Add the version to the same draft.
- Press "Submit for Review" once the draft lists the version and every IAP.
Removing a version from review and resubmitting drops every attachment. Check the draft panel before you press Submit.
The preflight checks that would have caught each one
I built an App Review gate on October 27: a checklist plus preflight.ts, a grep-based script that also makes read-only App Store Connect calls. It had 28 checks on November 1. Two of the problems below added checks since, so it now has 30, plus 10 App Store Connect checks. It exits 0 with no FAIL, 1 with any FAIL.
| Problem | Check | What it does |
|---|---|---|
| ATT prompt not found | none | Grep can't tell when a prompt fires. att only warns that ATT is present, and demo fails without a review recording. The rule lives in the checklist |
| Kids Category questions | kids-data-answers |
FAIL unless the review notes answer all four |
kids-sdks |
FAIL on a known analytics or ads SDK in a kids app | |
| ITMS-90683 | photo-key |
FAIL with expo-media-library and no photo-library key. WARN with expo-file-system |
purpose-strings |
FAIL on a vague purpose string or a plugin's default | |
| IAPs left out | asc-iap-attached |
FAIL when a version is in review with IAPs not in the submission, the state Paint Paws was in on October 27 |
| Long wait | asc-wait |
WARN after 7 days in review: ask App Review for a call |
A PASS means "found it", not "Apple will accept it". asc-iap-attached also warns while the IAPs sit in a draft submission, which it can't see.
What I check now before pressing Submit
- ATT, if you track at all, runs on first launch, after the app is active and before any ad or analytics SDK starts. Never behind gameplay or a mode your notes send reviewers into.
- A Kids Category app answers Apple's four data questions in its review notes on the first submit.
- Every framework you link gets a purpose string for what it links, even one your app never uses. An honest "never opens your photo library" passes.
- First-time IAPs go through the web UI's draft submission. Look at the draft panel before Submit.