appuploader-cli Tutorial: Uploading an IPA to the App Store from the Command Line on Windows

appuploader-cli tutorial: upload an IPA to the App Store from the command line on Windows with appuploader-cli upload -f IPA -u account -p app-specific-password, no two-factor prompt, check the exit code on failure. Teams and CI switch to API key login; pick one of the two methods. Pipeline examples: GitHub Actions, Jenkins/Shell, Windows PowerShell. The info command generates AppStoreInfo.plist locally. No Mac or Xcode required throughout.

appuploader-cli Tutorial: Uploading an IPA to the App Store from the Command Line on Windows

One command pushes your built IPA to App Store Connect — no Mac, no Xcode:

1appuploader-cli upload -f Payload.ipa -u dev@example.com -p abcd-efgh-ijkl-mnop

appuploader-cli is the command-line tool bundled with AppUploader (开心上架); once the app is installed, it’s right there in the install directory. Below is a walkthrough from scratch: how to find it, what you need before your first upload, which login method to use for teams and pipelines, and how to diagnose failed uploads.

When you need the command line

If a couple of clicks in the GUI can submit your build, most people don’t need the CLI. It’s really for these three situations:

  • The build is produced automatically. CI packages a build every time you commit, and then someone still has to click upload manually — that step should be wired in.
  • The upload has to be run by someone else. Outsourcing, agency work, releasing multiple apps in rotation — a script beats verbally explaining which button to click.
  • On a machine with no GUI. Such as a company build server with only a remote terminal.

If you only publish manually once in a while, clicking twice in the GUI is actually faster; the value of the CLI is in repeated execution.

Before you start

1. You need a signed package first. appuploader-cli handles the “hand the package to Apple” step; it does not compile or sign. You must already have a built .ipa (iOS) or .pkg (macOS).

2. Find the command. Once AppUploader is installed, it’s already in the install directory:

System Location
Windows appuploader-cli.exe in the same directory as the main program AppUploader.exe
macOS AppUploader.app/Contents/Resources/appuploader-cli

Open a terminal in that directory and you can use it directly. If you don’t want to cd there every time, add the directory to your system PATH (on Windows: Settings → System → About → Advanced system settings → Environment Variables → select Path → Edit → New, paste the directory path, then reopen a terminal for it to take effect).

Run one command to confirm it works:

1appuploader-cli --help

If it lists upload and info, you’ve found the right one.

First upload: using an app-specific password

The most convenient credential for logging in to Apple from the command line is an app-specific password — Apple created these specifically for third-party tools, they do not trigger two-factor authentication, and you don’t have to confirm on your phone for every upload.

Generate one first: sign in at https://account.apple.com/account/manage, find “App-Specific Passwords,” and create a new one. You’ll get a password shaped like abcd-efgh-ijkl-mnop, shown only once, so copy and save it right away.

Then:

1appuploader-cli upload -f Payload.ipa -u dev@example.com -p abcd-efgh-ijkl-mnop

Three arguments: -f is the path to the package (you can also omit -f and put the path at the end of the command), -u is the Apple ID email, and -p is the app-specific password you just created.

Make sure -p is the app-specific password, not the password you normally use to sign in to your Apple ID. An app-specific password is always 16 letters with three hyphens in the middle, looks nothing like your login password, and you can tell at a glance whether you got it wrong.

After you press Enter, progress prints line by line, and a few minutes later you’ll see this line, meaning success:

> upload finished successfully

On failure it’s > upload finished with error:, followed directly by the reason Apple gave — a duplicate version number, a signature mismatch, a missing Info.plist field, and so on; just fix accordingly.

In scripts you don’t need to read the logs: on failure the command’s exit code is non-zero, so checking the exit code is enough.

Don’t rush to App Store Connect to look for the build right after uploading — Apple takes anywhere from a few minutes to a few tens of minutes to process the package, and only after that does it appear in the “Builds” list, at which point you can select it for review.

