Skip to main content
Everything under flowdeck ship needs two things: an App Store Connect account FlowDeck can authenticate as, and a repository that knows which app record it belongs to. This page covers both.
Both are read-only and safe to run at any time. Together they answer “can this project ship, and where to”.

One stored credential, and two ways to get one

The only credential FlowDeck stores is an App Store Connect API key, kept in your login keychain. You can create one yourself in App Store Connect, or mint one through an Apple Account sign-in — either way, what ends up stored is the same kind of key. That is deliberate. An Apple ID gives away your entire Apple account; an API key is scoped to App Store Connect, revocable on its own, and documented, so it does not break when Apple changes something private. Create one in App Store Connect under Users and Access › Integrations, and download the .p8 file. Apple lets you download it once.

Connecting an account

The key is checked against Apple before anything is stored. A key that cannot authenticate, or that lacks permission to read apps, is refused rather than saved for you to discover later.
There is no --team-id. An API key belongs to exactly one account, and that account is exactly one team, so FlowDeck derives the Developer Team ID itself.

More than one account

Credentials are keyed by issuer ID, so a machine can hold as many as you have.
A project names its account explicitly, in .flowdeck/config.json. It is never inferred: two accounts can share a team, and the cost of guessing wrong is a build pushed to somebody else’s listing. If several accounts are connected and the repository has not chosen one, commands fail with shipAmbiguousAccount rather than picking for you.

Checking an account

isAdmin reports whether this key can create app records in App Store Connect, which is an Admin-only action on the web. It is a Bool?: absent means the check could not be made, not that you lack the permission.

Removing an account

This removes the keychain item and nothing else. The key itself stays live. A key cannot revoke itself and revocation is not in Apple’s public API, so revoke it in App Store Connect under Users and Access › Integrations when you mean to.

Apple web session

A handful of operations — minting a new API key, or creating a new app record automatically — are not exposed by App Store Connect’s API at all, only by the same web session the Apple Account website itself uses. flowdeck ship auth apple wraps that session for exactly those cases; most ship commands never need it.
In an interactive terminal, echo is disabled separately while the password and any two-factor code are read, then restored after each value. If FlowDeck cannot read, change, or restore the terminal settings, it stops with shipAppleSecureInputUnavailable instead of accepting visible secret input. Noninteractive callers can provide the password and, when requested, the code as successive lines on stdin. The password is passed to Apple’s own sign-in tooling through an environment variable; FlowDeck never logs it, stores it, or returns it in any output or error. Apple stores the resulting session in the Mac’s login keychain, the same way signing in through a browser would. If Apple asks for a two-factor code, login pauses and prints {"stage":"AWAIT_2FA"} on its progress stream under --json (a plain sentence in text mode); enter the code at the hidden prompt on the same stdin.

Can this account mint a key?

Minting a key requires the Account Holder or Admin role. Check first, read-only and offline:
roleCheck is one of three states, not two: accountHolderOrAdmin, insufficientRole, or unreadable. unreadable means the response could not be parsed — it is not evidence the account lacks the role, only that this could not tell, and it is a normal answer with exit code 0. No session at all is different: it fails with shipAppleMintKeyNoSession and a non-zero exit, because there is nothing to ask.

Minting a key

mint-key checks the role above before creating anything: an insufficientRole or unreadable account is refused up front rather than failing partway through. On success, the new key is imported the same way auth add stores a pasted one, so it appears in auth list immediately.
If Apple accepts the create but its response is incomplete, FlowDeck preserves every .p8 file it can find and reports the recovery path instead of offering to retry. Match that file with the key and issuer IDs under Users and Access › Integrations, then import it with flowdeck ship auth add; if they cannot be matched, revoke the new key before creating another. A later import failure is also reported as orphaned without a retry action, because retrying could consume another team key slot.

Linking a project to an app

An account can reach many apps. Linking records which one this repository ships to, in a small git-tracked file so the whole team inherits it.
Choosing the app is what chooses the account: --all-accounts tags each row with its issuer, so you do not have to know which account holds which app before you can pick one.

Where a project stands

An unlinked repository answers from local files alone, with no network call:
A linked one adds the app record and a row per platform:
currentVersion is the newest version in any state; liveVersion is the newest one on sale. They are equal when nothing new is in flight, and liveVersion is absent for a platform that has never shipped.

When the app record does not exist yet

Apple does not allow creating an app record through the public API, for any key. With a live Apple Account session (--apple-session auto, the default), app init creates the record directly through that session instead. Without one, or when Apple clearly refuses the direct attempt, it falls back to the guided web form, exactly as it always has:
The guided path registers the bundle ID through the API, prints the SKU to use, opens the App Store Connect form, then polls until the record appears and links it for you. A missing --name always takes this path too, live session or not: inventing one would create a real, permanently-SKU’d record under a name nobody chose.
The guided form waits for a person to fill it in. It is not suitable for CI or for an unattended agent run.
If the direct attempt fails and the guided fallback also gives up waiting, the command reports shipDirectAppCreateFailed: the bundle ID is already registered, so nothing is lost, and the fix is flowdeck ship app link --bundle-id com.example.myapp once the record exists. An exit-zero create with an unreadable response is different: Apple may have created the record already. FlowDeck polls by bundle ID for a bounded period and links the record if it appears. If it does not, the command reports shipAppCreateResultAmbiguous and tells you to check and link the existing record; it never opens or recommends the create form again. Do not run ship app init again after this uncertain result. Check App Store Connect, then finish with flowdeck ship app link --bundle-id com.example.myapp once the record is visible.

Next

With an account connected and a project linked, see TestFlight for building, uploading, and getting the build to testers.