Skip to main content
Take a build from source to the people testing it: archive and sign, upload to App Store Connect, then decide who receives it. Everything here needs a connected account and a linked project. See App Store Connect Account first. Every subcommand supports --json.

The whole flow

Building and uploading

Ask which version first

needsCreate: true means every version Apple holds is already live, so there is nothing for a build to attach to. Creating a version record writes to your real account, so it only happens when you ask:

Check signing before you wait

doctor reports signing blockers in a second: a missing or expired provisioning profile, a profile that does not cover the bundle ID or belongs to another team, a certificate whose private key is not on this Mac. The same checks run automatically before a manual-signing archive, so a blocker stops the build before xcodebuild starts rather than ten minutes in.
build archive, build upload and build deliver are not safe to retry after an uncertain result. Run flowdeck ship build status first. A build number can never be reused.

Groups

A group decides who receives which builds.
--group accepts a name or an ID, everywhere it appears.

Internal and external

Internal or external is decided when the group is created and can never be changed. Apple’s isInternalGroup is create-only, so --internal is the one chance to get it right.
groups create is the one create command that is not safe to retry: Apple allows two groups with the same name, so a second run makes a second group rather than returning the first.

Deleting

Deleting is refused while the group still holds testers:
Empty it first. The testers stay on the app; they lose the builds that group gave them.

Testers

Moving testers between groups

testers remove takes somebody off the app, revoking every build in every group they are in. groups remove-testers takes them out of one group and leaves the rest. They are different actions.

What testers list returns

It is a join, not a plain listing: who each tester is, which groups they are in, and what they have done with the app.
Two things to know about that shape:
  • A count Apple did not report is null, not 0. “No crashes” and “Apple did not say” are different answers. devices is always null, because Apple reports no device count on this endpoint.
  • A public-link tester has no email. Apple never collects one. It also means they cannot be invited or added to a group by address.

Internal groups refuse non-members

An internal group only accepts App Store Connect team members, and nothing can check that in advance. Adding a batch therefore sends one request per address and reports Apple’s refusal per email:
Everybody else in the batch is still added. Do not re-run the whole batch; only the named addresses failed.

Getting the build to testers

Only a build Apple has finished processing (VALID) can be distributed. Apple accepts a request for an unprocessed build and then silently does nothing, so FlowDeck refuses it rather than reporting a success that did not happen.

Beta App Review

An external group receives nothing until review passes. Write the tester notes first, because Apple gates the submission on them:
--notify sets Apple’s “Automatically notify testers” so testers are emailed when the build lands. Internal groups never wait for review. If people need the build today and review has not passed, an internal group is the answer.

Expiring a build

Expiring is not deleting. The build stays on the record and stops being installable. An uploaded build can never be deleted, and its build number can never be reused.

Idempotency

Anything in the second column may already have taken effect when it failed or timed out. Check with the matching list or status command before running it again.

Errors

Every failure carries a stable code, one sentence saying what happened, and a hint saying what to do: