AGENTSOURCE

The Shelf / App Store / Send It To Apple

App Store

Send It To Apple

The last mile to the App Store — and the checks that stop it costing you a day.

The job: take an app that works and get it into Apple's review queue, without losing a day to something that could have been caught before the upload started.

Why the obvious approach fails

The steps are not the hard part. Anyone can archive, upload and press submit.

What costs you is that almost everything on your machine looks like proof of something on Apple's side, and is not. The build number in your project file is what you meant to ship, not a build Apple has — get that backwards and you burn a number you can never use again. A clean commit log is not a clean working tree, so release notes written from it can describe a release you did not send. A successful export is not the file that went up, because the real run rebuilds it.

Each of those artefacts is real, local and readable. That is exactly why it gets trusted.

And the failures land at the worst possible moment: after the slowest step. An extension left on an old version number does not fail when you set it — it fails after the archive, the export and the upload have all finished.

What's on the tag

The whole run, in order, with the decision rules at each step: ask Apple what it already has before choosing any number, set the version everywhere including extensions, know what is actually in the build, archive, inspect, upload, verify it landed on the right app, create the version, submit.

A checker that opens your built file rather than your project settings, because those two disagree more often than you would like. It finds an extension on the wrong version, a missing privacy file, an out-of-date build tool, and whether the binary is even for the app you think it is — any one of which stops the upload cold.

The rules Apple added recently, each stamped with the date it came in, including one that arrives in September. An honest list of the four gates no tool can check because Apple has no way in for them. And what to actually do after a rejection — including why replying to the reviewer does not put you back in the queue, and why a crash Apple reports but you cannot reproduce is very often not your bug.

Why not a free directory download

Because most of what is written about this is out of date in a way you cannot see. The single-call submission endpoint most guides and scripts still use was removed from Apple's API — not deprecated, removed. Age rating moved. The common claim that the standard upload tool is deprecated is wrong, and the widely-repeated reason for it is about something else entirely.

Everything dated in this package was checked against Apple's own documentation the day it shipped, and the dates are printed so you can see what to re-check later. The procedure was run three times by a fresh model against a deliberately broken release; the first two runs found real holes in it, including one where the most important instruction had no tool behind it at all. Those runs are on this page, including what they found wrong. Field-tested, documented, issued as-is.

FIELD REPORT real output, not a promise

Setup: a mid-tier model, given only this package and a release that looks ready — an app called Lantern, archived and exported, project file saying version 1.3.0 build 9 — and asked to do everything before the upload, then stop with a go/no-go. No App Store Connect credentials, so it worked from a saved response. Output below is what the run produced.


It said no. Three blockers, from one command:

$ python3 references/shipcheck.py all ../repo ../repo/build/export/Lantern.ipa \
      --bundle-id com.example.lantern --version 1.3.0 --build 9 \
      --state-file ../repo/APPLE_STATE.json

  WARN  working tree has 2 uncommitted change(s)
        these COMPILE INTO the build. Release notes written from the commit log would
        describe a different release:
        ?? InvoiceView.swift
  FAIL  export options say destination=upload
        export will try to authenticate as a signed-in account, not your API key — it
        fails on 'accounts' while every API call keeps working. Set destination=export.
  PASS  app version 1.3.0 (9)
  FAIL  extension LanternWidget.appex version MISMATCH
        app is 1.3.0 (9), this is 1.0.0 (1) — Apple rejects the upload
  PASS  encryption compliance declared in the binary
  PASS  privacy manifest present in app
  FAIL  privacy manifest MISSING from LanternWidget.appex
        App Store Connect refuses the upload (ITMS-91053). Every executable needs its
        own PrivacyInfo.xcprivacy — an extension cannot use the app's.
  PASS  built with SDK iphoneos26.0 (Xcode 2660)
  PASS  live version on the App Store: 1.2.0
  PASS  no version currently in review
  PASS  highest build Apple holds: 7
  WARN  local build 9 skips past Apple's highest (7)
        build 8 was never uploaded — it exists only locally. FOLD this work into 8
        rather than burning the number.
  PASS  local version 1.3.0 differs from live 1.2.0

  8 passed, 2 warning(s), 3 failure(s)
  DO NOT UPLOAD until the failures are fixed.

Every one of those is a real cost. The widget's stale version gets the upload rejected. The missing privacy manifest gets it refused outright. The stale export setting fails with an account error while every other call to Apple keeps working — the kind of thing that eats an hour. And build 9 quietly burns build 8, which was never uploaded and can never be used again.

On the build number, the run's own reasoning:

Apple's highest held build is 7. Your project file says 9. That skips a number — build 8 was never sent to Apple, so it only exists on your machine. You fold new work into 8 rather than burn it by jumping to 9.

That decision is the whole reason the first instruction is "ask Apple what it already has."

