We took a React Native app through both stores between 23 September and 1 October 2026. The binary compiled and passed Apple’s own validator the first time. Submission still took nine days, and none of the delays were about code.

This is the sequence as it actually happened, because the order matters more than the checklist. Published guidance tends to list the requirements; it rarely says which ones block the others.

The thing nobody tells you about the order

Apple and Google both gate submission behind a set of declarations, and several of those declarations cannot be answered until the build exists. So the work does not run screenshots -> metadata -> upload -> submit. It runs in two tracks that only join at the end, and the join is where things stall.

What we hit, in order:

  1. Store listings and screenshots — straightforward, and the only genuinely creative step
  2. Play declarations — eight forms, answerable before the build
  3. iOS credentials — a dead end that took a day
  4. Uploading the binary — three attempts, two tools, one 500
  5. The App Privacy questionnaire — the last thing standing
  6. A form that said it was complete and was not

Credentials: physical security keys are a dead end for CI

The first real blocker had nothing to do with the app. The Apple ID held the developer account, and its only configured second factors were physical security keys.

Neither fastlane nor EAS can challenge a hardware key. There is no prompt for them to surface and no code for them to forward, so username-and-password automation is simply not available on that account. Not awkward — unavailable.

The fix is to stop authenticating as a person. An App Store Connect API key skips Apple ID two-factor entirely, because it is an account-level credential rather than a user login. We moved eas.json to use the ASC API key instead of appleId, which closed the problem permanently rather than working around it once.

Two practical notes for anyone running a team account:

  • Set the API key up before you need it. Discovering this on submission day costs a day.
  • Check your distribution certificate’s expiry while you are in there. Ours expires 21 November 2026 and is shared across five other apps on the team, so that date is a scheduled outage for all of them, not a per-app problem.

The 500 that was not the binary

With credentials working, the upload failed. Twice, with two independent tools: a local uploader, then Expo’s infrastructure using API-key auth. Both returned 500 UNEXPECTED_ERROR at the commit step, after every chunk had uploaded successfully.

This is the failure mode worth recognising, because it reads like a broken build and is not. The evidence that the bytes had arrived:

  • Apple’s own altool eventually succeeded where both others failed
  • It reported 0 bytes transferred, and reused the delivery UUID from the failed run
  • Apple’s validator passed the exact same artifact

So the file was already on Apple’s side; only the final commit was failing. The binary was never the cause, and rebuilding it — the obvious move — would have wasted hours and changed nothing.

One honest caveat. Apple’s status page showed no active incident at the time, and the only App Upload outage listed had been resolved on 15 September. So “this was Apple-side” is our inference from the evidence above, not something Apple confirmed. We are reasonably confident, and it is still an inference.

Build 8 came back VALID the same day. Which did not mean submittable.

Google Play went through while iOS was stuck

Worth recording because it contradicts common advice: we expected Play to require the first build be uploaded by hand through the console. It did not. The Publishing API accepted version 1.0.0 straight onto the internal track.

Play’s declarations were also the more tractable half of the week. Data Safety completed eight of eight, and the one that matters:

Advertising ID = No, verified against the built manifest. Not answered from memory. These forms ask what your app does, and the honest answer lives in the compiled artifact rather than in anyone’s recollection of it. We have written about this separately because it is the single cheapest way to avoid a rejection: read your own manifest before you answer the question about it.

The last blocker was an empty field

By 30 September everything in App Store Connect had cleared except one item. Release type was MANUAL, build 8 was attached, content rights were set, review notes were written, and pricing was configured across 175 territories.

Two findings from that pass:

The privacy policy URL was empty, for both locales. That is an automatic rejection, and nothing in the console had drawn attention to it. It is a text field on a tab you visit once.

One of our own “problems” was not real. An earlier readiness check had reported pricing MISSING (HTTP 404). The 404 was a wrong endpoint, not an absent resource — the error said the path did not match a defined resource type, which is a bad URL rather than missing data. We had nearly gone looking for a pricing gap that did not exist.

That left the App Privacy questionnaire as the only thing standing between a finished app and a review queue. It is a long form, it is mandatory, and it cannot be delegated to the build.

Existence is not completeness

The last defect is the one most worth passing on, because our own tooling reported green.

The age rating declaration existed as a relationship on the version. Every answer inside it was null. App Store Connect refused to accept the version as “not in valid state” and did not say why.

Our readiness check had looked for the relationship and found it, so it passed. The check was asking whether the object was there, when the question was whether it had been filled in. Those are different questions, and only one of them is useful.

If you build any pre-submission automation, assert on the values, not on the presence of the structure that holds them.

Once the age rating was genuinely complete — 17+ with an 18+ override, no simulated gambling, no ads, usesIdfa false — version 1.0 moved to WAITING_FOR_REVIEW with build 9 on 1 October.

The nine-day summary

DayWhat blocked itCategory
23 SepScreenshots, legal pages, both localesContent
24 SepPlay declarations, 8 of 8Compliance
29 SepApple ID second factor unusable by CICredentials
30 Sep500 UNEXPECTED_ERROR at commit, twiceInfrastructure
30 SepEmpty privacy policy URLMetadata
30 SepApp Privacy questionnaire outstandingCompliance
1 OctAge rating present but emptyTooling

Not one row is a code defect. If you are budgeting a launch, the compile is the part that goes to plan.

What this means for your timeline

Allow a week between “the app works” and “the app is in review”, and spend it deliberately:

  1. Create the App Store Connect API key now, not on submission day
  2. Check the distribution certificate expiry, and whose other apps share it
  3. Answer every store declaration against the built artifact, not from memory
  4. Walk every metadata tab looking for empty required fields
  5. Treat the App Privacy and Data Safety forms as their own task, with their own hours
  6. If your upload fails after the bytes transfer, try Apple’s altool before you rebuild

A build that compiles is roughly halfway. Pricing for both halves is set out in what a mobile app actually costs in Australia, and if you are holding a quote that treats submission as a formality, we will read it for you.


Awesome Apps is an app developer in Sydney building and submitting iOS and Android apps for Australian businesses, and part of Ganda Tech Services. Dates and errors above are from our own submission records, September – October 2026.

Ready to build your app?

Talk to a Sydney app developer — free.

30 minutes. We'll tell you what your app needs, how long it takes, and what it costs. Real answers, no sales pitch.

Book Free App Strategy Call →

Free · 30 minutes · No obligation