Appopolis

App Store Connect won't let you submit, and it won't tell you why

21 August 2026 · app store, ios, shipping

You finish the app. You fill in everything. You press submit, and App Store Connect says:

The app is not in a valid state.

That is the entire message. No field is highlighted. Nothing is red. The checklist looks complete, the in-app purchase says Ready to Submit, and the Add for Review button is either greyed out or throws the same useless sentence when you click it.

We shipped six apps this year. Every one of the blockers below stopped a submission at least once, and not one of them announced itself.

First: make Apple tell you the real reason

This is the single most useful thing in this post, so it goes first.

The dashboard hides the actual error. The API does not — it just buries it. When a submission fails, the response carries an errors array, and each error can carry its own meta.associatedErrors. The real reason is almost always in there, not in the top-level message.

try:
    call("POST", "/v1/reviewSubmissionItems", body)
except HTTPError as e:
    payload = json.loads(e.read())
    for err in payload.get("errors", []):
        print(err.get("title"), "-", err.get("detail"))
        # the part that actually names the problem
        for sub in (err.get("meta") or {}).get("associatedErrors", {}).values():
            for s in sub:
                print("   ->", s.get("detail"))

Without unfolding associatedErrors you get "not in valid state". With it you get the actual sentence, naming the actual field. Our release tooling now always unfolds it, and submissions stopped being guesswork.

The blockers that never announce themselves

1. The in-app purchase has no territory availability

The one that cost us the most time. The IAP had a name, a price, a localization and a review screenshot. It said Ready to Submit. It was still rejected from the submission, because it had no *territory availability* record — the list of countries it can be sold in.

Nothing in the UI suggests this is missing. Price and availability read as one thing in your head and are two records in the API.

2. The app's own price and availability

Same trap, one level up. Free still counts as a price, and an app with no price schedule is not submittable. If your app is free with an in-app purchase, you still have to set a price of zero, deliberately.

3. The age rating has questions you never saw

The age rating declaration has 29 questions. Ours had 25 unanswered while the dashboard showed a perfectly normal 4+ rating. A partly-answered declaration looks identical to a finished one, and blocks the submission silently.

4. Content rights is null, not false

"Does your app contain third-party content?" — No. Except we had never *answered* no, and null is not no. One boolean, invisible, blocking.

5. The regulated medical device declaration

Newer than the rest and easy to miss entirely: a banner on App Information asking whether your app is a regulated medical device. Our habit tracker and water tracker both had to answer it. Unanswered blocks submission.

No API exists for this one. It has to be clicked in the dashboard.

6. App Privacy has to be *published*, not just filled in

Filling in the App Privacy questionnaire is not enough. There is a Publish button, and until you press it the answers do not count. Also no API.

7. The IAP review screenshot has to show the button

This one is not a state error — it is a rejection, and it is the most expensive kind because you find out days later.

Apple wants a screenshot showing *where the purchase happens*. We sent a screenshot of the Settings screen, where the unlock card sits below the fold. Right screen, no visible purchase button. Rejected under Guideline 2.1.

If your unlock lives at the bottom of a scrolling screen, temporarily move it to the top, screenshot that, and put it back.

Two limits nobody documents

An IAP description caps at 55 characters. Not 4000, not 255. Fifty-five.

A first non-consumable cannot be submitted on its own. An IAP attaches to a review submission as its *version* — inAppPurchaseVersion — not as the IAP itself. Post /inAppPurchaseSubmissions for a first non-consumable and it refuses outright. It has to ride along with the app version, and the submission must contain exactly two items: the version and the IAP.

Why "Missing Metadata" lies to you

If you search this you will mostly find subscription answers — a subscription group missing its own localization, which keeps every subscription inside it stuck regardless of how complete they look. That is real, and it is well covered elsewhere.

For non-consumables, "Missing Metadata" more often means the review screenshot or the territory record. And here is a genuinely counterintuitive one: deleting and re-uploading a review screenshot puts the IAP back into Missing Metadata until the new one finishes processing. If you are replacing an asset just before a submission, check the state again afterwards.

The order that works

We stopped debugging submissions one at a time and made the order fixed:

1. Listing copy and screenshots 2. In-app purchase — localization, price, territory availability, review screenshot 3. Category, age rating (all 29), content rights, copyright 4. App price and territory availability 5. Build attached and processed 6. Reviewer contact, notes, demo account 7. In the dashboard: App Privacy → Publish, and the medical device declaration 8. Preflight everything, then submit

Steps 1–6 and 8 are automatable through the App Store Connect API. Step 7 is not, at any permission level, and it will block you at the end if you leave it.

The part worth internalising

None of these are hard problems. Every one is a single field, and every one of them fails the same way: silently, with a message that names nothing.

So the fix is not to remember the list. It is to make the failure loud — unfold associatedErrors on every error, and run a preflight that checks each gate individually and prints which one is not satisfied. Then a blocked submission takes thirty seconds instead of an afternoon.