Sửa lỗi ITMS-91053 Missing API declaration với Expo
Bởi Mehdi Azizi
Đăng ngày , 8 phút
ITMS-91053: build dùng API cần lý do mà chưa khai báo. Thêm danh mục và mã lý do vào ios.privacyManifests, rồi build lại.
ITMS-91053 là một bước kiểm tra tự động của Apple, được gửi qua e-mail sau khi bạn nộp build. Nó báo rằng code của app bạn dùng một API "cần lý do bắt buộc" (preferences, ngày tạo/sửa file, thời gian kể từ khi khởi động, dung lượng ổ đĩa hoặc bàn phím) mà không giải thích lý do trong privacy manifest. Với Expo, bạn sửa trong ios.privacyManifests: thêm danh mục Apple nêu, mã mô tả đúng cách bạn dùng, rồi build lại.
Trên Solys, app báo thức kèm mini-game của mình, các lý do này được khai báo trong cấu hình app. Dưới đây là khai báo thật của nó, lệnh liệt kê những gì các dependency của bạn dùng, và bước kiểm tra file cuối cùng trước khi gửi.
Lỗi ITMS-91053 nghĩa là gì?
Thông báo có tiêu đề "ITMS-91053: Missing API declaration". Nó cho biết code trong một file cụ thể của build tham chiếu tới một hoặc nhiều API cần khai báo lý do. Sau đó nó liệt kê các danh mục liên quan, ví dụ NSPrivacyAccessedAPICategoryUserDefaults.
Những API này không xin quyền gì từ người dùng. Nhưng chúng có thể bị dùng để nhận dạng thiết bị, thứ Apple gọi là fingerprinting. Việc này bị cấm, kể cả khi người dùng đồng ý cho theo dõi. Apple giải thích trong trang về API cần lý do bắt buộc: mỗi app và mỗi SDK phải khai báo lý do dùng chúng.
Cùng trang đó đặt ra quy tắc: từ ngày 1/5/2024, App Store Connect không chấp nhận app nào không mô tả các cách dùng này trong manifest. Một số developer kể rằng họ nhận e-mail này chỉ như một cảnh báo, trên một build vẫn được chấp nhận. Đừng trông chờ vào điều đó: hãy coi nó là lỗi chặn và sửa trước lần gửi tiếp theo.
Giống các lỗi ITMS khác, đây không phải là reviewer từ chối. Chưa ai mở app của bạn cả: Apple chỉ phân tích file binary.
Privacy manifest iOS: file PrivacyInfo.xcprivacy
Privacy manifest là một file tên PrivacyInfo.xcprivacy, đặt ở gốc bundle của app. Theo tài liệu của Apple về privacy manifest, nó mô tả dữ liệu được thu thập, việc theo dõi và các API cần lý do. Các API này nằm trong mảng NSPrivacyAccessedAPITypes: mỗi danh mục một mục, gồm loại và danh sách lý do.
Điểm dễ mắc bẫy: một manifest chỉ áp dụng cho chính bundle của nó. Apple ghi rõ: mỗi file thực thi hoặc thư viện động dùng các API này phải có manifest trong bundle của mình. Manifest của app không bao gồm framework nhúng, và một SDK không thể dựa vào manifest của app bạn.
Đừng nhầm nó với các câu mô tả trong Info.plist mà ITMS-90683 yêu cầu. Những câu đó hiển thị cho người dùng khi xin quyền. Còn manifest thì chỉ Apple đọc.
5 danh mục API và mã lý do
Tên đầy đủ của một danh mục bắt đầu bằng NSPrivacyAccessedAPICategory. Danh sách API và lý do nằm ở trang của khóa NSPrivacyAccessedAPIType, mình đọc ngày 15/10/2026.
| Danh mục | API điển hình | Lý do phổ biến |
|---|---|---|
| UserDefaults | UserDefaults | CA92.1 |
| FileTimestamp | creationDate | C617.1 |
| SystemBootTime | systemUptime | 35F9.1 |
| DiskSpace | volumeAvailableCapacityKey | E174.1 |
| ActiveKeyboards | activeInputModes | 54BD.1 |
Những mã bạn sẽ gặp nhiều nhất:
- CA92.1: app đọc và ghi preferences của riêng nó, các app khác không truy cập được.
- 1C8F.1: preferences chia sẻ với widget hoặc extension trong cùng App Group.
- C617.1: ngày hoặc kích thước của file nằm trong container của app.
- 3B52.1: file do người dùng chọn, ví dụ qua trình chọn tài liệu.
- 35F9.1: đo thời gian trôi qua giữa hai sự kiện trong app, hoặc chạy bộ hẹn giờ.
- E174.1: kiểm tra dung lượng trống trước khi ghi file, có tác động mà người dùng thấy được.
- 85F4.1: hiển thị dung lượng ổ đĩa cho người dùng.
Hãy khai báo lý do đúng với cách bạn thực sự dùng, không phải lý do nghe có vẻ rộng nhất. Apple nói rõ các API này và dữ liệu của chúng chỉ được dùng cho lý do đã khai báo, không bao giờ để theo dõi. Hai mã dành riêng cho SDK bên thứ ba: C56D.1 và 0A2A.1. Chúng không thuộc về manifest của app bạn.
Khai báo privacy manifest trong Expo
Với Expo, bạn không tự viết file. Bạn mô tả nó dưới expo.ios.privacyManifests, trong app.json hoặc app.config, và prebuild sẽ tạo ra PrivacyInfo.xcprivacy. Đây là cách trong tài liệu của Expo về privacy manifest.
Đây là đoạn trích thật từ cấu hình của Solys. Khóa này còn chứa phần theo dõi và dữ liệu thu thập, mình đã bỏ đi ở đây:
{
"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 và 35F9.1 lấy lại những gì React Native và các module Expo của app khai báo. 1C8F.1 đến từ chính Solys: app chia sẻ cài đặt với widget qua một App Group.
Một cái bẫy cần biết: nếu thư mục ios của bạn được tạo tự động và bị git bỏ qua, file PrivacyInfo.xcprivacy viết tay trong thư mục đó sẽ không bao giờ vào được build. EAS chạy lại prebuild và tạo lại thư mục. Mình đã gặp chuyện này ở một project ngày 9/9/2026: manifest viết tay biến mất sau một lần prebuild. Khai báo phải nằm trong cấu hình.
Điểm cuối: manifest là một phần của binary. Cập nhật từ xa không thay đổi được nó. Bạn phải build lại và gửi lại.
Các SDK bên thứ ba có sẵn manifest riêng
Phần lớn thư viện mới đều kèm manifest. Các package Expo dùng những API này có PrivacyInfo.xcprivacy trong thư mục ios, React Native cũng vậy. Vấn đề nằm ở cách chúng được liên kết vào app.
Tài liệu Expo cảnh báo: Apple không đọc đúng mọi manifest của các dependency CocoaPods liên kết tĩnh, mà package Expo lại thuộc trường hợp này. Vì vậy có thể bạn phải chép lại lý do của chúng vào cấu hình app.
Để bớt việc, tùy chọn ios.privacyManifestAggregationEnabled của expo-build-properties gộp manifest của các dependency CocoaPods thành một file. Trong Podfile mà Expo tạo ra cho các project của mình (SDK 57), việc gộp này được bật sẵn, trừ khi bạn tắt nó.
Việc gộp có giới hạn: nó chỉ thấy các dependency có manifest. Một thư viện cũ không có manifest thì vẫn vô hình. Vì vậy hãy đọc đường dẫn file được nêu trong e-mail: nó cho bạn biết cần sửa ở đâu.
| File được nêu | Sửa ở đâu |
|---|---|
| File thực thi của app | ios.privacyManifests |
Một .framework | Cập nhật SDK |
Một extension .appex | Manifest của extension |
Kiểm tra trước khi gửi
Expo gợi ý chờ e-mail của Apple sau khi gửi. Cách đó được, nhưng mỗi vòng thử lại tốn một build. Đây là bốn bước kiểm tra mình làm trước.
1. Liệt kê những gì dependency khai báo. Từ thư mục gốc project:
find node_modules -name PrivacyInfo.xcprivacy -not -path '*/example/*' \
-exec grep -ho "NSPrivacyAccessedAPICategory[A-Za-z]*" {} + | sort | uniq -c
Trên Solys, ngày 15/10/2026, lệnh này tìm thấy 13 manifest và bốn danh mục: ngày của file, preferences, thời gian kể từ khi khởi động và dung lượng ổ đĩa. Mỗi danh mục trong danh sách phải có mặt trong manifest cuối cùng của app, hoặc trong manifest của framework dùng nó.
2. Tìm trong code native của chính bạn. Module tự viết, plugin, các target như widget: không SDK nào khai báo hộ bạn phần code này.
grep -rlE "UserDefaults|systemUptime|mach_absolute_time|creationDate|modificationDate|volumeAvailableCapacity|activeInputModes" \
--include='*.swift' --include='*.m' --include='*.mm' modules plugins targets
Điều chỉnh thư mục theo project của bạn. Trên Solys, chính lần tìm này làm lộ ra preferences chia sẻ qua App Group, nên mới có mã 1C8F.1.
3. Đọc manifest được tạo ra. Chạy npx expo prebuild -p ios, rồi mở ios/<NomDeTonApp>/PrivacyInfo.xcprivacy. Lưu ý: lệnh này có thể xóa rồi tạo lại thư mục ios. Nếu bạn đã sửa tay trong đó, hãy làm trên một bản sao của project.
4. Kiểm tra build cuối cùng. Đây là bằng chứng duy nhất có giá trị. Tải file IPA từ trang build trên expo.dev, rồi chạy trên Mac:
unzip -q MonApp.ipa -d ipa
find ipa/Payload -name PrivacyInfo.xcprivacy
plutil -p ipa/Payload/*.app/PrivacyInfo.xcprivacy
Dòng thứ hai liệt kê mọi manifest được nhúng, của app và của các framework. Dòng thứ ba hiển thị manifest của app, để so với danh sách ở bước 1.
Manifest phải khớp với trang App Store của bạn
Manifest không đứng một mình. Nếu nó khai báo việc theo dõi hoặc dữ liệu thu thập, trang App Store và chính sách quyền riêng tư của bạn phải nói đúng như vậy. Sự lệch nhau giữa ba thứ này sau đó có thể khiến bạn bị reviewer từ chối theo guideline 5.1.1. Những gì cần đồng bộ được trình bày chi tiết trong chính sách quyền riêng tư cho ứng dụng.
Gửi build mà không gặp bất ngờ
Lỗi ITMS sửa nhanh khi bạn biết cách đọc. Các lần bị từ chối sau đó cần bình tĩnh hơn: cách xử lý có ở trang ứng dụng bị Apple từ chối, còn các lý do thường gặp nhất có trong vì sao Apple từ chối app.
Để kiểm tra hồ sơ trước khi gửi, Apple Review Kit gom checklist của mình và các skill Claude Code đọc lại project của bạn. Nếu việc gửi bị kẹt và bạn muốn có người xem giúp, đặt một giờ với mình: chúng ta cùng xem project của bạn qua video call, ngay trên màn hình của bạn. Còn nếu bạn đang chuẩn bị ra mắt app đầu tiên, làm bài chẩn đoán miễn phí: nó sắp xếp lại các bước theo đúng thứ tự và gửi bạn checklist chống bị Apple từ chối của mình.
Câu hỏi thường gặp
ITMS-91053 có chặn việc gửi build của tôi không?
Apple ghi rằng từ ngày 1/5/2024, App Store Connect không chấp nhận app không mô tả cách dùng các API này. Tuy vậy, một số developer vẫn nhận e-mail trên build được chấp nhận, như một cảnh báo. Dù trường hợp nào, hãy sửa manifest trước build tiếp theo.
Nên chọn mã lý do nào cho UserDefaults?
CA92.1 nếu app chỉ đọc và ghi preferences của riêng nó. Thêm 1C8F.1 nếu app chia sẻ chúng với widget hoặc extension qua App Group. C56D.1 dành riêng cho SDK bên thứ ba: đừng đưa nó vào manifest của app bạn.
Có cần chép manifest của từng SDK vào app.json không?
Không nhất thiết. Nếu tính năng gộp manifest được bật, manifest của các dependency CocoaPods sẽ được gộp lại khi cài pods. Chỉ chép một danh mục khi nó thiếu trong manifest cuối cùng, kèm lý do mô tả đúng cách dùng thực tế.
Widget hoặc extension của tôi có cần manifest riêng không?
Theo Apple, mỗi bundle chứa file thực thi dùng các API này phải có manifest riêng. Extension là một file thực thi riêng, trong bundle riêng: manifest của app không bao gồm nó.
Có sửa được ITMS-91053 mà không cần build lại không?
Không. Privacy manifest là một phần của binary. Sửa cấu hình, build lại, rồi thử nó trên TestFlight trước khi gửi đi review.