xcrun altool: Upload and Validate iOS Builds in CI
xcrun altool --upload-app sends a built .ipa to App Store Connect (TestFlight/App Store); --validate-app checks it first without uploading.
altool is the command-line uploader for iOS builds. In CI you authenticate with an App Store Connect API key (not a password), because interactive 2FA is impossible on a headless runner.
What it does
xcrun altool validates and uploads app packages to App Store Connect. --validate-app runs the same checks as upload without publishing; --upload-app transfers the .ipa. Authentication is via an App Store Connect API key (--apiKey / --apiIssuer) with the key file in a known location, or a legacy app-specific password.
Common usage
xcrun altool --validate-app -f App.ipa -t ios \
--apiKey "$ASC_KEY_ID" --apiIssuer "$ASC_ISSUER_ID"
xcrun altool --upload-app -f App.ipa -t ios \
--apiKey "$ASC_KEY_ID" --apiIssuer "$ASC_ISSUER_ID"
# the .p8 must be at ~/.appstoreconnect/private_keys/AuthKey_<KEYID>.p8Options
| Flag | What it does |
|---|---|
| --upload-app | Upload the package to App Store Connect |
| --validate-app | Validate without uploading |
| -f <file> | The .ipa (or .pkg) to process |
| -t <platform> | ios, macos, or tvos |
| --apiKey <keyId> | App Store Connect API key id |
| --apiIssuer <issuerId> | API key issuer id |
In CI
Use an App Store Connect API key: store the .p8 as a secret, write it to ~/.appstoreconnect/private_keys/AuthKey_<KEYID>.p8, and pass --apiKey/--apiIssuer. This avoids app-specific passwords and 2FA prompts. Note Apple recommends notarytool/Transporter for newer flows, but altool still uploads app builds.
Common errors in CI
"Unable to authenticate with App Store Connect ... API key" means the .p8 is not in the expected private_keys path or the key id/issuer is wrong. "*** Error: ... ITMS-90022: Missing required icon" and similar ITMS-90xxx codes are asset/metadata validation failures in the build. "ITMS-90161: Invalid provisioning profile" points at signing. "The provided entity includes an attribute with a value that has already been used" is a duplicate build number.