Tải ảnh chụp màn hình App Store lên bằng App Store Connect API
appScreenshotSet cho đúng ngôn ngữ và thiết bị, đặt trước một appScreenshot (Apple sẽ gửi cho bạn các URL đoạn đã ký sẵn), PUT các byte dữ liệu, rồi PATCH việc đặt trước đó với uploaded: true cùng mã MD5 của tệp. Xác thực là một JWT có hiệu lực 20 phút, được ký bằng khóa .p8 của bạn. Không có bước nào khó cả, nhưng tất cả đều lắt léo. Đây là chuỗi thao tác thực tế — và cả lối tắt nếu bạn không muốn tự xây dựng nó.Tài liệu của Apple về việc này chính xác nhưng gần như không thể đọc nổi — quy trình bị dàn trải trên cả chục trang tham chiếu mà không có lấy một ví dụ hoàn chỉnh nào. Bài viết này là bản hướng dẫn duy nhất, theo đúng thứ tự, với các endpoint và tên trường chính xác. Nó được viết cho người đang phân vân giữa việc tự xây dựng tích hợp này hay dùng một công cụ đã có sẵn.
Mô hình đối tượng bạn đang tải lên
Ảnh chụp màn hình không gắn trực tiếp vào app của bạn. Chúng nằm trong một chuỗi đối tượng, và bạn phải đi từ trên xuống:
- appStoreVersion — phiên bản cụ thể bạn đang chỉnh sửa (phải ở trạng thái có thể chỉnh sửa).
- appStoreVersionLocalization — một cho mỗi ngôn ngữ trên App Store (en-US, de-DE, ja, v.v.).
- appScreenshotSet — một cho mỗi loại hiển thị bên trong một bản địa hóa (iPhone 6.7", iPad 13", v.v.).
- appScreenshot — từng ảnh riêng lẻ, tối đa 10 ảnh mỗi bộ.
Vậy một danh sách được bản địa hóa đầy đủ với 50 ngôn ngữ, hai kích cỡ iPhone và một kích cỡ iPad là 50 × 3 bộ, mỗi bộ chứa tối đa 10 ảnh chụp màn hình. Phép nhân đó là lý do chính khiến người ta tự động hóa việc này thay vì bấm thủ công qua giao diện web.
Bước 0 — JWT có hiệu lực 20 phút
Mọi yêu cầu, trừ chính việc tải byte dữ liệu, đều cần một tiêu đề Authorization: Bearer mang theo một JSON Web Token do bạn tự ký. Bạn cần ba thứ từ App Store Connect: issuer ID, key ID, và tệp khóa riêng .p8 mà bạn tải về một lần khi tạo khóa (Apple không bao giờ hiển thị lại nó).
Token được ký bằng ES256 — ECDSA trên đường cong P-256 với SHA-256 — dùng khóa .p8 đó. Phần header mang kid; phần payload mang issuer dưới dạng iss, audience là chuỗi ký tự cố định appstoreconnect-v1, và exp không quá 20 phút sau iat. Apple từ chối bất kỳ token nào có thời hạn dài hơn, nên bạn phải tạo lại thường xuyên — hãy tạo một token mới cho mỗi đợt tải lên thay vì cố giữ một token mở.
Ký nó bằng bất kỳ thư viện JWT nào hỗ trợ ES256 (jsonwebtoken, PyJWT, hay bất cứ thứ gì stack của bạn dùng), đưa nội dung .p8 làm khóa, rồi đặt kết quả vào header Authorization cho mọi lệnh gọi bên dưới.
Bước 1 — tạo bộ ảnh chụp màn hình
Một bộ chỉ thuộc về một bản địa hóa và một loại hiển thị. Bạn gửi POST tới /v1/appScreenshotSets với screenshotDisplayType trong attributes và một quan hệ trỏ tới appStoreVersionLocalization bạn muốn điền.
Các loại hiển thị phổ biến: APP_IPHONE_67 (6.7", 1290 × 2796), kích cỡ iPhone lớn hiện tại, và APP_IPAD_PRO_3GEN_129 (12.9", 2048 × 2732). Một điều hữu ích Apple hiện làm: ảnh chụp màn hình cho cỡ iPhone lớn nhất được dùng lại cho các dòng iPhone nhỏ hơn, và cỡ iPad lớn nhất cho các iPad nhỏ hơn — nên thực tế bạn thường chỉ cần một bộ iPhone và một bộ iPad cho mỗi ngôn ngữ, chứ không phải một bộ cho từng thiết bị vật lý. Phản hồi trả về cho bạn một id của bộ, dùng cho lệnh gọi tiếp theo. Nếu một bộ đã tồn tại cho ngôn ngữ/loại đó, hãy tái sử dụng thay vì tạo trùng lặp.
Bước 2 — đặt trước ảnh chụp màn hình
Bạn không tải tệp lên trong một lần. Trước tiên bạn đặt trước nó: cho Apple biết tên tệp và kích thước byte chính xác, và nó sẽ trả về một kế hoạch tải lên. Gửi POST tới /v1/appScreenshots với các thuộc tính fileName và fileSize, cùng một quan hệ trỏ tới bộ ở bước 1.
Phản hồi mới là phần thú vị. Trong thuộc tính của ảnh chụp màn hình mới, bạn nhận được uploadOperations — một mảng mô tả chính xác cách đẩy dữ liệu byte lên. Với tệp nhỏ đó là một thao tác duy nhất; với tệp lớn Apple chia thành nhiều thao tác. Mỗi mục cho bạn một method (PUT), một url đã ký sẵn, offset và length byte cần gửi, cùng requestHeaders cần đính kèm.
Bước 3 — PUT dữ liệu byte
Với mỗi thao tác, hãy đọc đoạn tệp của bạn từ offset cho length byte, và PUT nó tới url được cung cấp với đúng các requestHeaders mà Apple đã gửi. Các URL này đã được ký sẵn, nên bạn không gửi JWT của mình ở đây — thêm header Authorization thực ra có thể phá vỡ yêu cầu đã ký. Nhiều thao tác có thể chạy song song, nhưng Apple giới hạn tốc độ với các đợt tải lên dồn dập, vì vậy hãy bọc chúng trong cơ chế thử lại có độ trễ tăng dần thay vì gửi tất cả cùng lúc.
Bước 4 — commit với uploaded: true và mã MD5
Việc tải byte dữ liệu lên không có tác dụng gì cho đến khi bạn báo cho Apple biết việc đặt trước đã hoàn tất. PATCH lại ảnh chụp màn hình với uploaded: true và một sourceFileChecksum — mã MD5 của toàn bộ tệp dưới dạng chuỗi hex chữ thường. Đây là bước kiểm tra tính toàn vẹn của Apple: nếu checksum không khớp với những gì đã đến, quá trình xử lý sẽ thất bại.
Sau khi PATCH, tài nguyên bước vào quá trình xử lý phía Apple. Hãy thăm dò assetDeliveryState của ảnh chụp màn hình cho đến khi đạt trạng thái hoàn tất — và đọc errors nếu thất bại, vì đó là nơi các vấn đề về kích thước sai và kênh alpha xuất hiện. PUT và PATCH đều có thể thành công nhưng ảnh chụp màn hình vẫn có thể bị từ chối vài phút sau trong lúc xử lý. Đừng cho rằng mã 2xx ở bước commit nghĩa là bạn đã xong; hãy theo dõi trạng thái phân phối.
Bước 5 — đặt thứ tự hiển thị
Ảnh chụp màn hình trả về theo thứ tự chúng được tạo, hiếm khi là thứ tự bạn muốn hiển thị. Thứ tự của bộ là một quan hệ riêng biệt. PATCH /v1/appScreenshotSets/{id}/relationships/appScreenshots với một mảng ID ảnh chụp màn hình — thứ tự trong mảng chính là thứ tự hiển thị trên store.
Các ràng buộc mà Apple thực sự áp dụng
- Chỉ PNG hoặc JPEG. Không HEIC, không WebP. Nếu công cụ render của bạn xuất ra định dạng khác, hãy chuyển đổi trước.
- Không có kênh alpha. Làm phẳng độ trong suốt thành nền đặc và xuất RGB. Một kênh alpha bị sót là một trong những nguyên nhân bị từ chối âm thầm phổ biến nhất khi xử lý.
- Kích thước chính xác theo từng loại hiển thị. Ảnh phải khớp chính xác với kích thước pixel của loại hiển thị — không sai lệch dù chỉ vài pixel, không được phóng to. Một tệp 1290 × 2796 chỉ đi vào bộ
APP_IPHONE_67và không nơi nào khác. - Tối đa 10 ảnh mỗi bộ. Tối đa mười ảnh chụp màn hình cho mỗi ngôn ngữ, mỗi loại hiển thị.
- Chỉ trên phiên bản có thể chỉnh sửa. Bạn chỉ có thể thay đổi các bộ trên một phiên bản đang ở trạng thái có thể chỉnh sửa; phiên bản đã ở trạng thái đang xét duyệt bị khóa.
Vì sao việc này tốn công hơn vẻ ngoài của nó
Mỗi lệnh gọi riêng lẻ đều đơn giản. Chi phí nằm ở mọi thứ xung quanh: ký và làm mới một token 20 phút, tính MD5, cắt tệp cho khớp với uploadOperations, xử lý việc thử lại khi tải dở dang, thăm dò trạng thái phân phối, ánh xạ ngôn ngữ của bạn sang mã ngôn ngữ của Apple, và làm tất cả những điều đó hơn 150 lần cho một danh sách được bản địa hóa đầy đủ. Làm cho nó ổn định là công sức của cả một cuối tuần với xác thực, tải lên theo đoạn, và xử lý lỗi — rồi sau đó bạn phải duy trì nó mỗi khi Apple thêm một loại hiển thị mới.
Nếu bạn muốn có quy trình này mà không cần tự viết, công cụ deliver của Fastlane đóng gói cùng API này và là lựa chọn mã nguồn mở tiêu chuẩn. Nó giải quyết phần tải lên; nó không thiết kế ảnh chụp màn hình hay viết chú thích.
…hoặc bỏ qua tất cả
API chỉ di chuyển các ảnh đã hoàn thiện. Vẫn cần thứ gì đó thiết kế carousel, viết chú thích, và dịch chúng theo từng ngôn ngữ — đó mới là phần thực sự tốn thời gian. Mokbi làm được điều đó ngay hôm nay: nó thiết kế ảnh chụp màn hình, soạn nội dung listing, và dịch toàn bộ sang 50 ngôn ngữ, sau đó xuất từng ảnh đúng kích thước theo từng loại hiển thị mà API này yêu cầu — PNG, RGB, không alpha, đúng kích cỡ — để việc tải lên không bị bật lại ở bước kiểm tra trạng thái phân phối.
Sau đó nó xuất bản. Với App Store, Mokbi chạy chính xác chuỗi reserve-and-commit này ở phía sau — tải lên ảnh chụp màn hình và metadata của bạn, chuẩn bị sẵn phiên bản để bạn gửi duyệt, nên bạn không bao giờ phải đụng đến JWT hay PUT theo đoạn. Apple vẫn yêu cầu bước Submit cuối cùng và quá trình xét duyệt của họ; mọi thứ trước đó đều đã được xử lý sẵn cho bạn. Với Google Play, nó đẩy thẳng lên store. Dù theo cách nào, bạn cũng có được kết quả của API mà không cần viết code, với thiết kế, nội dung, và bản dịch 50 ngôn ngữ đã hoàn tất sẵn.