144 lines
13 KiB
Markdown
144 lines
13 KiB
Markdown
---
|
|
name: review-rejections
|
|
description: Triage and answer an App Store or Google Play rejection, policy warning or strike for a mobile app or game - decide fix / clarify / push back / appeal, write a short Resolution Center reply, and map the message to the usual guidelines (Apple 2.1, 2.3, 3.1.1, 4.2, 4.3, 5.1.1, 5.1.2, privacy label mismatch; Play Data safety mismatch, disruptive ads, Families, strikes and appeals). Use when the user pastes a rejection or policy email, an update is "Rejected" / "Metadata Rejected", or Play Console shows a policy issue.
|
|
---
|
|
|
|
# Review rejections and policy issues
|
|
|
|
Read the full message first (App Store Connect > the app > App Review > the submission; Play Console > Policy status /
|
|
Inbox). Quote the guideline number and the reviewer's exact sentence back to the owner before proposing anything.
|
|
Never suggest hiding behaviour from reviewers, different builds for review, fake accounts or other manipulative tactics:
|
|
they turn a rejection into an account termination.
|
|
|
|
## 0. Before the next submission: `storebound scan`
|
|
`storebound scan` (MCP `scan`, read-only, offline) reads the project itself and lists the rejections the files can
|
|
prove, with guideline, evidence (file:line), fix and skill: 2.1(a) demo account and placeholder text, 5.1.1(v) account
|
|
deletion, 4.8 Sign in with Apple, 3.1.1 Restore Purchases and outside payment, 3.1.2 subscription info, 5.1.2 ATT,
|
|
5.1.1(ii) purpose strings, 5.1.1(i) privacy policy link, privacy manifest (ITMS-91053 / 91061), 2.3.10 other
|
|
platforms, 2.3.7 keywords, 2.3.8 "For Kids", 4.2 web wrappers, 2.5.1 UIWebView and private URL schemes, 2.5.5 IPv6,
|
|
ATS, export compliance, 1.3 kids ads, 5.6.1 / 3.2.2(x) review prompts, privacy label vs SDKs. On Google Play: target
|
|
SDK, delete-account URL, App access, package name, the restricted permissions and their Console declarations
|
|
(background location, photo / video, All files access, AccessibilityService, REQUEST_INSTALL_PACKAGES, Health Connect,
|
|
full-screen intent, VpnService, foreground services, exact alarms, QUERY_ALL_PACKAGES, SMS / call log), prominent
|
|
disclosure and the in-app privacy policy (User Data), Data safety vs the SDKs' disclosures, Families (certified ad SDKs,
|
|
AD_ID, location) from `play.targetAges`, the AD_ID permission, 16 KB page sizes (Unity, React Native, NDK, AGP, prebuilt
|
|
.so), the closed-testing requirement of new personal accounts, Play metadata (title length, emoji, caps, claims,
|
|
keyword repetition, other apps), interstitials at launch, payments outside Play Billing and brand names in the package
|
|
(low "check"). When the project was built locally it also reads the merged manifest under build/intermediates. After
|
|
a rejection, run it too: the same cause often hides elsewhere. A finding the owner decided is fine goes
|
|
into `scan.ignore` with the reason (never silence one yourself).
|
|
|
|
Checked at review, not by scan (check these yourself in the app and the listing): crashes and hangs on the reviewer's device (2.1; `store-health`),
|
|
links that are dead at review time (`storebound validate` checks the configured URLs), screenshots and description
|
|
that do not match the app (2.3, `store-screenshots`), poor or unfinished UI (4.0), "too simple" and spam / template
|
|
apps (4.2, 4.3; `portfolio-originality`), IAPs that fail in the sandbox (2.1(b), `store-iap-setup`), privacy policy
|
|
content (5.1.1(i), `privacy-policy`), local law (5.2). Play: whether a restricted permission serves the core purpose
|
|
(the declaration and its video decide), the wording and timing of a prominent disclosure, ad placements in Unity /
|
|
Flutter / React Native code, the Health apps declaration, alignment inside prebuilt .so files.
|
|
|
|
## 1. Triage (pick one, say why)
|
|
| route | when | what you do |
|
|
|---|---|---|
|
|
| **Fix** | the reviewer is right (crash, missing restore button, wrong label) | change the app / metadata, resubmit, reply what changed |
|
|
| **Clarify** | the feature exists but the reviewer did not find it, or misread it | reply with exact steps / a video link; no new build if nothing changed |
|
|
| **Push back** | the guideline does not apply, with a concrete reason | short factual reply quoting the guideline text; offer a call |
|
|
| **Appeal** | a clarification was rejected and you still believe it complies | Apple: App Review Board appeal form; Play: the appeal form linked from the notice |
|
|
|
|
Start with Fix or Clarify; Push back only with evidence; Appeal last. Always ask the owner before sending anything.
|
|
|
|
### Apple: resubmit a fixed build (Invalid Binary, crash, ...)
|
|
The rejected submission stays open with **UNRESOLVED_ISSUES** and keeps its items (the version, REJECTED, and its
|
|
in-app purchases). Resubmit that same submission; never create a second one next to it (Apple then answers 409
|
|
`ITEM_PART_OF_ANOTHER_SUBMISSION`).
|
|
1. Upload the fixed build and note its number (`storebound apple upload`, `apple encryption` if it asks).
|
|
2. `storebound apple status`: the submission with UNRESOLVED_ISSUES, its items and their states.
|
|
3. `storebound apple resubmit --build <n>` (dry run without `--yes`): the plan table shows the build to attach, the
|
|
submission reused, every item in it already or added, which REJECTED items are marked resolved, and the submit.
|
|
An app with a macOS (tvOS, visionOS) version too: it picks the platform whose version was rejected, and asks for
|
|
`--platform ios|macos|...` when both were (pitfall 79).
|
|
4. The owner's yes, then `storebound apple resubmit --build <n> --yes`: build n is attached by its number, missing
|
|
items go in, the REJECTED version item is marked resolved (`PATCH reviewSubmissionItems/{id}` `resolved: true`),
|
|
then it submits. Apple answers every submit with "Version is not ready to be submitted yet" (and the console's
|
|
Resubmit button stays disabled) while any item is still REJECTED; the command reads the items again on that
|
|
answer and stops at once naming the item instead of retrying. A REJECTED in-app purchase or subscription item is
|
|
marked resolved only with `--resolve-iaps`, once what App Review asked for is fixed. With nothing left REJECTED,
|
|
"not ready" is retried with backoff (normal for a few minutes after a build is attached; it gives up after
|
|
10 minutes and a re-run continues).
|
|
An empty draft next to the submission (0 items) cannot be canceled and is harmless: leave it.
|
|
5. Reply in the Resolution Center what changed (template below), then `storebound apple status --wait-review`.
|
|
|
|
## 2. Reply template (Resolution Center / appeal, under 200 words)
|
|
```
|
|
Hello, thank you for the review.
|
|
|
|
Regarding Guideline <n> (<reviewer's point in one line>):
|
|
<What we changed in build <number>> | <Where the feature is: Home > Settings > Restore Purchases>
|
|
<One sentence of evidence: screenshot / video link, or the guideline sentence that applies>
|
|
|
|
<If a new build:> Build <number> is attached to this submission.
|
|
Happy to clarify anything else.
|
|
```
|
|
Plain, polite, specific; one guideline per paragraph; no blame, no threats, no "other apps do it".
|
|
|
|
## 3. Apple guidelines apps and games hit most
|
|
- **2.1 App completeness**: crash on launch / during review (check the crash log in the message, test the review
|
|
device class and OS), placeholder text or art, broken links, IAPs not working in the sandbox (Paid Apps agreement
|
|
active? products READY_TO_SUBMIT and in the same submission?), features behind a login without a demo
|
|
account (`review-access` skill: demo account in `.storebound/credentials.json`, `storebound apple review-details`
|
|
sends it).
|
|
- **2.3 Accurate metadata**: screenshots must show the real app (for a game: real gameplay); no prices, no "free"
|
|
claims for paid items, no other platforms' UI; keywords without competitor names; age rating answers that match
|
|
the content.
|
|
- **2.3.10 Accurate Metadata (other platforms)**: "Android", "Google Play", "Google Play Games" or another store in
|
|
any App Store text, often a What's New copied from notes shared with Play (store-pitfalls 77). Fix: remove the
|
|
lines (tag shared notes `[play]` / `[apple]`, `storebound whats-new apply`), `storebound check` must show no
|
|
`other platform` blocker, push with `apple listing --version x.y.z`, reply "removed the references to other
|
|
platforms" and resubmit. Play's mirror case is the Metadata policy (iOS, App Store, Game Center in Play text).
|
|
- **2.3.1 Hidden or undocumented features** (Play: Deceptive behavior): remote config, feature flags or a remote
|
|
paywall the review notes did not mention (`storebound scan` rule `remote-features`, store-pitfalls 74). Fix: say in
|
|
the notes which SDK changes what remotely; a business-model change needs a new review and a description update.
|
|
- **3.1.1 In-app purchase**: digital goods only through IAP; a visible **Restore Purchases** for non-consumables;
|
|
no links to outside payment; loot boxes must show odds.
|
|
- **3.1.2 Subscriptions**: the purchase screen shows title, length, price per period and what the user gets; links to
|
|
the privacy policy and the Terms of Use (EULA) in the app and in the metadata; free trial terms clear. See
|
|
`store-subscriptions`.
|
|
- **4.2 Minimum functionality**: a thin app (one screen, a tutorial-level loop) or a web wrapper. Add depth (for a game:
|
|
levels, progression, offline play; for an app: native features, offline use, real utility). Explain what the user
|
|
does in the notes.
|
|
- **4.3 Spam**: too similar to your own or others' apps (same template, reskins). See `portfolio-originality`; merge
|
|
ideas instead of shipping near-duplicates. Hard to appeal without real differences.
|
|
- **5.1.1 Data collection**: privacy policy link in the listing **and inside the app**; only collect what you need;
|
|
account deletion in-app if you have accounts (5.1.1(v), `account-deletion` skill).
|
|
- **5.1.2 Data use / ATT**: the App Tracking Transparency prompt before any tracking SDK reads the IDFA, a purpose string
|
|
that says why, no incentive or pressure to allow, no tracking when denied.
|
|
- **Privacy label mismatch**: App Privacy must match what the SDKs really collect (ads / analytics SDK docs list it).
|
|
Update the label in App Store Connect (UI) and Publish; no new build needed for label-only fixes.
|
|
|
|
## 4. Google Play
|
|
- **Data safety mismatch**: Play scans the SDKs in your AAB. Fix the form (`storebound play data-safety`) to include
|
|
what the SDKs collect, or remove the SDK; then resubmit from Publishing overview.
|
|
- **Ads policy / disruptive ads**: no interstitials at launch, on exit or mid-gameplay; close buttons visible; rewarded
|
|
ads only opt-in. Ads must not imitate system UI.
|
|
- **Families**: if the target audience includes children, only Families Self-Certified Ads SDKs (answer/9859768;
|
|
AppLovin left the program), no advertising ID in child-only apps (remove AD_ID with tools:node="remove"), no location
|
|
in child-only apps, a neutral age screen for mixed audiences. If the app is not for children, make sure store art and
|
|
the target audience form say so. Set `play.targetAges` so `storebound scan` checks it.
|
|
- **Sensitive permissions** (background location, photo / video, All files access, AccessibilityService, install
|
|
packages, Health Connect, full-screen intent, VpnService, SMS / call log, QUERY_ALL_PACKAGES): the rejection names the
|
|
permission. Either remove it from the merged manifest (an SDK may add it: tools:node="remove") or fill the App content
|
|
declaration with the core feature and a short video that shows the prominent disclosure and the runtime prompt.
|
|
- **Prominent disclosure / privacy policy (User Data)**: an in-app disclosure right before the permission prompt that
|
|
names the data and its use, an affirmative accept, and a privacy policy linked inside the app and in Play Console.
|
|
- **16 KB page sizes**: Play Console flags native libraries that are not 16 KB aligned; from 2027-02-01 such updates
|
|
cannot be released. Update Unity / React Native / NDK / AGP (scan names the version) and rebuild.
|
|
- **Metadata**: title 30 characters or less, no emoji, ALL CAPS, "best / #1 / free / new / sale" in the title, no
|
|
keyword stuffing or unrelated keywords, no other apps' names (`aso-basics`).
|
|
- **Impersonation**: a name, icon or package that suggests another brand; rename (the package cannot change, so a new
|
|
app may be needed) or show the written permission in the appeal.
|
|
- **Strikes and suspensions**: one policy strike affects the account; repeated strikes can terminate it. Fix first,
|
|
then appeal from the notice with the change described. Do not upload the same app under a new package to dodge it.
|
|
- **Rejected updates**: the previous version stays live; fix and send changes for review again.
|
|
|
|
## 5. After sending
|
|
- `storebound apple status --wait-review` (exit 0 approved, 1 rejected, 7 timeout) or `storebound play status`.
|
|
- Write down guideline, cause and fix in the project's docs so the next app does not repeat it.
|