先不要用無限重試解決 App Store Connect API 限流。你應先把上傳請求、狀態查詢和後台處理分開,再採用指數退避、幂等任務、Webhook 觸發與有限次數的補償輪詢;若二進位檔交付和 API 狀態管理互相阻塞,就把 Transporter 或 Xcode 上傳鏈路與 API 查詢鏈路拆開。

本週建議動作:先為每次發布建立唯一任務識別碼,停止同一 Build 的重複輪詢;接著用一個真實 TestFlight 構建驗收「已上傳、已處理、可測試」三個狀態。這套方法適合需要持續交付、但不希望一次限流就讓整條任務失敗的團隊。

01

先判斷你遇到的是哪一種故障

這篇文章適合三類讀者:

  • 獨立開發者:在遠端 Mac 上自動上傳 TestFlight 構建,不想因一次限流重做整個發布。
  • 小型團隊:多個 App 或 Runner 共用 App Store Connect API,需要請求隔離與重試邊界。
  • 自動化維護者:正在接入 fastlane、腳本或 CI,希望同時追蹤上傳結果與後台處理狀態。

「構建沒有出現」不是一個足夠精確的故障描述。你要先看證據入口:

故障現象 主要證據入口 下一步動作
API 回應請求過多 HTTP 狀態、回應內容、限流相關資訊 暫停新增輪詢,保存請求記錄並套用退避
二進位檔沒有完成交付 Transporter 或 Xcode 交付記錄 檢查上傳流程與檔案驗證,不要只查 API
上傳完成但 Build 未可測試 Build Uploads 狀態與 App Store Connect 頁面 將「交付完成」和「後台處理完成」分成兩個狀態
任務反覆重啟 遠端 Mac 任務記錄、Runner 狀態 讀取持久化狀態,避免從頭上傳
沒有新事件通知 Webhook 接收記錄與補償查詢記錄 只開啟受控的補償輪詢

Apple 的限流識別文件可用來核對 API 回應與限流判斷。不要把社群提到的固定額度、固定間隔或未公開的後端門檻當成官方規則。

02

第一步:把發布流程拆成四層

遠端 Mac 自動上傳最常見的設計錯誤,是讓「上傳成功」直接等同於「TestFlight 可用」。實際上,至少要保留以下四層邊界:

  1. 上傳執行:由 Transporter、Xcode 或既有交付工具傳送構建。
  2. 上傳結果確認:確認交付工具是否完成,以及是否有可核對的 Build。
  3. Build Processing 查詢:確認 App Store Connect 是否仍在處理、失敗或完成。
  4. 最終可測試確認:確認構建已符合 TestFlight 測試使用條件,而不是只有檔案存在。

Apple 的上傳構建說明與Build Uploads API 資源文件分別描述交付與 API 資源。這正是你不應用單一輪詢器包辦整個流程的原因。

可用下列方式分工:

  • Transporter 或 Xcode:證明「交付動作」是否完成。
  • Build Uploads API:取得可追蹤的上傳資源與狀態。
  • App Store Connect 頁面:作為人工核對入口,不適合當作唯一自動化訊號。
  • Webhook:在狀態事件發生時觸發後續工作,減少持續查詢。

如果你使用的是遠端 Mac,建議把編譯、上傳、狀態管理分成不同工作目錄和任務記錄。你可以先參考遠端 Mac 的 Mac mini 方案,但不要把租用主機本身當成限流修復;真正需要修的是發布狀態機。

03

第二步:避免輪詢把一次發布放大

限流往往不是一次請求造成,而是多個元件同時把同一個問題當成自己的責任。常見放大路徑包括:

  • 固定頻率輪詢,無論狀態是否改變都繼續發送請求。
  • 多個 Job 同時查詢同一個 App、版本或 Build。
  • 失敗任務重啟後遺失舊狀態,再次建立完整上傳流程。
  • 上傳工具、主腳本和監控程式各自查詢同一個狀態。
  • 一個 API 回應延遲,觸發下一層重試,形成重疊工作。

