altool "Unable to authenticate" with App Store Connect API key in CI
xcrun altool reached App Store Connect but could not authenticate with the API key you passed. The key id and issuer id must match a key that exists, and the AuthKey_<keyid>.p8 must be readable, otherwise the upload is rejected before the binary is sent.
What this error means
altool --upload-app or --validate-app fails with "Unable to authenticate. Invalid value provided for the API key id" or "Could not find the private key file AuthKey_XXXX.p8". Password-based logins are not affected; only the API key path fails.
Error: Unable to authenticate. Invalid value provided for the API key id 'ABCD1234'.
You can find your API key id in the App Store Connect Users and Access settings.Common causes
Key id or issuer id does not match a real key
The --apiKey value or --apiIssuer UUID is wrong, was revoked, or belongs to a different team than the bundle you upload. App Store Connect rejects the pair.
The .p8 private key file is not found
altool looks for AuthKey_<keyid>.p8 in ./private_keys, ~/private_keys, ~/.private_keys, or ~/.appstoreconnect/private_keys. If the secret was written elsewhere, altool cannot load it.
How to fix it
Place the .p8 where altool searches and pass the right ids
- Write the API key secret to one of altool's search directories as AuthKey_<keyid>.p8.
- Copy the Key ID and Issuer ID from App Store Connect, Users and Access, Integrations.
- Pass both ids on the altool command and re-run the upload.
mkdir -p ./private_keys
echo "$ASC_KEY_P8" > ./private_keys/AuthKey_$ASC_KEY_ID.p8
xcrun altool --upload-app -f App.ipa -t ios \
--apiKey "$ASC_KEY_ID" --apiIssuer "$ASC_ISSUER_ID"Confirm the key is active and scoped
In App Store Connect, Users and Access, Integrations, App Store Connect API, verify the key is not revoked and has at least the App Manager role. A revoked or under-scoped key authenticates as invalid.
How to prevent it
- Store the .p8, key id, and issuer id as CI secrets and never commit them.
- Write the .p8 to ./private_keys at job start so altool finds it deterministically.
- Rotate the key in one place and update the secret, not the workflow.