Skip to main content
Glama
README.md
# store-review-check

[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Rules: 40](https://img.shields.io/badge/rules-40-informational.svg)](#rules) [![MCP server](https://img.shields.io/badge/MCP-stdio-black.svg)](#use-it-as-an-mcp-server)

Find App Store and Google Play rejection risks in an iOS, Android, Flutter, React Native or Expo project before you submit it. It runs as a CLI and as an MCP server, so you can use it from a terminal, from CI, or from an AI coding assistant.

```sh
npx store-review-check ./my-app
```

It reads `Info.plist`, entitlements, `PrivacyInfo.xcprivacy`, StoreKit configuration files, `AndroidManifest.xml`, Gradle files and version catalogs, `pubspec.yaml`, `package.json` and Expo `app.json`, plus your store listing text if you provide it. Each finding names the guideline section, links to the official Apple or Google page and says how to fix the problem.

The rule set leans toward problems dating and social apps run into, such as user-generated content without report and block tools, missing account deletion, social login without Sign in with Apple and subscription terms. Most rules apply to any app.

There is no AI in the checks. The same project gives the same report every time, and nothing leaves your machine.

## Contents

- [What it checks](#what-it-checks)
- [Sample output](#sample-output)
- [Install and run](#install-and-run)
- [Store listing data](#store-listing-data)
- [Use it as an MCP server](#use-it-as-an-mcp-server)
- [Rules](#rules)
- [How it works, and what it can't see](#how-it-works-and-what-it-cant-see)
- [Contributing](#contributing)
- [Credits](#credits)

## What it checks

- iOS permissions: if the code or a library uses the camera, photos, location, contacts, microphone, tracking and so on, the matching `NS…UsageDescription` key has to exist and say why. Empty, placeholder and Expo default strings are flagged too.
- iOS privacy manifest: whether the app has `PrivacyInfo.xcprivacy`, and whether your own Swift and Objective-C code uses required reason APIs (UserDefaults, file timestamps, disk space, boot time, active keyboards) without declaring them.
- Accounts: account creation without in-app deletion, the Google Play web deletion link, third-party login without Sign in with Apple, and a missing demo account for reviewers.
- User-generated content: report, block, content filtering, published contact details and terms acceptance, plus an age check for dating apps.
- Payments: subscriptions shorter than seven days, missing restore purchases, missing terms and price details, third-party payment SDKs for digital goods, and an outdated Play Billing Library.
- Android platform: `targetSdk` against the current Google Play requirement, restricted permissions (SMS, call log, all-files access and others), photo and video permissions, background location, and sensitive permissions with no disclosure screen.
- Store listing: length limits, price and ranking claims, other apps' names in keywords, mentions of other platforms, emoji and ALL CAPS in Play titles, and wording tied to hookup or anonymous chat apps.

## Sample output

Output of a run against the risky Flutter dating app in [`test/fixtures/flutter-dating-risky`](test/fixtures/flutter-dating-risky), errors only:

```text
$ npx store-review-check test/fixtures/flutter-dating-risky --min-severity error
store-review-check 0.1.0 · rules 2026.10.15
Project: test/fixtures/flutter-dating-risky
Platforms: ios, android (flutter)
Listing data: store-review.yaml
Dating app: yes (app.category is "dating")
User content: yes (dating apps show profiles, photos and messages)
Accounts: yes (dependency firebase_auth (pubspec.yaml), lib/main.dart)

ERRORS (9)
  ✖ android.billing-library  Google Play Billing Library version is no longer accepted
      com.android.billingclient:billing:6.2.1 in android/app/build.gradle. Play requires Billing Library 8 or later since 2026-08-31 (extension until 2026-11-01).
      Files: android/app/build.gradle
      Fix: Update com.android.billingclient:billing (or billing-ktx) to 8.x or newer. If billing comes from a plugin (in_app_purchase, RevenueCat, react-native-iap), update the plugin to a release built on PBL 8+.
      See: Google Play · Google Play Billing Library version deprecation · https://developer.android.com/google/play/billing/deprecation-faq
  ✖ android.target-sdk  targetSdk is below the current Google Play requirement
      targetSdk is 34 (android/app/build.gradle). Google Play requires 36 or higher for new apps and updates since 2026-08-31 (extension requests until 2026-11-01). At 34 an already published app is also hidden from new users on newer Android versions. Wear OS and Android Automotive OS apps need API 35; Android TV and Android XR apps need API 34.
      Files: android/app/build.gradle
      Fix: Raise targetSdk (and compileSdk) to 36 in the app module's build.gradle, then test the Android 15 and 16 behavior changes.
      See: Google Play · Meet Google Play's target API level requirement · https://developer.android.com/google/play/requirements/target-sdk
           Google Play · Target API level requirements for Google Play apps · https://support.google.com/googleplay/android-developer/answer/11926878
  ✖ ios.privacy-manifest-required-reason  Required reason API used but not declared in the privacy manifest
      User defaults APIs are used in ios/Runner/AppDelegate.swift but NSPrivacyAccessedAPICategoryUserDefaults is not declared in the app (no PrivacyInfo.xcprivacy exists yet).
      Files: ios/Runner/AppDelegate.swift
      Fix: Add a dictionary to NSPrivacyAccessedAPITypes with NSPrivacyAccessedAPIType set to the category and NSPrivacyAccessedAPITypeReasons set to the approved reason code that matches your use (for example CA92.1 for UserDefaults read and written only by the app itself).
      See: Apple · Describing use of required reason API · https://developer.apple.com/documentation/bundleresources/describing-use-of-required-reason-api
           Apple · NSPrivacyAccessedAPIType · https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitype
  ✖ ios.purpose-string-missing  Protected resource used without a purpose string
      - Camera is used (dependency image_picker (pubspec.yaml)) but NSCameraUsageDescription is missing from ios/Runner/Info.plist.
        Files: ios/Runner/Info.plist, pubspec.yaml
      - Location (while in use) is used (dependency geolocator (pubspec.yaml)) but NSLocationWhenInUseUsageDescription is missing from ios/Runner/Info.plist.
        Files: ios/Runner/Info.plist, pubspec.yaml
      Fix: Add the key to Info.plist (or to INFOPLIST_KEY_* build settings, or expo.ios.infoPlist) with one specific sentence: what the app uses it for, e.g. "Your camera is used to take profile photos and verify your identity."
      See: Apple 5.1.1(ii) · Data Collection and Storage: Permission · https://developer.apple.com/app-store/review/guidelines/#5.1.1
           Apple · Requesting access to protected resources · https://developer.apple.com/documentation/uikit/requesting-access-to-protected-resources
  ✖ ios.subscription-min-period  Auto-renewable subscription shorter than seven days
      Subscription com.example.amoryx.boost3day renews every P3D (3 days) in ios/Runner/Products.storekit.
      Files: ios/Runner/Products.storekit
      Fix: Use a period of one week or longer. For short boosts (a common dating-app product) sell a consumable in-app purchase instead.
      See: Apple 3.1.2(a) · Subscriptions: Permissible uses · https://developer.apple.com/app-store/review/guidelines/#3.1.2a
  ✖ listing.field-limits  Store listing field is over the length limit
      appStore.name is 38 characters; the limit is 30.
      Fix: Shorten the field. App Store Connect and Play Console will not save values over the limit.
      See: Apple 2.3.7 · Accurate Metadata: app names · https://developer.apple.com/app-store/review/guidelines/#2.3.7
           Apple · App information (name, subtitle) · https://developer.apple.com/help/app-store-connect/reference/app-information/app-information
           Apple · Platform version information (keywords, description, promotional text) · https://developer.apple.com/help/app-store-connect/reference/app-information/platform-version-information
           Google Play · Best practice for your store listing · https://support.google.com/googleplay/android-developer/answer/13393723
  ✖ listing.privacy-policy  No privacy policy URL in the store listing data
      links.privacyPolicyUrl is not set.
      Fix: Publish a privacy policy that covers what you collect, who you share it with, retention and how to delete data. Set links.privacyPolicyUrl in store-review.yaml and link it in the app's settings.
      See: Apple 5.1.1(i) · Data Collection and Storage: Privacy Policies · https://developer.apple.com/app-store/review/guidelines/#5.1.1
           Google Play · Provide information for Google Play's Data safety section · https://support.google.com/googleplay/android-developer/answer/10787469
  ✖ listing.prohibited-dating-terms  Listing uses terms tied to hookup or compensated dating
      appStore.description contains "hookup".
      Fix: Remove these terms from the name, subtitle, keywords and descriptions, and make sure the product itself does not offer what they describe.
      See: Apple 1.1.4 · Objectionable Content: overtly sexual material · https://developer.apple.com/app-store/review/guidelines/#1.1.4
           Google Play · Inappropriate Content: sexual content · https://support.google.com/googleplay/android-developer/answer/9878810

Summary: 9 errors, 22 warnings, 5 info · 40 rules checked, 0 not applicable · 9 files scanned
27 findings below --min-severity not shown.
Static checks against public App Store and Google Play guidelines. A clean report does not guarantee approval: review is done by people, and policies change. Treat findings as a checklist, not a verdict.
```

<details>
<summary>Full output with warnings and info (same project)</summary>

```text
$ npx store-review-check test/fixtures/flutter-dating-risky
store-review-check 0.1.0 · rules 2026.10.15
Project: test/fixtures/flutter-dating-risky
Platforms: ios, android (flutter)
Listing data: store-review.yaml
Dating app: yes (app.category is "dating")
User content: yes (dating apps show profiles, photos and messages)
Accounts: yes (dependency firebase_auth (pubspec.yaml), lib/main.dart)

ERRORS (9)
  ✖ android.billing-library  Google Play Billing Library version is no longer accepted
      com.android.billingclient:billing:6.2.1 in android/app/build.gradle. Play requires Billing Library 8 or later since 2026-08-31 (extension until 2026-11-01).
      Files: android/app/build.gradle
      Fix: Update com.android.billingclient:billing (or billing-ktx) to 8.x or newer. If billing comes from a plugin (in_app_purchase, RevenueCat, react-native-iap), update the plugin to a release built on PBL 8+.
      See: Google Play · Google Play Billing Library version deprecation · https://developer.android.com/google/play/billing/deprecation-faq
  ✖ android.target-sdk  targetSdk is below the current Google Play requirement
      targetSdk is 34 (android/app/build.gradle). Google Play requires 36 or higher for new apps and updates since 2026-08-31 (extension requests until 2026-11-01). At 34 an already published app is also hidden from new users on newer Android versions. Wear OS and Android Automotive OS apps need API 35; Android TV and Android XR apps need API 34.
      Files: android/app/build.gradle
      Fix: Raise targetSdk (and compileSdk) to 36 in the app module's build.gradle, then test the Android 15 and 16 behavior changes.
      See: Google Play · Meet Google Play's target API level requirement · https://developer.android.com/google/play/requirements/target-sdk
           Google Play · Target API level requirements for Google Play apps · https://support.google.com/googleplay/android-developer/answer/11926878
  ✖ ios.privacy-manifest-required-reason  Required reason API used but not declared in the privacy manifest
      User defaults APIs are used in ios/Runner/AppDelegate.swift but NSPrivacyAccessedAPICategoryUserDefaults is not declared in the app (no PrivacyInfo.xcprivacy exists yet).
      Files: ios/Runner/AppDelegate.swift
      Fix: Add a dictionary to NSPrivacyAccessedAPITypes with NSPrivacyAccessedAPIType set to the category and NSPrivacyAccessedAPITypeReasons set to the approved reason code that matches your use (for example CA92.1 for UserDefaults read and written only by the app itself).
      See: Apple · Describing use of required reason API · https://developer.apple.com/documentation/bundleresources/describing-use-of-required-reason-api
           Apple · NSPrivacyAccessedAPIType · https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitype
  ✖ ios.purpose-string-missing  Protected resource used without a purpose string
      - Camera is used (dependency image_picker (pubspec.yaml)) but NSCameraUsageDescription is missing from ios/Runner/Info.plist.
        Files: ios/Runner/Info.plist, pubspec.yaml
      - Location (while in use) is used (dependency geolocator (pubspec.yaml)) but NSLocationWhenInUseUsageDescription is missing from ios/Runner/Info.plist.
        Files: ios/Runner/Info.plist, pubspec.yaml
      Fix: Add the key to Info.plist (or to INFOPLIST_KEY_* build settings, or expo.ios.infoPlist) with one specific sentence: what the app uses it for, e.g. "Your camera is used to take profile photos and verify your identity."
      See: Apple 5.1.1(ii) · Data Collection and Storage: Permission · https://developer.apple.com/app-store/review/guidelines/#5.1.1
           Apple · Requesting access to protected resources · https://developer.apple.com/documentation/uikit/requesting-access-to-protected-resources
  ✖ ios.subscription-min-period  Auto-renewable subscription shorter than seven days
      Subscription com.example.amoryx.boost3day renews every P3D (3 days) in ios/Runner/Products.storekit.
      Files: ios/Runner/Products.storekit
      Fix: Use a period of one week or longer. For short boosts (a common dating-app product) sell a consumable in-app purchase instead.
      See: Apple 3.1.2(a) · Subscriptions: Permissible uses · https://developer.apple.com/app-store/review/guidelines/#3.1.2a
  ✖ listing.field-limits  Store listing field is over the length limit
      appStore.name is 38 characters; the limit is 30.
      Fix: Shorten the field. App Store Connect and Play Console will not save values over the limit.
      See: Apple 2.3.7 · Accurate Metadata: app names · https://developer.apple.com/app-store/review/guidelines/#2.3.7
           Apple · App information (name, subtitle) · https://developer.apple.com/help/app-store-connect/reference/app-information/app-information
           Apple · Platform version information (keywords, description, promotional text) · https://developer.apple.com/help/app-store-connect/reference/app-information/platform-version-information
           Google Play · Best practice for your store listing · https://support.google.com/googleplay/android-developer/answer/13393723
  ✖ listing.privacy-policy  No privacy policy URL in the store listing data
      links.privacyPolicyUrl is not set.
      Fix: Publish a privacy policy that covers what you collect, who you share it with, retention and how to delete data. Set links.privacyPolicyUrl in store-review.yaml and link it in the app's settings.
      See: Apple 5.1.1(i) · Data Collection and Storage: Privacy Policies · https://developer.apple.com/app-store/review/guidelines/#5.1.1
           Google Play · Provide information for Google Play's Data safety section · https://support.google.com/googleplay/android-developer/answer/10787469
  ✖ listing.prohibited-dating-terms  Listing uses terms tied to hookup or compensated dating
      appStore.description contains "hookup".
      Fix: Remove these terms from the name, subtitle, keywords and descriptions, and make sure the product itself does not offer what they describe.
      See: Apple 1.1.4 · Objectionable Content: overtly sexual material · https://developer.apple.com/app-store/review/guidelines/#1.1.4
           Google Play · Inappropriate Content: sexual content · https://support.google.com/googleplay/android-developer/answer/9878810

WARNINGS (22)
  ▲ account.deletion  Account creation without in-app account deletion
      Accounts are created (dependency firebase_auth (pubspec.yaml), lib/main.dart) but no delete-account flow was found in source or strings.
      Fix: Add Settings > Delete account that deletes the account and its data server-side (not just deactivation). If deletion lives in a WebView or on the server, declare it with features.accountDeletionInApp: true in store-review.yaml.
      See: Apple 5.1.1(v) · Data Collection and Storage: Account Sign-In · https://developer.apple.com/app-store/review/guidelines/#5.1.1v
           Apple · Offering account deletion in your app · https://developer.apple.com/support/offering-account-deletion-in-your-app/
           Google Play · Understanding Google Play's app account deletion requirements · https://support.google.com/googleplay/android-developer/answer/13327111
  ▲ account.deletion-web-link  No web link for account deletion (Google Play)
      No links.accountDeletionUrl in the store listing data. Google Play asks for this link in the Data safety form.
      Fix: Publish a page (for example https://example.com/delete-account) that explains the steps and accepts requests, then set links.accountDeletionUrl in store-review.yaml and add it in Play Console.
      See: Google Play · Understanding Google Play's app account deletion requirements · https://support.google.com/googleplay/android-developer/answer/13327111
  ▲ android.background-location  ACCESS_BACKGROUND_LOCATION declared
      ACCESS_BACKGROUND_LOCATION declared in android/app/src/main/AndroidManifest.xml.
      Files: android/app/src/main/AndroidManifest.xml
      Fix: Request location only while the app is in use. If background access is truly core, prepare the declaration form and a video of the disclosure flow.
      See: Google Play · Location Permissions · https://support.google.com/googleplay/android-developer/answer/9799150
  ▲ android.permission-rationale  Sensitive runtime permissions with no disclosure or rationale found
      Declared: CAMERA, ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION, READ_CONTACTS, RECEIVE_SMS, READ_MEDIA_IMAGES. No rationale or disclosure code found (shouldShowRequestPermissionRationale, a rationale dialog, or permission_handler's shouldShowRequestRationale).
      Files: android/app/src/main/AndroidManifest.xml
      Fix: Before each runtime request, show a screen that says what data you access and why, and handle the shouldShowRequestPermissionRationale case. Request permissions in context (camera when the user taps Add photo), not all at launch.
      See: Google Play · User Data: Prominent Disclosure & Consent Requirement · https://support.google.com/googleplay/android-developer/answer/10144311
           Google Play · Request runtime permissions · https://developer.android.com/training/permissions/requesting
  ▲ android.photo-video-permissions  READ_MEDIA_IMAGES / READ_MEDIA_VIDEO declared
      READ_MEDIA_IMAGES declared in android/app/src/main/AndroidManifest.xml.
      Files: android/app/src/main/AndroidManifest.xml
      Fix: Use the Android Photo Picker (androidx.activity 1.7.0+ PickVisualMedia; Flutter image_picker and Expo ImagePicker use it) and remove the permissions. If a library adds them through manifest merging, remove them with tools:node="remove".
      See: Google Play · Photo and Video Permissions (restricted permissions with minimum scope alternatives) · https://support.google.com/googleplay/android-developer/answer/14115180
  ▲ android.restricted-permissions  Restricted permission declared
      RECEIVE_SMS declared in android/app/src/main/AndroidManifest.xml. SMS and Call Log permissions are limited to default SMS/phone handlers and listed exceptions. For one-time codes use the SMS Retriever or SMS User Consent API.
      Files: android/app/src/main/AndroidManifest.xml
      Fix: Remove the permission (check merged manifests from libraries too), or prepare the declaration for the approved use case listed in the policy.
      See: Google Play · Permissions and APIs that Access Sensitive Information · https://support.google.com/googleplay/android-developer/answer/9888170
  ▲ dating.age-gate  Dating app with no age check found
      Dating app (app.category is "dating") with no date-of-birth or age check found.
      Fix: Ask for date of birth at sign-up and block under-18s (or use Apple's Declared Age Range API), state the minimum age in your terms, and declare it as age assurance in the rating questionnaires. Set features.ageGate: true when done.
      See: Apple 2.3.6 · Accurate Metadata: age rating · https://developer.apple.com/app-store/review/guidelines/#2.3.6
           Apple · Age ratings values and definitions · https://developer.apple.com/help/app-store-connect/reference/app-information/age-ratings-values-and-definitions
           Google Play · Content Ratings · https://support.google.com/googleplay/android-developer/answer/9898843
           Google Play · User Generated Content (incidental sexual content) · https://support.google.com/googleplay/android-developer/answer/9876937
  ▲ iap.subscription-disclosure  Subscription terms, price or legal links missing
      Subscriptions found: no links.termsOfUseUrl (Apple requires a Terms of Use link in the app and in App Store metadata); no plans listed under subscriptions, so price and period cannot be checked.
      Fix: Set links.termsOfUseUrl and links.privacyPolicyUrl, list each plan under subscriptions (price, period, trial) in store-review.yaml, and put the same facts on the paywall next to the buy button.
      See: Apple 3.1.2(c) · Subscriptions: Subscription Information · https://developer.apple.com/app-store/review/guidelines/#3.1.2c
           Apple · Auto-renewable subscriptions · https://developer.apple.com/app-store/subscriptions/
           Google Play · Subscriptions · https://support.google.com/googleplay/android-developer/answer/9900533
  ▲ iap.third-party-payments  Third-party payment SDK in an app that sells digital features
      Found dependency flutter_stripe (pubspec.yaml). Make sure it only charges for physical goods or real-world services. This is a dating app, so premium features count as digital goods.
      Files: pubspec.yaml
      Fix: Sell digital features through StoreKit / Play Billing. Keep Stripe or PayPal only for physical goods or real-world services, or follow the specific external-link programs for the storefronts you target.
      See: Apple 3.1.1 · In-App Purchase · https://developer.apple.com/app-store/review/guidelines/#3.1.1
           Google Play · Payments · https://support.google.com/googleplay/android-developer/answer/9858738
  ▲ ios.privacy-manifest-missing  No app privacy manifest (PrivacyInfo.xcprivacy)
      No PrivacyInfo.xcprivacy found for the app target. Expected something like ios/Runner/PrivacyInfo.xcprivacy.
      Fix: In Xcode choose File > New File > App Privacy, add it to the app target, and declare collected data types and each required reason API category your own code uses. Flutter: ios/Runner/PrivacyInfo.xcprivacy. Expo: expo.ios.privacyManifests in app.json.
      See: Apple · Privacy manifest files · https://developer.apple.com/documentation/bundleresources/privacy-manifest-files
           Apple · Describing use of required reason API · https://developer.apple.com/documentation/bundleresources/describing-use-of-required-reason-api
  ▲ ios.purpose-string-quality  Purpose string is empty, a placeholder or too vague
      NSPhotoLibraryUsageDescription is empty or a placeholder ("Photos") in ios/Runner/Info.plist.
      Files: ios/Runner/Info.plist
      Fix: Rewrite the string as a full sentence that names the feature, e.g. "Location is used to show people near you and the distance to them."
      See: Apple 5.1.1(ii) · Data Collection and Storage: Permission · https://developer.apple.com/app-store/review/guidelines/#5.1.1
  ▲ ios.restore-purchases  In-app purchases without a restore mechanism
      In-app purchases found (dependency purchases_flutter (pubspec.yaml)) but no restore call (AppStore.sync, restorePurchases, restoreCompletedTransactions).
      Files: pubspec.yaml
      Fix: Add a visible "Restore purchases" button that calls AppStore.sync() (StoreKit 2), restoreCompletedTransactions() (StoreKit 1) or your SDK's restore method (RevenueCat restorePurchases, in_app_purchase restorePurchases).
      See: Apple 3.1.1 · In-App Purchase · https://developer.apple.com/app-store/review/guidelines/#3.1.1
           Apple · Auto-renewable subscriptions: clearly describing subscriptions · https://developer.apple.com/app-store/subscriptions/
  ▲ ios.sign-in-with-apple  Third-party login without an equivalent privacy-friendly option
      Third-party login found (dependency google_sign_in (pubspec.yaml), Info.plist key GIDClientID (ios/Runner/Info.plist)) and no Sign in with Apple or other 4.8-compliant option.
      Files: pubspec.yaml, ios/Runner/Info.plist
      Fix: Add Sign in with Apple next to the other social buttons (entitlement com.apple.developer.applesignin plus the ASAuthorization flow), or confirm that one of the exceptions in 4.8 applies to your app.
      See: Apple 4.8 · Login Services · https://developer.apple.com/app-store/review/guidelines/#4.8
  ▲ ios.tracking-without-att  Ad or attribution SDK present, no App Tracking Transparency prompt found
      Found dependency google_mobile_ads (pubspec.yaml), but no App Tracking Transparency request and no NSUserTrackingUsageDescription.
      Files: pubspec.yaml
      Fix: If any SDK tracks users (IDFA, cross-app attribution), request permission with ATTrackingManager.requestTrackingAuthorization and add NSUserTrackingUsageDescription. If none does, configure the SDKs not to track and answer the App Privacy questions to match.
      See: Apple 5.1.2(i) · Data Use and Sharing · https://developer.apple.com/app-store/review/guidelines/#5.1.2
           Apple · App Tracking Transparency · https://developer.apple.com/documentation/apptrackingtransparency
  ▲ listing.competitor-names  Other app names in the App Store keywords or name
      appStore.keywords mentions "tinder", "bumble".
      Fix: Remove other apps' names from the name, subtitle and keywords; describe what your app does instead.
      See: Apple 2.3.7 · Accurate Metadata: names and keywords · https://developer.apple.com/app-store/review/guidelines/#2.3.7
  ▲ listing.other-platform-mentions  App Store listing mentions Android or Google Play
      appStore.description contains "Android", "Google Play".
      Fix: Remove mentions such as "also on Android" or "Google Play" from the App Store name, subtitle, keywords, description and promotional text.
      See: Apple 2.3.10 · Accurate Metadata: other platforms · https://developer.apple.com/app-store/review/guidelines/#2.3.10
  ▲ listing.promo-terms  Price, promotion or ranking claims in the name, subtitle, title or keywords
      - appStore.name contains "Free".
      - appStore.keywords contains "free".
      Fix: Move prices and offers into the paywall and promotional text; keep the title to the brand plus a plain description.
      See: Apple 2.3.7 · Accurate Metadata · https://developer.apple.com/app-store/review/guidelines/#2.3.7
           Google Play · Metadata · https://support.google.com/googleplay/android-developer/answer/9898842
  ▲ listing.title-format  Google Play title uses emoji, repeated symbols or ALL CAPS
      googlePlay.title "AMORYX 💘 DATING!!" uses emoji, repeated special characters, ALL CAPS (fine only if that is the brand name).
      Fix: Write the title in normal case without emoji or decorative symbols.
      See: Google Play · Metadata · https://support.google.com/googleplay/android-developer/answer/9898842
  ▲ review.demo-account  Login required, but no demo account for reviewers
      The app has a login (dependency firebase_auth (pubspec.yaml), lib/main.dart). Set review.demoAccountProvided: true once a reviewer account is entered in both consoles.
      Fix: Create a reviewer account with a finished profile, a few matches and a chat, enter it in App Review Information / App access, and set review.demoAccountProvided: true in store-review.yaml.
      See: Apple 2.1(a) · App Completeness · https://developer.apple.com/app-store/review/guidelines/#2.1
           Google Play · Prepare your app for review (App access) · https://support.google.com/googleplay/android-developer/answer/9859455
  ▲ ugc.moderation  User-generated content without report, block, filter or contact info
      No sign of filtering of objectionable text and photos, a way to report users and content, a way to block users in source or strings. User content detected: dating apps show profiles, photos and messages.
      Fix: Add Report and Block on every profile and in every chat, filter text and photos (word list plus a moderation API), and publish a support contact. If these live on the server or in a WebView, declare them under features in store-review.yaml.
      See: Apple 1.2 · User-Generated Content · https://developer.apple.com/app-store/review/guidelines/#1.2
           Google Play · User Generated Content · https://support.google.com/googleplay/android-developer/answer/9876937
  ▲ ugc.terms-acceptance  No terms of use that users accept before posting content
      No terms of use link (links.termsOfUseUrl) and no terms acceptance step found in source or strings.
      Fix: Show the terms (with a zero-tolerance line on objectionable content and abusive users) at sign-up and require a tap to accept before the first profile photo or message. Set links.termsOfUseUrl in store-review.yaml.
      See: Google Play · User Generated Content · https://support.google.com/googleplay/android-developer/answer/9876937

INFO (5)
  ● android.contacts-permission  READ_CONTACTS declared (new Contacts Permission policy)
      READ_CONTACTS declared in android/app/src/main/AndroidManifest.xml.
      Files: android/app/src/main/AndroidManifest.xml
      Fix: Use the Contact Picker for invite-a-friend flows. Keep READ_CONTACTS only if a core feature needs the full list, and prepare the declaration.
      See: Google Play · Contacts Permission (restricted permissions with minimum scope alternatives) · https://support.google.com/googleplay/android-developer/answer/14115180
  ● android.location-minimum-scope  Precise location declared; approximate may be enough
      ACCESS_FINE_LOCATION declared in android/app/src/main/AndroidManifest.xml. Check whether approximate location is enough.
      Files: android/app/src/main/AndroidManifest.xml
      Fix: Declare only ACCESS_COARSE_LOCATION unless a feature needs precise location, and say which feature in your Data safety answers.
      See: Google Play · Location Permissions · https://support.google.com/googleplay/android-developer/answer/9799150
  ● ios.saturated-category  Dating is a category Apple calls well established
      Detected as a dating app (app.category is "dating"). Expect App Review to ask what makes it different from existing dating apps.
      Fix: In the App Review notes, describe in two or three sentences what your app does that existing dating apps don't (audience, matching model, safety features). Don't ship several near-identical dating apps from one account.
      See: Apple 4.3(b) · Spam · https://developer.apple.com/app-store/review/guidelines/#4.3
  ● listing.age-rating-dating  Age rating answers to review for a dating app
      Dating app (app.category is "dating"): review the user-generated content, messaging and chat, age assurance and mature themes answers.
      Fix: Answer yes to user-generated content and messaging, describe your age assurance, and pick the frequency of mature themes honestly. Keep screenshots free of suggestive photos.
      See: Apple 2.3.6 · Accurate Metadata: age rating · https://developer.apple.com/app-store/review/guidelines/#2.3.6
           Apple 2.3.8 · Accurate Metadata: appropriate for all audiences · https://developer.apple.com/app-store/review/guidelines/#2.3.8
           Apple · Age ratings values and definitions · https://developer.apple.com/help/app-store-connect/reference/app-information/age-ratings-values-and-definitions
           Google Play · Content Ratings · https://support.google.com/googleplay/android-developer/answer/9898843
  ● privacy.data-safety-hints  Data types to declare in Data safety and App Privacy
      Likely data types: Location: Precise location; Location: Approximate location; Photos and videos: Photos; Contacts: Contacts; Messages: Other in-app messages; Personal info: Name, Email address, User IDs; Personal info: Other info (profile details such as date of birth and gender), and Sexual orientation or Political or religious beliefs if the profile asks for them; Device or other IDs; Financial info: Purchase history; Financial info: User payment info.
      Fix: Compare this list with your Data safety form and App Privacy answers. Set googlePlay.dataSafety.dataTypes in store-review.yaml to have this check compare them for you.
      See: Google Play · Provide information for Google Play's Data safety section · https://support.google.com/googleplay/android-developer/answer/10787469
           Apple · App privacy details on the App Store · https://developer.apple.com/app-store/app-privacy-details/

Summary: 9 errors, 22 warnings, 5 info · 40 rules checked, 0 not applicable · 9 files scanned
Static checks against public App Store and Google Play guidelines. A clean report does not guarantee approval: review is done by people, and policies change. Treat findings as a checklist, not a verdict.
```

</details>

## Install and run

You need Node.js 22 or newer. No install is needed with `npx`:

```sh
npx store-review-check                 # current folder
npx store-review-check ./apps/mobile   # a subfolder of a monorepo
npx store-review-check . --json > store-review-report.json
npx store-review-check rules --tag dating
```

Or add it to the project:

```sh
npm install --save-dev store-review-check
```

| Option | What it does |
| --- | --- |
| `-p, --platform ios\|android\|all` | Check one store only. Default: every platform found. |
| `-m, --metadata <file>` | Store listing data. Default: `store-review.yaml` in the project root. |
| `--dating` | Run the dating-app checks even if nothing in the project says it is one. |
| `--json` | Print the JSON report. |
| `-o, --output <file>` | Also write the report to a file. A `.json` name writes JSON. |
| `--min-severity error\|warning\|info` | Hide findings below this level. They still count in the summary. |
| `--fail-on error\|warning\|info\|never` | Exit with 1 when there are findings at this level. Default: `error`. |
| `-v, --verbose` | List the rules that did not apply and why. |
| `--no-color` | Plain output. `NO_COLOR` works too. |

Exit codes: `0` when nothing reaches the `--fail-on` level, `1` when something does, `2` for a usage error or a folder that doesn't exist.

### In CI

```yaml
# .github/workflows/store-review.yml
name: Store review check
on: [pull_request]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 22
      - run: npx store-review-check . --fail-on error
```

### As a library

```js
import { checkProject, checkListing, formatText } from 'store-review-check';

const report = checkProject('./my-app', { platform: 'ios' });
console.log(report.summary, report.findings);
console.log(formatText(report));
```

## Store listing data

Some rules need facts that aren't in the code: your listing text, your privacy policy link, or the fact that reporting lives on your server. Put them in `store-review.yaml` (or `.json`) in the project root. Every field is optional.

```yaml
app:
  name: Shelfmate
  category: dating            # "dating", "matchmaking" or "matrimony" turns on the dating checks
  hasAccounts: true
  hasUserGeneratedContent: true
  hasChat: true

links:
  privacyPolicyUrl: https://example.com/privacy
  termsOfUseUrl: https://example.com/terms
  supportUrl: https://example.com/support
  contactEmail: support@example.com
  accountDeletionUrl: https://example.com/delete-account   # Google Play asks for this

appStore:
  name: Shelfmate
  subtitle: Slow dating for book lovers
  keywords: books,reading,slow dating,book club
  description: Meet people through the books you love.
  ageRating: 18+

googlePlay:
  title: Shelfmate
  shortDescription: Meet people through the books you love.
  fullDescription: Every match starts with a shared favorite book.
  dataSafety:
    dataTypes: [Location, Photos and videos, Messages, Personal info]

# Things the code scan can't see, such as server-side moderation.
features:
  accountDeletionInApp: true
  reportContent: true
  blockUsers: true
  contentFiltering: true
  ageGate: true
  restorePurchases: true
  termsAcceptance: true
  permissionDisclosure: true

review:
  demoAccountProvided: true

subscriptions:
  - productId: com.example.shelfmate.premium.monthly
    name: Premium monthly
    price: $12.99
    period: 1 month
    trial: 7 days free
```

If the project uses fastlane, the tool also reads `fastlane/metadata` (App Store name, subtitle, keywords, description, privacy and support URLs) and `fastlane/metadata/android` (Play title and descriptions). Values in `store-review.yaml` win. Demo account passwords are never read.

Misspelled keys are reported as errors, so a typo can't silently switch a check off.

## Use it as an MCP server

`npx store-review-check mcp` starts an MCP server over stdio. It has three read-only tools:

| Tool | Input | What it returns |
| --- | --- | --- |
| `check_project` | `path`, and optionally `platform`, `metadataPath`, `dating`, `minSeverity` | The text report plus structured findings |
| `check_listing` | `listing` (same shape as `store-review.yaml`), `platform`, `dating`, `minSeverity` | Listing problems, without a project folder |
| `list_rules` | `platform`, `tag`, `id` | Rules with severity, guideline section and source URL |

`check_listing` is useful while an assistant drafts store copy: it flags length limits, price claims, other apps' names and risky dating wording before anything reaches App Store Connect.

**Claude Desktop** (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "store-review-check": {
      "command": "npx",
      "args": ["-y", "store-review-check", "mcp"]
    }
  }
}
```

**Cursor** (`.cursor/mcp.json` in the project, or `~/.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "store-review-check": {
      "command": "npx",
      "args": ["-y", "store-review-check", "mcp"]
    }
  }
}
```

**Claude Code:**

```sh
claude mcp add store-review-check -- npx -y store-review-check mcp
```

Then ask something like "Check this project for App Store rejection risks" or "Is this App Store description OK for a dating app?".

## Rules

<!-- rules:start -->
40 rules, checked against the official pages on 2026-10-15. Source of truth: [rules/rules.json](rules/rules.json).

| Rule | Severity | Platform | What it checks | Source |
| --- | --- | --- | --- | --- |
| `ios.purpose-string-missing` | error | iOS | Protected resource used without a purpose string | [Apple 5.1.1(ii)](https://developer.apple.com/app-store/review/guidelines/#5.1.1)<br>[Apple: Requesting access to protected resources](https://developer.apple.com/documentation/uikit/requesting-access-to-protected-resources) |
| `ios.purpose-string-quality` | warning | iOS | Purpose string is empty, a placeholder or too vague | [Apple 5.1.1(ii)](https://developer.apple.com/app-store/review/guidelines/#5.1.1) |
| `ios.privacy-manifest-missing` | warning | iOS | No app privacy manifest (PrivacyInfo.xcprivacy) | [Apple: Privacy manifest files](https://developer.apple.com/documentation/bundleresources/privacy-manifest-files)<br>[Apple: Describing use of required reason API](https://developer.apple.com/documentation/bundleresources/describing-use-of-required-reason-api) |
| `ios.privacy-manifest-required-reason` | error | iOS | Required reason API used but not declared in the privacy manifest | [Apple: Describing use of required reason API](https://developer.apple.com/documentation/bundleresources/describing-use-of-required-reason-api)<br>[Apple: NSPrivacyAccessedAPIType](https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitype) |
| `ios.privacy-manifest-tracking-domains` | warning | iOS | NSPrivacyTracking is true but no tracking domains are listed | [Apple: Privacy manifest files](https://developer.apple.com/documentation/bundleresources/privacy-manifest-files) |
| `ios.tracking-without-att` | warning | iOS | Ad or attribution SDK present, no App Tracking Transparency prompt found | [Apple 5.1.2(i)](https://developer.apple.com/app-store/review/guidelines/#5.1.2)<br>[Apple: App Tracking Transparency](https://developer.apple.com/documentation/apptrackingtransparency) |
| `ios.sign-in-with-apple` | warning | iOS | Third-party login without an equivalent privacy-friendly option | [Apple 4.8](https://developer.apple.com/app-store/review/guidelines/#4.8) |
| `ios.siwa-entitlement` | warning | iOS | Sign in with Apple code without the entitlement | [Apple 2.1(a)](https://developer.apple.com/app-store/review/guidelines/#2.1)<br>[Apple: Sign in with Apple entitlement](https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.applesignin) |
| `ios.subscription-min-period` | error | iOS | Auto-renewable subscription shorter than seven days | [Apple 3.1.2(a)](https://developer.apple.com/app-store/review/guidelines/#3.1.2a) |
| `ios.restore-purchases` | warning | iOS | In-app purchases without a restore mechanism | [Apple 3.1.1](https://developer.apple.com/app-store/review/guidelines/#3.1.1)<br>[Apple: Auto-renewable subscriptions: clearly describing subscriptions](https://developer.apple.com/app-store/subscriptions/) |
| `ios.saturated-category` | info | iOS | Dating is a category Apple calls well established | [Apple 4.3(b)](https://developer.apple.com/app-store/review/guidelines/#4.3) |
| `ios.photo-picker` | info | iOS | Full photo library access where the system picker may be enough | [Apple 5.1.1(iii)](https://developer.apple.com/app-store/review/guidelines/#5.1.1) |
| `android.target-sdk` | error | Android | targetSdk is below the current Google Play requirement | [Google Play: Meet Google Play's target API level requirement](https://developer.android.com/google/play/requirements/target-sdk)<br>[Google Play: Target API level requirements for Google Play apps](https://support.google.com/googleplay/android-developer/answer/11926878) |
| `android.target-sdk-unresolved` | info | Android | targetSdk could not be read from the build files | [Google Play: Meet Google Play's target API level requirement](https://developer.android.com/google/play/requirements/target-sdk) |
| `android.billing-library` | error | Android | Google Play Billing Library version is no longer accepted | [Google Play: Google Play Billing Library version deprecation](https://developer.android.com/google/play/billing/deprecation-faq) |
| `android.photo-video-permissions` | warning | Android | READ_MEDIA_IMAGES / READ_MEDIA_VIDEO declared | [Google Play: Photo and Video Permissions (restricted permissions with minimum scope alternatives)](https://support.google.com/googleplay/android-developer/answer/14115180) |
| `android.background-location` | warning | Android | ACCESS_BACKGROUND_LOCATION declared | [Google Play: Location Permissions](https://support.google.com/googleplay/android-developer/answer/9799150) |
| `android.location-minimum-scope` | info | Android | Precise location declared; approximate may be enough | [Google Play: Location Permissions](https://support.google.com/googleplay/android-developer/answer/9799150) |
| `android.restricted-permissions` | warning | Android | Restricted permission declared | [Google Play: Permissions and APIs that Access Sensitive Information](https://support.google.com/googleplay/android-developer/answer/9888170) |
| `android.contacts-permission` | info | Android | READ_CONTACTS declared (new Contacts Permission policy) | [Google Play: Contacts Permission (restricted permissions with minimum scope alternatives)](https://support.google.com/googleplay/android-developer/answer/14115180) |
| `android.permission-rationale` | warning | Android | Sensitive runtime permissions with no disclosure or rationale found | [Google Play: User Data: Prominent Disclosure & Consent Requirement](https://support.google.com/googleplay/android-developer/answer/10144311)<br>[Google Play: Request runtime permissions](https://developer.android.com/training/permissions/requesting) |
| `account.deletion` | warning | Both | Account creation without in-app account deletion | [Apple 5.1.1(v)](https://developer.apple.com/app-store/review/guidelines/#5.1.1v)<br>[Apple: Offering account deletion in your app](https://developer.apple.com/support/offering-account-deletion-in-your-app/)<br>[Google Play: Understanding Google Play's app account deletion requirements](https://support.google.com/googleplay/android-developer/answer/13327111) |
| `account.deletion-web-link` | warning | Android | No web link for account deletion (Google Play) | [Google Play: Understanding Google Play's app account deletion requirements](https://support.google.com/googleplay/android-developer/answer/13327111) |
| `ugc.moderation` | warning | Both | User-generated content without report, block, filter or contact info | [Apple 1.2](https://developer.apple.com/app-store/review/guidelines/#1.2)<br>[Google Play: User Generated Content](https://support.google.com/googleplay/android-developer/answer/9876937) |
| `ugc.terms-acceptance` | warning | Both | No terms of use that users accept before posting content | [Google Play: User Generated Content](https://support.google.com/googleplay/android-developer/answer/9876937) |
| `iap.third-party-payments` | warning | Both | Third-party payment SDK in an app that sells digital features | [Apple 3.1.1](https://developer.apple.com/app-store/review/guidelines/#3.1.1)<br>[Google Play: Payments](https://support.google.com/googleplay/android-developer/answer/9858738) |
| `iap.subscription-disclosure` | warning | Both | Subscription terms, price or legal links missing | [Apple 3.1.2(c)](https://developer.apple.com/app-store/review/guidelines/#3.1.2c)<br>[Apple: Auto-renewable subscriptions](https://developer.apple.com/app-store/subscriptions/)<br>[Google Play: Subscriptions](https://support.google.com/googleplay/android-developer/answer/9900533) |
| `review.demo-account` | warning | Both | Login required, but no demo account for reviewers | [Apple 2.1(a)](https://developer.apple.com/app-store/review/guidelines/#2.1)<br>[Google Play: Prepare your app for review (App access)](https://support.google.com/googleplay/android-developer/answer/9859455) |
| `dating.age-gate` | warning | Both | Dating app with no age check found | [Apple 2.3.6](https://developer.apple.com/app-store/review/guidelines/#2.3.6)<br>[Apple: Age ratings values and definitions](https://developer.apple.com/help/app-store-connect/reference/app-information/age-ratings-values-and-definitions)<br>[Google Play: Content Ratings](https://support.google.com/googleplay/android-developer/answer/9898843)<br>[Google Play: User Generated Content (incidental sexual content)](https://support.google.com/googleplay/android-developer/answer/9876937) |
| `privacy.third-party-ai` | info | Both | Personal data may go to a third-party AI service | [Apple 5.1.2(i)](https://developer.apple.com/app-store/review/guidelines/#5.1.2) |
| `privacy.data-safety-hints` | info | Both | Data types to declare in Data safety and App Privacy | [Google Play: Provide information for Google Play's Data safety section](https://support.google.com/googleplay/android-developer/answer/10787469)<br>[Apple: App privacy details on the App Store](https://developer.apple.com/app-store/app-privacy-details/) |
| `listing.privacy-policy` | error | Both | No privacy policy URL in the store listing data | [Apple 5.1.1(i)](https://developer.apple.com/app-store/review/guidelines/#5.1.1)<br>[Google Play: Provide information for Google Play's Data safety section](https://support.google.com/googleplay/android-developer/answer/10787469) |
| `listing.field-limits` | error | Both | Store listing field is over the length limit | [Apple 2.3.7](https://developer.apple.com/app-store/review/guidelines/#2.3.7)<br>[Apple: App information (name, subtitle)](https://developer.apple.com/help/app-store-connect/reference/app-information/app-information)<br>[Apple: Platform version information (keywords, description, promotional text)](https://developer.apple.com/help/app-store-connect/reference/app-information/platform-version-information)<br>[Google Play: Best practice for your store listing](https://support.google.com/googleplay/android-developer/answer/13393723) |
| `listing.prohibited-dating-terms` | error | Both | Listing uses terms tied to hookup or compensated dating | [Apple 1.1.4](https://developer.apple.com/app-store/review/guidelines/#1.1.4)<br>[Google Play: Inappropriate Content: sexual content](https://support.google.com/googleplay/android-developer/answer/9878810) |
| `listing.anonymous-chat-terms` | warning | iOS | Listing promotes random or anonymous chat or rating people | [Apple 1.2](https://developer.apple.com/app-store/review/guidelines/#1.2) |
| `listing.competitor-names` | warning | iOS | Other app names in the App Store keywords or name | [Apple 2.3.7](https://developer.apple.com/app-store/review/guidelines/#2.3.7) |
| `listing.promo-terms` | warning | Both | Price, promotion or ranking claims in the name, subtitle, title or keywords | [Apple 2.3.7](https://developer.apple.com/app-store/review/guidelines/#2.3.7)<br>[Google Play: Metadata](https://support.google.com/googleplay/android-developer/answer/9898842) |
| `listing.other-platform-mentions` | warning | iOS | App Store listing mentions Android or Google Play | [Apple 2.3.10](https://developer.apple.com/app-store/review/guidelines/#2.3.10) |
| `listing.title-format` | warning | Android | Google Play title uses emoji, repeated symbols or ALL CAPS | [Google Play: Metadata](https://support.google.com/googleplay/android-developer/answer/9898842) |
| `listing.age-rating-dating` | info | Both | Age rating answers to review for a dating app | [Apple 2.3.6](https://developer.apple.com/app-store/review/guidelines/#2.3.6)<br>[Apple 2.3.8](https://developer.apple.com/app-store/review/guidelines/#2.3.8)<br>[Apple: Age ratings values and definitions](https://developer.apple.com/help/app-store-connect/reference/app-information/age-ratings-values-and-definitions)<br>[Google Play: Content Ratings](https://support.google.com/googleplay/android-developer/answer/9898843) |
<!-- rules:end -->

Severity means:

- **error**: the stores block this at upload or reject for it, and the project shows it directly (a missing key, a version number, a field over its limit).
- **warning**: a common rejection reason, or something the tool could not find evidence for. Check it.
- **info**: a reminder or a likely question from review.

## How it works, and what it can't see

The scan walks the project (skipping `node_modules`, `Pods`, `build` and similar folders), parses the build and config files, and searches your source and localization strings for specific API names and patterns. The patterns live in [`rules/rules.json`](rules/rules.json) next to the rules, so you can read exactly what triggers each finding.

What that means in practice:

- **Absence is a guess.** "No report button found" means no matching code or string was found. If reporting lives in a WebView, on the server, or under an unusual name, say so in `features` and the warning goes away.
- **Library manifests aren't merged.** Permissions that an Android library adds at build time don't show up unless you scan the merged manifest. Check `app/build/intermediates/merged_manifests` after a build.
- **Some values are only known at build time.** `flutter.targetSdkVersion`, the Expo SDK default and `app.config.js` aren't evaluated; the report says so instead of guessing.
- **Binary property lists aren't parsed.** Convert them with `plutil -convert xml1`.
- **String matching is English-first.** Localized UI text in other languages may not count as evidence.
- **Passing isn't approval.** App Review is done by people, and guidelines change. Treat the report as a checklist.

## Contributing

[Issues](https://github.com/Rocketech-Software-Development/store-review-check/issues) and pull requests are welcome, especially new rules, false positives and false negatives. Please don't include code or details from apps you don't own.

To add or change a rule:

1. Find the requirement on an official page: the [App Review Guidelines](https://developer.apple.com/app-store/review/guidelines/), Apple developer documentation, [Play Console Help](https://support.google.com/googleplay/android-developer/) or developer.android.com. Rules without an official source are not accepted.
2. Edit [`rules/rules.json`](rules/rules.json): add the rule with its `references`, and any detection patterns under `signals`. If it needs new logic, add a check function in `src/checks/` and register it in `src/checks/index.ts`.
3. Add or extend a fixture under `test/fixtures/` and assert the finding in `test/fixtures.test.ts`. Add a negative case too, so the rule doesn't fire where it shouldn't.
4. Run:

   ```sh
   npm install
   npm test             # build + all tests
   npm run check:urls   # every source URL must load
   npm run docs:rules   # regenerate the rule table above
   npm run format:rules # keep rules.json tidy
   ```

When a store changes a deadline or a number (target SDK level, Billing Library version), update `params` in the rule and the `verified` date at the top of `rules.json`.

## Credits

Built by [Rocketech](https://rocketech.it/dating-app-development), a team that builds and ships dating and social apps for iOS and Android. If a review keeps bouncing your app, we can help.

## License

[MIT](LICENSE)