因此,不要直接問「App Store Connect API 應該多久輪詢一次」。正確做法是為每項任務建立請求預算和停止條件。間隔、重試次數及是否繼續查詢,必須依最新官方文件和實際回應決定,不能寫死成通用秒數。

一個脫敏的狀態流轉可以長這樣:

[任務建立]
  task_id=<TASK_ID>
  app_id=<APP_ID>
  build_id=<BUILD_ID>

        ↓

[上傳執行] ── 交付失敗 ──> [停止,保存交付記錄]
        │
        └── 交付完成 ──> [等待事件或受控查詢]
                              │
              ┌───────────────┴───────────────┐
              ↓                               ↓
      [Processing 中]                  [Processing 失敗]
              │                               │
       [Webhook / 補償查詢]             [人工接管]
              │
      [可測試確認] ──> [任務完成]

任務重啟時,先查詢 <TASK_ID> 的持久化狀態。若 <BUILD_ID> 已有完成交付記錄,就不要重新上傳;若只有建立任務而沒有交付證據,才進入明確定義的補償流程。<KEY_ID>、<ISSUER_ID>、<REQUEST_ID> 和主機名也應以占位符保存於記錄中,避免把憑據或可識別資料寫入公開日誌。

04

第三步:用有限重試和幂等恢復取代重跑

遇到 HTTP 429 時,先保存完整回應,再停止同一任務新增的查詢。Apple 的錯誤處理文件可用來核對錯誤處理方式;不要自行推導未公開的限流閾值。

你的恢復邏輯至少需要這些欄位:

  • task_id=<TASK_ID>:一次發布的唯一識別碼。
  • app_id=<APP_ID>、build_id=<BUILD_ID>:避免跨 App 或跨 Build 誤合併。
  • upload_state:尚未開始、進行中、已交付或交付失敗。
  • processing_state:尚未確認、處理中、完成或失敗。
  • last_confirmed_at=<TIMESTAMP>:最後一次有證據的狀態。
  • request_id=<REQUEST_ID>:串起 API 回應與遠端 Mac 記錄。
  • manual_takeover:達到停止條件後交給人工處理。

恢復時依序執行:

  1. 讀取持久化任務,而不是直接建立新任務。
  2. 以 <BUILD_ID> 核對是否已有交付結果。
  3. 若交付未完成,停止並檢查上傳工具記錄。
  4. 若交付完成但處理未結束,等待 Webhook 或進入受控補償查詢。
  5. 若回應持續失敗、狀態矛盾或沒有可核對證據,停止自動化並保留人工接管入口。

優點與限制

有限重試的優點

  • 不會因一次 API 異常而重複上傳相同二進位檔。
  • 任務重啟後能從最後可信狀態繼續。
  • 每個 App、環境和發布階段都能獨立觀察。
  • 失敗原因可回溯到交付、查詢或後台處理其中一層。

需要付出的成本

  • 你必須維護狀態儲存,而不能只依賴終端機輸出。
  • Webhook 接收端需要驗證事件、記錄事件並處理重複通知。
  • 沒有事件的階段仍需保留補償查詢。
  • 團隊要定義何時由自動化轉交人工。
05

第四步:讓 Webhook 和查詢各司其職

Webhook 不是「收到事件就直接發布成功」。它只是一個觸發來源。接收事件後,仍要核對 App、Build 和目前任務狀態,並拒絕把舊事件套用到新任務。

Apple 的Webhook 設定與解析文件及 WebhookEventType 定義可用來核對事件類型與解析方式。設計上可採取以下分工:

  • Webhook 到達:更新「收到事件」時間,不能直接覆寫最終狀態。
  • 事件核對:確認 <APP_ID>、<BUILD_ID> 和 <TASK_ID> 是否一致。
  • 狀態確認:只在需要時做一次 API 查詢,取得可保存的證據。
  • 沒有事件:使用有限的補償查詢,不讓所有 Runner 無限等待。
  • 重複事件:依事件識別資料去重,不重新建立上傳工作。

這樣做的好處,是將高頻查詢改成事件驅動;缺點是你需要處理事件遺失、重複事件和事件先後順序。不要把 Webhook 設計成另一條無限重試隊列。

06

第五步:隔離遠端 Mac 的發布任務

