After getting rejected in App Store review, I completed the entire fix-and-resubmit workflow using the App Store Connect API. No browser interaction was needed.

Rejection Details

The initial submission of JPS Explorer (a Japan Search cultural resource exploration app) was rejected for two issues:

  1. The Tip Jar screen displayed an error because the In-App Purchase products had not been registered in App Store Connect.
  2. The camera search “Capture” button caused a crash because NSCameraUsageDescription was missing from the iOS Info.plist.

Fixes

Camera Crash

Added camera and photo library usage descriptions to Info.plist:

<key>NSCameraUsageDescription</key>
<string>Used to search for similar cultural resources via camera</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Used to search for similar cultural resources from your photo library</string>

When using Flutter’s image_picker package to access the camera, the app crashes on a real device without these entries. This issue is easy to miss because the camera is unavailable in the simulator.

I also added handling for PlatformException with camera_access_denied and photo_access_denied.

Tip Jar Screen

When In-App Purchase products are not registered, StoreKit returns an error. I changed the error display to a user-friendly “Coming soon” message instead of showing the raw error.

Resubmission Steps via API

After rejection, App Store Connect’s browser UI shows an “Edit” button and a “Resubmit to App Review” button. All of these operations can be performed through the API.

Step 1: Increment the Build Number and Upload

A rejected build cannot be re-uploaded with the same build number. Increment it in pubspec.yaml:

# Before
version: 1.0.0+1
# After
version: 1.0.0+2

Build and upload:

flutter build ipa --export-method app-store
xcrun altool --upload-app --type ios \
  --file build/ios/ipa/jps_explorer.ipa \
  --apiKey $ASC_KEY_ID --apiIssuer $ASC_ISSUER_ID

Step 2: Wait for Build Processing to Complete

After upload, Apple processes the build on their side. Poll until processingState becomes VALID:

builds = api_request("GET",
    f"builds?filter[app]={APP_ID}&sort=-uploadedDate&limit=1")
state = builds["data"][0]["attributes"]["processingState"]
# PROCESSING → VALID

This usually takes a few minutes. If the build number is unchanged, the upload will not be recognized as a new build.

Step 3: Attach the New Build

NEW_BUILD_ID = builds["data"][0]["id"]

api_request("PATCH",
    f"appStoreVersions/{VERSION_ID}/relationships/build",
    {"data": {"type": "builds", "id": NEW_BUILD_ID}})

Step 4: Set Encryption Compliance

Each new build requires an encryption compliance declaration:

api_request("PATCH", f"builds/{NEW_BUILD_ID}", {
    "data": {
        "type": "builds", "id": NEW_BUILD_ID,
        "attributes": {"usesNonExemptEncryption": False}
    }
})

If the build already has this set, you get a 409 ENTITY_ERROR.ATTRIBUTE.INVALID error (“You cannot update when the value is already set”). This error can be safely ignored.

Step 5: Replace Screenshots (Optional)

Screenshots can be deleted and re-added after rejection. While screenshots are locked during review (WAITING_FOR_REVIEW), they become editable again after rejection.

# Delete existing screenshot
api_request("DELETE", f"appScreenshots/{screenshot_id}")

# Upload new screenshot (3 stages)
# 1. Reserve
result = api_request("POST", "appScreenshots", {...})
# 2. Upload binary
# 3. Commit
api_request("PATCH", f"appScreenshots/{screenshot_id}", {...})

Step 6: Resubmit

Send submitted: true to the submission ID that is in UNRESOLVED_ISSUES state from the rejection:

api_request("PATCH", f"reviewSubmissions/{SUBMISSION_ID}", {
    "data": {
        "type": "reviewSubmissions",
        "id": SUBMISSION_ID,
        "attributes": {"submitted": True}
    }
})

There is no need to create a new submission. The existing one is reused.

Timing Considerations

“Version is not ready to be submitted yet” Error

The following error may be returned when resubmitting:

{
  "code": "STATE_ERROR",
  "detail": "Version is not ready to be submitted yet, please try again later."
}

This indicates that the build attachment or screenshot processing has not yet completed internally. Waiting a few minutes and retrying resolves the issue.

There can be a gap between when App Store Connect’s browser UI shows “Ready for Review” and when the API actually accepts a submission. In my case, I confirmed the status in the browser and then successfully resubmitted via the API.

Handling Stale Submissions

Multiple submission attempts can leave behind submissions in READY_FOR_REVIEW state. These can sometimes be canceled with canceled: true, but in my case this returned a 409 error. Resubmitting against the UNRESOLVED_ISSUES submission resolved the situation.

Complete Post-Rejection Flow (API Only)

1. Fix code
2. Increment build number in pubspec.yaml
3. flutter build ipa
4. xcrun altool --upload-app
5. Poll builds API until processing completes (VALID)
6. PATCH appStoreVersions/{id}/relationships/build to attach new build
7. PATCH builds/{id} to set encryption compliance
8. (Optional) Replace screenshots
9. PATCH reviewSubmissions/{id} with submitted: true to resubmit

Browser operations like “Edit” or “Resubmit to App Review” are unnecessary. That said, checking the state in the browser can be useful when running into timing issues with the API.

Findings

FindingDetail
Camera permission in Info.plistMissing NSCameraUsageDescription causes a crash on camera access. Not detectable in the simulator.
IAP product not registeredStoreKit errors should be converted to user-friendly messages rather than displayed raw.
Duplicate build numberA build with the same number will not be recognized as new. Increment by at least 1.
Encryption compliance per buildRequired for each new build. Re-setting on an already-configured build returns a 409 error, which can be ignored.
Resubmission timingImmediately after attaching a build, the API may return “not ready.” Succeeds after a few minutes.
Submission reuseThe UNRESOLVED_ISSUES submission from the rejection can be resubmitted directly.