DEV Community

xxhshenqi
xxhshenqi

Posted on

Apple blocked app-specific-password uploads (error -22938). Here's how to upload IPAs with the official Build Uploads API

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: confirm it's server-side

I tried every variant I could think of — all rejected with the identical message:

  • plain and gzip-compressed request bodies
  • HTTP/1.1 and HTTP/2
  • the JSON-RPC endpoint and the SOAP /ws/ endpoint
  • different User-Agents (iTMSTransporter/4.2.0, 4.3.1, Transporter/5.0)
  • an App Store Connect API key JWT sent as the myacinfo cookie or a Bearer token
  • the contentdelivery02 host

Even a nonexistent JSON-RPC method name returns the same login prompt — which means the request never reaches the backend service. It is rejected at the gateway (response headers show Server: daiquiri/5, instance tag 26HOTFIX47).

Conclusion: Apple closed the Apple-ID + app-specific-password login path for this service at the gateway. No client-side parameter tweak will get around it.

The fix: the official Build Uploads API

App Store Connect has a documented Build Uploads resource that does exactly this, authenticated with an API key (JWT) — it never touches the blocked endpoints:

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)