It checked the checker before trusting it, which is the habit the package is built around:

$ python3 references/shipcheck.py selftest
[1] a GOOD ipa — every check must pass              -> PASS
[2] extension version MISMATCH — the check MUST fail -> PASS (caught it)
[3] compliance key ABSENT — must warn                -> PASS (warned)
[4] privacy manifest MISSING — the check MUST fail   -> PASS (caught it)
[5] built with an OLD SDK — the check MUST fail      -> PASS (caught it)
[6] bundle id MISMATCH — the check MUST fail         -> PASS (caught it)
[7] bundle id MATCHES — must NOT fail                -> PASS
[8] export options set to upload — the check MUST fail -> PASS (caught it)
[9] export options set to export — must NOT fail     -> PASS
ALL SELFTESTS PASS

Five of those nine have to catch a deliberately broken fixture; four have to leave a correct one alone.

And it went one level closer than the tool, unzipping the build by hand rather than taking the report on trust:

Confirmed: app = 1.3.0 (9), widget = 1.0.0 (1), only one PrivacyInfo.xcprivacy in the whole archive.

On the gates the package cannot check for you, it reported them as unchecked rather than quietly skipping them — the App Privacy questionnaire, the age rating, the social-media question and the review contact phone all live in Apple's web interface with no API behind them. The package says so plainly, and the run repeated it back.


What these runs changed in the package. Three passes, each against a fresh model, each finding real defects:

  • The first found that the most important instruction in the file — ask Apple what it holds — had no tool behind it, and that the upload step named no command at all. Roughly half the procedure was judgment with nothing runnable attached. Both are now real commands.
  • The second found that the command literally named "the one-command pre-flight" silently skipped the build-number comparison — the single most expensive mistake it exists to prevent. It now runs it, or says loudly that it did not. That pass also found the package documented a privacy-manifest rule as an upload blocker and then never checked for it, though it was reading the same file already.
  • The third confirmed every documented command exists and works, and found that the offline mode used throughout this very report was documented only inside the script, not in the skill — so a reader without credentials would have concluded the tool was unusable.

One more thing worth saying: the second run accepted build 9 as reasonable. It was wrong, by the package's own rule. That is exactly why the build-number call stopped being prose and became a check — the third run got it right because the tool decided, not the reader.

SERVICE RECORD living gear — updated as the factory learns

v1.0.0 — 2026-08-27

First issue. Ported from an internal release procedure that has shipped a portfolio of apps and absorbed a lot of expensive surprises along the way.

Scoped deliberately to one job: getting a finished app's version into the review queue. The source covers far more of App Store Connect — creating products, prices, screenshots, subscription attachment — and cramming that in would have made a worse package. Subscription attachment is named as a boundary rather than half-covered, because it is a genuinely different job with a different (web-interface only) flow.

The pre-flight checker was written fresh for this package rather than ported. It does the checks a buyer can run without any account or key — reading the SIGNED bundle for extension version lockstep and the compliance declaration, catching a stale export-settings file, and flagging uncommitted work that will compile into the build. It ships with a self-test whose checks have to fail on deliberately broken fixtures before you trust them.

v1.0.1 — 2026-08-27

Corrected the social-media requirement. The first issue said answering yes forces a minimum 13+ age rating. Apple's own announcement does not say that — the stated consequences are a Social Media content descriptor on the product page and a Time Allowance category, with an exclusion for apps that disable those features for under-13s. The overstated version came from a research summary rather than Apple's page; fetching the primary source settled it. Apple's definition is now quoted directly.

Every update ships free to owners — your locker always serves the latest version.

QUESTIONS

Does this upload my app for me?

It gives your agent the whole procedure and the checks, and it will run them. The two steps you cannot take back — uploading and submitting — are meant to stop and ask you first, and the package says so. A build number, once uploaded, is spent forever, and a version created without a build attached cannot be deleted. Those are worth a human glance.

I already use a build service. Is this useless to me?

No, and the package is straight about where they stop. The build services are good at building and at getting a build to testers. Most of them stop there. The version record, the fields Apple requires, the questions only the browser can answer, and the actual submit are still yours — and so is every trap in here, because they happen on your machine before anything is uploaded.

Does it cover in-app purchases and subscriptions?

No, and that is on purpose. Getting your first purchase or subscription approved alongside the app is a genuinely different job with a different flow that only works in the browser. Half-covering it would have made a worse package. It IS named where it matters, so you are not blindsided by it at submit time.

Apple changes things constantly. How long is this good for?

The procedure and the traps are stable — they are about how the pieces relate, not about this month's rules. The handful of things Apple sets on a schedule are marked with the date they came in and the note that they move, so you can see at a glance what to re-check. Nothing in here quotes a number without saying when it was true.