多個 App 共用一台常駐 Mac 時,問題不只在 API。共享重試隊列會讓一個 App 的限流拖慢其他 App,還可能把不同環境的憑據、Build 和日誌混在一起。

建議按以下維度隔離:

  • App:不同 Bundle ID 不共用任務狀態。
  • 環境:測試、預發布和正式發布分開。
  • 階段:編譯、上傳、Processing 確認和可測試確認分開。
  • Runner:每個工作程序只能持有自己的任務識別碼。
  • 憑據:Keychain、API 金鑰檔案和環境變數不要跨專案共用。

遠端 Mac 的 SSH 連線中斷,也不應等同於發布失敗。SSH 只負責控制工作;任務狀態應寫入持久化位置,讓你重新連線後可以讀取最後確認結果。若你需要常駐打包,可再參考遠端 Mac SSH 斷線後的持續工作方案,但仍要保留獨立的上傳與狀態記錄。

07

發布前驗收清單

以下清單要在一次真實 TestFlight 構建上執行,不要只用模擬的成功回應:

  • [ ] 為本次發布建立唯一的 <TASK_ID>。
  • [ ] 記錄 <APP_ID>、<BUILD_ID>、<REQUEST_ID>,並對敏感欄位脫敏。
  • [ ] 保存 Transporter 或 Xcode 的交付結果。
  • [ ] 確認交付完成不會直接把任務標記為可測試。
  • [ ] 將 Processing 狀態和最終可測試狀態分開記錄。
  • [ ] 驗證 Webhook 事件可被核對、去重和保存。
  • [ ] 模擬 Runner 重啟,確認不會重複上傳。
  • [ ] 模擬 API 限流,確認新增輪詢會停止。
  • [ ] 驗證補償查詢有退避、上限和人工接管入口。
  • [ ] 檢查不同 App 的任務不會共用同一重試隊列。
驗收階段 必須保存的證據 通過條件
構建生成 構建識別資料與遠端 Mac 任務記錄 可追溯到唯一任務
二進位檔交付 Transporter 或 Xcode 交付結果 能判斷是否完成上傳
Build 處理 API、Webhook 或頁面核對資料 不把上傳完成誤判為處理完成
TestFlight 可用 最終狀態與人工核對結果 測試者可取得正確構建
任務重啟 重啟前後狀態與請求記錄 不重複上傳、不無限輪詢
處理方式 適合情況 主要風險
保留現有上傳流程,降低查詢頻率 上傳穩定,只是狀態查詢過密 仍需處理事件遺失
上傳與 API 查詢拆成兩條鏈路 交付工具正常,但查詢重試互相阻塞 狀態同步需要持久化
Webhook 加有限補償查詢 希望減少持續輪詢 需處理重複或延遲事件
轉人工接管 狀態矛盾、證據不足或失敗反覆發生 發布流程需要明確交接記錄
08

常見故障的最終判斷

如果一次真實驗收顯示:二進位檔已完成交付、API 查詢可在受控條件下恢復,而且任務重啟不會重複上傳,你可以保留現有方案,只降低自動化查詢頻率並加入 Webhook。

如果上傳工具和 API 查詢共用同一個重試器,或一個 App 的限流會拖住其他 App,就應立即拆分遠端 Mac 任務。不要用增加重試次數來掩蓋狀態設計問題。

若你目前依賴本地電腦、臨時雲端工作階段或共享伺服器,常見缺點是工作階段中斷後狀態不完整、憑據難以隔離,以及沒有穩定的常駐環境承接 SSH 斷線後的任務。對需要持續在線、又不想專門購買一台 Mac 作為發布機的你,租用 VpsMesh 的遠端 Mac 會更容易把上傳、查詢和恢復分開管理;但若你需要長期滿載編譯、實體 USB 裝置或完全掌控硬體,直接購買並維護本地 Mac 仍可能更合適。

你可以先做兩步:把上傳與狀態查詢拆開,再用一次真實 TestFlight 構建驗證重試邊界。只有當這兩步穩定後,才值得把 Webhook、SSH 斷線恢復和多 App 任務隔離納入長期發布流程。