Got a built and signed IPA? One command hands it to Apple—no Mac and no Xcode required:
appuploader-cli upload -f Payload.ipa -u dev@example.com -p abcd-efgh-ijkl-mnop
appuploader-cli is the command-line tool bundled with AppUploader. The upload command submits a signed .ipa (iOS) or .pkg (macOS) to App Store Connect—the same thing as clicking "Submit to App Store" in the GUI, except it can be written into scripts.
Below, let's go through how to use this command: how to choose between the two login methods, how to upload non-iOS packages, how to tell whether it actually succeeded, and in what order to troubleshoot when uploads fail.
Find the Command
After installing AppUploader, the command-line tool is already installed with it:
| OS | Location |
|---|---|
| Windows |
appuploader-cli.exe in the same directory as the main program AppUploader.exe
|
| macOS | AppUploader.app/Contents/Resources/appuploader-cli |
| Linux |
appuploader-cli in the same directory as the main program AppUploader
|
Open a terminal in this directory and you can use it. To run it from any directory, add it to PATH: on Windows, go to Settings → System → About → Advanced system settings → Environment Variables → Path; after adding it, reopen the terminal for it to take effect. On macOS/Linux, add a line export PATH="$PATH:/your/install/directory" to ~/.zshrc or ~/.bashrc.
Also prepare a package that has already been signed—upload does not compile or sign; its input is the built .ipa or .pkg.
Login Method 1: App-Specific Password
The easiest credential for command-line uploads is an App-Specific Password. Apple designed it specifically for third-party tools, and it does not trigger two-factor authentication—you don't have to confirm on your phone every time you upload, which is essential for scripts and unattended build machines.
Go to https://account.apple.com/account/manage and sign in, find "App-Specific Passwords", create one, and you'll get a password like abcd-efgh-ijkl-mnop. It is shown only once, so save it immediately.
Then:
appuploader-cli upload -f Payload.ipa -u dev@example.com -p abcd-efgh-ijkl-mnop
-u is the Apple ID email, -p is the newly generated app-specific password; both must be provided together. Specify the package path with -f, or omit -f and put it directly at the end of the command:
appuploader-cli upload -u dev@example.com -p abcd-efgh-ijkl-mnop Payload.ipa
The most common mistake is putting your Apple ID login password in
-p. An app-specific password is always 16 letters with three hyphens in the middle, completely different from a login password. Check it before entering it.
Login Method 2: App Store Connect API Key
If this command will run on a team build machine or pipeline, using someone's Apple ID is not ideal—when that person leaves or changes their password, the whole automation breaks. In this case, use an App Store Connect API key instead: it belongs to the team rather than an individual, can be revoked separately, and also does not involve two-factor authentication.
Go to https://appstoreconnect.apple.com/access/api to generate one (requires Account Holder or Admin permission). You need three things: the Key ID, the Issuer ID at the top of the page, and the .p8 private key file. The .p8 can only be downloaded once; Apple will not give it a second time. Store it immediately in a password manager or your CI's secret store.
appuploader-cli upload -f Payload.ipa \
--api-key UK29KBAX9X \
--api-issuer 69a6de78-4459-47e3-e053-5b8c7c11a4d1 \
--private-key Auth.Key_UK29KBAX9X.p8
All three parameters are required. The two login methods are mutually exclusive; mixing -u -p with these three parameters will be rejected outright—when switching from password to key, remember to remove the original -u -p completely.
Before using the key method, confirm two things:
-
It can only upload
.ipa. This method relies on the Bundle ID in the package to find the corresponding app on App Store Connect; it cannot be obtained from a.pkg. For macOS.pkg, use the app-specific password method above. - This Bundle ID must already have an app created in App Store Connect. If it hasn't been created, you'll get an error saying the corresponding app cannot be found. Create it first, then upload.
Which One to Choose
In one sentence: for manual releases by yourself, use an App-Specific Password; for multi-person collaboration or pipelines, use an API key.
The app-specific password wins on simplicity—two parameters and it runs; but it is tied to an individual Apple ID, so handing it to a pipeline means handing over personal credentials. The API key takes a few more configuration steps, but in exchange it is decoupled from a person, can be revoked separately, and if something goes wrong you only need to rotate that one key.
Uploading Non-iOS Packages
By default it is treated as iOS; for other platforms, add --type:
# macOS
appuploader-cli upload -u dev@example.com -p abcd-efgh-ijkl-mnop --type osx App.pkg
# tvOS
appuploader-cli upload -f App.ipa -u dev@example.com -p abcd-efgh-ijkl-mnop --type appletvos
There are four values in total: ios (default), osx, appletvos, xros (visionOS).
How to Tell Whether the Upload Actually Succeeded
After pressing Enter, progress will print line by line in real time; the larger the package, the longer it takes. Look at the last line:
> upload finished successfully
On failure:
> upload finished with error: <specific reason from Apple>
If you're writing a script, do not grep these logs—when the command fails, the exit code is non-zero; checking the exit code is enough, and it won't break when the log wording changes:
set -e
appuploader-cli upload -f build/App.ipa -u "$APPLE_ID" -p "$APP_PASSWORD"
In Windows PowerShell:
& appuploader-cli.exe upload -f .\build\App.ipa -u $env:APPLE_ID -p $env:APP_PASSWORD
if ($LASTEXITCODE -ne 0) { throw "Upload failed" }
One more thing that's easy to misunderstand: a successful command return only means Apple accepted the package and it passed initial validation. Apple still processes it for a few minutes to a few tens of minutes; only after processing does it appear in the "Builds" list in App Store Connect, and only then can you select it for review. Not seeing it immediately after upload is normal.
If Upload Fails, Troubleshoot in This Order
Category 1: The command exits before it even runs. These problems are local; you can verify immediately after fixing them.
-
file not found: the path is wrong. In CI, the most common cause is that the current working directory is not what you think it is; use absolute paths in scripts whenever possible. - Error saying the two login methods cannot be mixed: keep only one set of
-u/-por--api-key/--api-issuer/--private-key. -
not a valid PEM fileorexpected PKCS8:--private-keydoes not point to the.p8Apple gave you, or the file content is corrupted. In CI, the most common cause is losing line breaks when storing the.p8in a Secret; copy the original file completely again and re-save it. -
the ipa has no CFBundleIdentifier: you used the key method to upload a non-IPA package; switch back to the app-specific password method.
Category 2: The package uploaded, but Apple rejected it. These reasons are all written after upload finished with error:. Common ones:
-
Duplicate version/build number: the same
CFBundleShortVersionString+CFBundleVersioncombination has already been uploaded. Apple does not allow overwriting; change the build number and repackage. - Signing mismatch: the certificate or provisioning profile used by the package is not for App Store distribution. Check whether you selected an App Store type provisioning profile when packaging.
-
Missing Info.plist fields: common ones include missing icons, missing required usage descriptions (
NSxxxUsageDescriptionfor camera, photos, location, etc.), or missing certain device capability declarations. Add them according to the error and repackage. - Corresponding app not found: this Bundle ID has not had an app created in App Store Connect yet.
After this command is configured, you basically don't need to touch it again; just run it once when releasing or let CI run it. The same tool can also sign certificates, create Bundle IDs, and generate provisioning profiles on Windows; that's how I bypassed a Mac for the entire iOS release pipeline.
Top comments (0)