Uploading macOS / tvOS / visionOS packages

It’s treated as iOS by default; add --type for other platforms:

1appuploader-cli upload -u dev@example.com -p abcd-efgh-ijkl-mnop --type osx App.pkg

There are only four possible values: ios (default), osx, appletvos, xros (visionOS).

Teams and CI: switch to API key login

The approach in the previous section has a problem: the command carries an individual’s Apple ID and password. If that person leaves, changes their password, or simply doesn’t want to hand their account to the pipeline, the whole automation breaks.

For team scenarios, an App Store Connect API key is more appropriate: it belongs to the team rather than an individual, can be revoked independently, and doesn’t involve two-factor authentication.

Generate one at https://appstoreconnect.apple.com/access/api (requires Account Holder or Admin permission) and you’ll get three things:

  • Key ID (a string of uppercase letters and digits)
  • Issuer ID (at the top of the page, shaped like 69a6de78-4459-47e3-e053-5b8c7c11a4d1)
  • The .p8 private key filedownloadable only once, Apple gives no second chance, so save it immediately

Then replace -u -p with these three arguments:

1appuploader-cli upload -f Payload.ipa \
2  --api-key UK29KBAX9X \
3  --api-issuer 69a6de78-4459-47e3-e053-5b8c7c11a4d1 \
4  --private-key AuthKey_UK29KBAX9X.p8

The two login methods are mutually exclusive; mixing them is rejected outright.

Confirm two things before using an API key:

  1. You can only upload .ipa. This method relies on the Bundle ID in the package to find the corresponding app on App Store Connect, which can’t be obtained from a .pkg. For macOS packages, stick with the app-specific password from the previous section.
  2. An app must already exist in App Store Connect for this Bundle ID. If not, you’ll get no App Store Connect app found for bundle ID — create the app first, then upload.

Wiring it into a pipeline

The idea is one sentence: after the build produces the IPA, add an upload step, and pass credentials via the CI’s built-in secrets rather than writing them in plaintext in the script.

GitHub Actions (store the three items as repository secrets):

 1- name: Upload to App Store Connect
 2  env:
 3    ASC_KEY_ID:    ${{ secrets.ASC_KEY_ID }}
 4    ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
 5    ASC_P8:        ${{ secrets.ASC_P8 }}
 6  run: |
 7    printf '%s' "$ASC_P8" > AuthKey.p8
 8    appuploader-cli upload -f build/App.ipa \
 9      --api-key "$ASC_KEY_ID" \
10      --api-issuer "$ASC_ISSUER_ID" \
11      --private-key AuthKey.p8
12    rm -f AuthKey.p8

Don’t skip that last rm line — the private key shouldn’t be left in the Runner’s working directory.

Jenkins / generic shell:

1set -e   # abort the whole pipeline if the upload fails
2appuploader-cli upload -f "$WORKSPACE/build/App.ipa" \
3  -u "$APPLE_ID" -p "$APP_SPECIFIC_PASSWORD"

Windows batch / PowerShell:

1& "C:\Program Files\AppUploader\appuploader-cli.exe" upload `
2  -f .\build\App.ipa -u $env:APPLE_ID -p $env:APP_PASSWORD
3if ($LASTEXITCODE -ne 0) { throw "Upload failed" }

The whole process needs no Mac; even if the build machine is Windows, this step still runs fine.

By the way: inspecting a package without uploading

There’s also the info command, which doesn’t upload over the network — it only reads the package locally and generates the resource description file App Store needs, AppStoreInfo.plist:

1appuploader-cli info -u dev@example.com Payload.ipa -o AppStoreInfo.plist

Two uses: confirm the package itself is fine before uploading (on success it prints the package’s Bundle ID and size, so you can double-check you’re uploading the right one); or your process uses a different upload tool and you’re only missing this plist.

Without -o it prints to the terminal, which is handy for piping. info only supports the -u account form, with no API key mode.