# ITMS-91053 修正：Expo 缺少 API 宣告

> ITMS-91053 是 build 用了需宣告理由的 API 卻沒宣告。在 ios.privacyManifests 加上類別與代碼，再重新 build。

原始頁面：[ITMS-91053 修正：Expo 缺少 API 宣告](https://devoria.fr/zh-hant/blog/itms-91053-missing-api-declaration-expo)
作者：[Mehdi Azizi](https://devoria.fr/zh-hant/mehdi-azizi)
發布日期：2026-10-25
內容最後修訂：2026-10-25


ITMS-91053 是 Apple 的自動檢查，會在[你上傳 build 之後](https://devoria.fr/zh-hant/blog/eas-submit-ios-expo-app-store)用 e-mail 寄給你。它的意思是：你 app 的程式碼用了「需宣告理由」的 API（偏好設定、檔案時間戳、開機後經過時間、磁碟空間或鍵盤），卻沒有在隱私權清單裡說明原因。**用 Expo 的話，修正方式是在 `ios.privacyManifests` 裡加上 Apple 指出的類別、描述你實際用途的代碼，然後重新 build。**

在 Solys（我做的小遊戲鬧鐘）上，這些理由都宣告在 app 的設定裡。下面是它真實的宣告內容、列出相依套件用了哪些 API 的指令，以及上傳前要對最終檔案做的檢查。

## ITMS-91053 錯誤是什麼意思？

這封信的標題是「ITMS-91053: Missing API declaration」。它指出你 build 裡某個特定檔案的程式碼，引用了一個或多個需要宣告理由的 API。接著會列出相關的類別，例如 `NSPrivacyAccessedAPICategoryUserDefaults`。

這些 API 不需要向使用者要求任何權限。但它們可以被用來辨識裝置，也就是 Apple 說的 fingerprinting。這是被禁止的，就算使用者同意追蹤也一樣。Apple 在[需宣告理由的 API](https://developer.apple.com/documentation/bundleresources/describing-use-of-required-reason-api) 頁面說明：每個 app 和每個 SDK 都必須宣告使用它們的原因。

同一頁也定下規則：**自 2024 年 5 月 1 日起，App Store Connect 不再接受沒在清單中說明這些用途的 app。**有開發者回報，這封信只是個警告，build 還是被接受了。別指望這樣：把它當成阻擋，在下次上傳前修好。

跟其他 ITMS 錯誤一樣，這不是審查人員退件。沒有人打開你的 app：是 Apple 分析了你的二進位檔。

## iOS 隱私權清單：PrivacyInfo.xcprivacy 檔案

隱私權清單是一個名為 `PrivacyInfo.xcprivacy` 的檔案，放在 app bundle 的根目錄。根據 [Apple 關於 privacy manifests 的文件](https://developer.apple.com/documentation/bundleresources/privacy-manifest-files)，它描述收集的資料、追蹤行為，以及需宣告理由的 API。後者放在 `NSPrivacyAccessedAPITypes` 陣列裡：每個類別一筆，包含類型和理由清單。

容易踩雷的地方：**一份清單只涵蓋它自己的 bundle。**Apple 寫得很清楚：每個使用這些 API 的執行檔或動態函式庫，都必須在自己的 bundle 裡有一份清單。你 app 的清單不涵蓋內嵌的 framework，SDK 也不能靠你 app 的清單。

別把它跟 [ITMS-90683](https://devoria.fr/zh-hant/blog/itms-90683-missing-purpose-string-expo) 要求的 Info.plist 說明文字搞混。那些文字會在權限請求中顯示給使用者看。隱私權清單則只有 Apple 會讀。

## 5 個 API 類別與理由代碼

類別的完整名稱以 `NSPrivacyAccessedAPICategory` 開頭。API 和理由的清單在 [NSPrivacyAccessedAPIType](https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitype) 這個 key 的頁面上，我在 2026 年 10 月 15 日查閱。

| 類別            | 典型 API                     | 常見理由 |
| --------------- | ---------------------------- | -------- |
| UserDefaults    | `UserDefaults`               | CA92.1   |
| FileTimestamp   | `creationDate`               | C617.1   |
| SystemBootTime  | `systemUptime`               | 35F9.1   |
| DiskSpace       | `volumeAvailableCapacityKey` | E174.1   |
| ActiveKeyboards | `activeInputModes`           | 54BD.1   |

你最常遇到的代碼：

- **CA92.1**：app 讀寫自己的偏好設定，其他 app 無法存取。
- **1C8F.1**：跟同一個 App Group 的 widget 或 extension 共用偏好設定。
- **C617.1**：存放在 app 容器內檔案的日期或大小。
- **3B52.1**：使用者選取的檔案，例如透過文件選擇器。
- **35F9.1**：測量 app 內兩個事件之間經過的時間，或執行計時器。
- **E174.1**：寫入檔案前檢查剩餘空間，且對使用者有可見的影響。
- **85F4.1**：向使用者顯示磁碟空間。

**宣告符合你實際用途的理由，而不是看起來涵蓋最廣的那個。**Apple 明確表示，這些 API 和它們的資料只能用於宣告的理由，絕不能用於追蹤。有兩個代碼是保留給第三方 SDK 的：C56D.1 和 0A2A.1。它們不該出現在你 app 的清單裡。

## 在 Expo 中宣告隱私權清單

用 Expo 的話，你不用自己寫這個檔案。你在 app.json 或 app.config 的 `expo.ios.privacyManifests` 底下描述它，prebuild 就會產生 `PrivacyInfo.xcprivacy`。這是 [Expo 關於 privacy manifests 的文件](https://docs.expo.dev/guides/apple-privacy/)提供的方法。

以下是 Solys 設定的真實片段。這個 key 裡還有追蹤和收集資料的內容，這裡先拿掉了：

```json
{
  "expo": {
    "ios": {
      "privacyManifests": {
        "NSPrivacyAccessedAPITypes": [
          {
            "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryUserDefaults",
            "NSPrivacyAccessedAPITypeReasons": ["CA92.1", "1C8F.1"]
          },
          {
            "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryFileTimestamp",
            "NSPrivacyAccessedAPITypeReasons": ["C617.1"]
          },
          {
            "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategorySystemBootTime",
            "NSPrivacyAccessedAPITypeReasons": ["35F9.1"]
          }
        ]
      }
    }
  }
}
```

CA92.1、C617.1 和 35F9.1 對應的是 React Native 和 app 裡 Expo 模組所宣告的內容。1C8F.1 則來自 Solys 本身：app 透過 App Group 跟它的 widget 共用設定。

有個陷阱要知道：如果你的 `ios` 資料夾是自動產生並被 git 忽略的，**你在這個資料夾裡手寫的 `PrivacyInfo.xcprivacy` 永遠不會進到 build 裡。**EAS 會重跑 prebuild，重新產生整個資料夾。我在 2026 年 9 月 9 日的一個專案上親眼看到：手寫的清單在 prebuild 之後就不見了。宣告必須放在設定裡。

最後一點：清單是二進位檔的一部分。遠端更新改不了它。你必須重新 build 再上傳。

## 自帶清單的第三方 SDK

大部分較新的函式庫都會附上自己的清單。使用這些 API 的 Expo 套件在它們的 `ios` 資料夾裡都有 `PrivacyInfo.xcprivacy`，React Native 也一樣。問題出在它們連結到你 app 的方式。

Expo 的文件提醒：Apple 無法正確讀取所有以靜態方式連結的 CocoaPods 相依套件的清單，而 Expo 套件正是這種情況。所以你可能得把它們的理由複製到你 app 的設定裡。

為了減少這種工作，[expo-build-properties](https://docs.expo.dev/versions/latest/sdk/build-properties/) 的 `ios.privacyManifestAggregationEnabled` 設定會把 CocoaPods 相依套件的清單合併成單一檔案。在 Expo 為我的專案（SDK 57）產生的 Podfile 裡，這個合併預設是開啟的，除非你把它關掉。

合併有個限制：它只看得到有提供清單的相依套件。沒有清單的舊函式庫就看不到。**所以要讀 e-mail 裡指出的檔案路徑**：它告訴你該去哪裡修。

| 指出的檔案              | 修正位置               |
| ----------------------- | ---------------------- |
| app 的執行檔            | `ios.privacyManifests` |
| 某個 `.framework`       | 更新該 SDK             |
| 某個 `.appex` extension | extension 的清單       |

## 上傳前先檢查

Expo 建議在上傳後等 Apple 的 e-mail。這樣可行，但每來回一次就要多花一次 build。以下是我在上傳前做的四項檢查。

**1. 列出相依套件宣告了什麼。**在專案根目錄執行：

```bash
find node_modules -name PrivacyInfo.xcprivacy -not -path '*/example/*' \
  -exec grep -ho "NSPrivacyAccessedAPICategory[A-Za-z]*" {} + | sort | uniq -c
```

在 Solys 上，2026 年 10 月 15 日執行這個指令，找到 13 份清單和四個類別：檔案時間戳、偏好設定、開機後經過時間和磁碟空間。列出的每個類別都必須出現在 app 的最終清單裡，或出現在使用它的 framework 的清單裡。

**2. 搜尋你自己的原生程式碼。**自製模組、plugin、像 widget 這樣的 target：這些程式碼沒有任何 SDK 會替你宣告。

```bash
grep -rlE "UserDefaults|systemUptime|mach_absolute_time|creationDate|modificationDate|volumeAvailableCapacity|activeInputModes" \
  --include='*.swift' --include='*.m' --include='*.mm' modules plugins targets
```

依你的專案調整資料夾。在 Solys 上，就是這個搜尋找出了透過 App Group 共用的偏好設定，所以才有 1C8F.1 這個代碼。

**3. 讀產生出來的清單。**執行 `npx expo prebuild -p ios`，然後打開 `ios/<YourAppName>/PrivacyInfo.xcprivacy`。注意：這個指令可能會刪掉再重建 `ios` 資料夾。如果你在裡面手動改過東西，請在專案的副本上操作。

**4. 檢查最終 build。**這是唯一算數的證據。從 expo.dev 的 build 頁面下載 IPA，然後在你的 Mac 上執行：

```bash
unzip -q MyApp.ipa -d ipa
find ipa/Payload -name PrivacyInfo.xcprivacy
plutil -p ipa/Payload/*.app/PrivacyInfo.xcprivacy
```

第二行會列出所有內嵌的清單，包括 app 的和 framework 的。第三行會顯示 app 的清單，拿來跟第 1 步的清單比對。

## 清單要跟你的商店頁面說法一致

清單不是單獨存在的。如果它宣告了追蹤或收集資料，你的 App Store 頁面和隱私權政策也必須說一樣的事。三者之間有落差，之後可能會依 [guideline 5.1.1](https://devoria.fr/zh-hant/blog/guideline-5-1-1-app-store-rejection-fix) 被人工審查退件。要對齊哪些內容，詳見[app 的隱私權政策](https://devoria.fr/zh-hant/blog/ios-app-privacy-policy-guide)。

## 上傳 build 不再有意外

只要看得懂，ITMS 錯誤很快就能修好。接下來的退件則需要多一點判斷：處理方式在[被 Apple 退件的 app](https://devoria.fr/zh-hant/expertise/app-rejected-by-apple) 這頁，最常見的原因在[Apple 為什麼退你的 app](https://devoria.fr/zh-hant/blog/apple-app-rejection-reasons-real-cases)。

想在上傳前檢查你的資料，[Apple Review Kit](https://devoria.fr/zh-hant/kits/review-apple) 收錄了我的檢查清單，以及會幫你審視專案的 Claude Code skills。如果上傳卡住了，想找人幫你看看，[跟我預約一小時](https://devoria.fr/zh-hant/coaching)：我們透過視訊，在你的螢幕上一起檢查專案。如果你正在準備第一次上架，[做免費診斷](https://devoria.fr/zh-hant/diagnostic)：它會幫你把步驟排好順序，並寄給你我的 Apple 防退件檢查清單。

## 常見問題

### ITMS-91053 會擋住我的 build 上傳嗎？

Apple 寫明，自 2024 年 5 月 1 日起，App Store Connect 不再接受沒說明這些 API 用途的 app。不過也有開發者在 build 被接受的情況下收到這封信，當作警告。不管哪種情況，都要在下次 build 前修好清單。

### UserDefaults 該選哪個理由代碼？

如果 app 只讀寫自己的偏好設定，選 CA92.1。如果它透過 App Group 跟 widget 或 extension 共用，再加上 1C8F.1。C56D.1 是保留給第三方 SDK 的：不要放進你 app 的清單。

### 每個 SDK 的清單都要複製到 app.json 嗎？

不一定。如果清單合併有開啟，CocoaPods 相依套件的清單會在安裝 pods 時合併在一起。只有當最終清單缺少某個類別時才需要複製，並附上描述實際用途的理由。

### 我的 widget 或 extension 需要自己的清單嗎？

根據 Apple，每個包含使用這些 API 的執行檔的 bundle，都必須有自己的清單。extension 是獨立的執行檔，有自己的 bundle：app 的清單涵蓋不到它。

### 不重新 build 能修正 ITMS-91053 嗎？

不能。隱私權清單是二進位檔的一部分。修正設定、重新 build，然後先在 [TestFlight](https://devoria.fr/zh-hant/blog/testflight-test-iphone-app-before-review) 測試，再送審。


## 你的專案

[Apple Review Kit](https://devoria.fr/zh-hant/kits/review-apple)：我使用的 App Store 審核檢查清單、我被 Apple 退件後的修正、給審核人員的備註，以及兩個審閱你 App 的 Claude Code skills。29 €。

[iOS Kit](https://devoria.fr/zh-hant/kits/ios)：用 Claude Code 打造你的 iPhone App：我的 App 所用的 Starter Code、檢查成果的 skills，以及 Apple Review Kit、App Store Screenshots Kit 與 ASO Kit。129.99 €。
