DEV Community

xxhshenqi
xxhshenqi

Posted on

-22938 upload error: it was not Apple blocking password auth, it was a User-Agent change

If you upload your IPA with a tool that authenticates using an Apple ID and an app-specific password (ios-uploader, custom CI scripts, etc.), it stopped working around October 28, 2026.

The symptom

Found Bundle ID "com.example.app", Version 125 (1.2.5).
Unexpected token 'S', "Sign in wi"... is not valid JSON
Enter fullscreen mode Exit fullscreen mode

The IPA itself is fine — the failure happens at the authentication step.

Step 1: rule out your credentials

The same app-specific password still authenticates fine against Apple's CalDAV endpoint (HTTP 207). But hitting the upload auth endpoint returns plain text instead of JSON:

Sign in with the app-specific password you generated. If you forgot the
app-specific password or need to create a new one, go to account.apple.com. (-22938)
Enter fullscreen mode Exit fullscreen mode

Same account, same password, one works and one doesn't. So the credentials are not the problem.

Step 2: find the real cause (my first guess was wrong)

I tried every variant I could think of — gzip, HTTP/2, different endpoints, an API-key JWT as cookie or Bearer — and all were rejected with the identical message. So I concluded "Apple closed this channel".

The real cause: Apple tightened the User-Agent validation. It must match a specific full format:

iTMSTransporter/4.3.0.4 (Windows 11; 10.0; amd64; Java 21.0.5) SmartClient/0.1.0
Enter fullscreen mode Exit fullscreen mode

With that UA, login works again — same app-specific password:

[old UA] iTMSTransporter/4.2.0                     -> rejected
[new UA] iTMSTransporter/4.3.0.4 (Windows 11;...)  -> SessionId returned, login OK
Enter fullscreen mode Exit fullscreen mode

The error message only says "sign in with an app-specific password" and never mentions the UA — extremely misleading. The ios-uploader maintainer hit the same wall and shipped v4.0.3 with the fix.

The real takeaway: these tools depend on Apple's undocumented internal endpoints and "magic strings". Apple can change one silently with zero notice, and you get to guess again.

A more durable approach: the official Build Uploads API

To be clear — for occasional uploads, ios-uploader v4.0.3 is fixed, just keep using it. The approach below is for when the upload path has to be dependable long-term: CI automation, shared team tooling, and not re-guessing Apple's internals every time.

App Store Connect has a documented Build Uploads resource that does exactly this. Authentication uses an API key (JWT) and the contract is public — no reliance on hidden conventions like UA formats:

POST  /v1/buildUploads            create a build upload (version, build number, platform)
POST  /v1/buildUploadFiles        get presigned object-storage upload URLs
PUT   <presigned-url>             upload the IPA in parts (5 MB chunks)
PATCH /v1/buildUploadFiles/{id}   mark uploaded=true
-> Apple parses the build; it shows up in TestFlight / App Store Connect
Enter fullscreen mode Exit fullscreen mode

Gotcha: valid assetType values are ASSET, ASSET_DESCRIPTION, and ASSET_SPI — not IPA.

What you need

  1. An App Store Connect API key: App Store Connect → Users and Access → Integrations → App Store Connect API → generate a key (role App Manager or higher). Download the .p8 (only downloadable once), note the Key ID and Issuer ID.
  2. Your app's numeric App ID (the number after apps/ in the App Store Connect URL).

A working CLI

I packaged this flow into a zero-dependency Node.js CLI (built-in modules only, works on Windows / macOS / Linux):

# install (no npm account needed, ~2s)
npm i -g https://github.com/xxhshenqi/asc-ipa-uploader/archive/refs/heads/master.tar.gz

# look up your App ID
asc-ipa-uploader --key ./AuthKey_XXXX.p8 --key-id XXXXXXXXXX \
  --issuer <ISSUER_ID> --list-apps

# upload (version = CFBundleShortVersionString, build = CFBundleVersion)
asc-ipa-uploader --key ./AuthKey_XXXX.p8 --key-id XXXXXXXXXX \
  --issuer <ISSUER_ID> --app <APP_ID> --ipa ./MyApp.ipa --version 1.2.5 --build 125

# optionally create the App Store version and attach the build afterwards
asc-ipa-uploader ... --release
Enter fullscreen mode Exit fullscreen mode

A few things it handles for you:

  • checks whether the same build number already exists before uploading (saves you a pointless 17 MB transfer)
  • retries the transient 401s that brand-new API keys produce while Apple propagates them
  • uploads in chunks with progress output
  • polls until the build reaches VALID

In my test a 17 MB IPA uploaded and reached VALID in about two minutes.

Notes / caveats

  • Never commit your .p8. The CLI also accepts env vars: ASC_KEY_PATH, ASC_KEY_ID, ASC_ISSUER, ASC_APP_ID.
  • Apple rejects duplicate build numbers — bump the build number to re-upload.
  • If you have a Mac and use Xcode or the official Transporter app, you are unaffected by this change. The breakage hits Windows/Linux CI pipelines and anyone uploading without a Mac.

If this saves you a day of debugging, a star is appreciated. Issues and PRs welcome.

Top comments (0)