· Developers · 6분 분량

App Store Connect API로 스크린샷 업로드하기

App Store Connect API로 스크린샷 업로드하기
TL;DR. App Store Connect API로 스크린샷 하나를 업로드하려면 이미지당 네 단계를 거쳐야 해요. 원하는 로케일과 기기용 appScreenshotSet을 만들고, appScreenshot을 예약(Apple이 사전 서명된 청크 URL을 내려줘요)하고, 바이트를 PUT하고, 마지막으로 uploaded: true와 파일의 MD5로 예약을 PATCH하는 순서예요. 인증은 .p8 키로 서명한 20분짜리 JWT를 사용해요. 어려운 건 없지만 손이 많이 가요. 실제 순서와, 직접 만들고 싶지 않을 때의 지름길을 소개할게요.

Apple 문서는 정확하지만 거의 읽기가 힘들어요 — 흐름이 여러 참고 페이지에 흩어져 있고 하나로 정리된 예제가 없거든요. 이 글은 정확한 엔드포인트와 필드 이름을 순서대로 정리한 하나의 안내서예요. 직접 연동을 만들지, 이미 만들어진 도구를 쓸지 고민하는 사람을 위한 글이에요.

업로드 대상이 되는 객체 모델

스크린샷은 앱에 바로 붙지 않아요. 여러 객체가 사슬처럼 연결되어 있고, 위에서부터 순서대로 따라가야 해요.

  • appStoreVersion — 지금 편집 중인 특정 버전(반드시 편집 가능한 상태여야 해요).
  • appStoreVersionLocalization — App Store 로케일마다 하나씩(en-US, de-DE, ja 등).
  • appScreenshotSet — 로케일 안에서 디스플레이 유형마다 하나씩(6.7인치 iPhone, 13인치 iPad 등).
  • appScreenshot — 개별 이미지, 세트당 최대 10개.

그러니 50개 언어로, iPhone 두 사이즈와 iPad 한 사이즈에 완전히 현지화된 목록을 만들면 50 × 3세트, 각 세트마다 최대 10개의 스크린샷이 들어가요. 이 곱셈이야말로 사람들이 웹 UI를 일일이 클릭하는 대신 자동화하는 이유예요.

0단계 — 20분짜리 JWT

바이트 업로드 자체를 빼면 모든 요청에는 직접 서명한 JSON Web Token을 담은 Authorization: Bearer 헤더가 필요해요. App Store Connect에서 세 가지가 필요해요. 발급자 ID, 키 ID, 그리고 키를 만들 때 한 번만 다운로드하는 .p8 개인 키 파일이에요(Apple은 이후 다시 보여주지 않아요).

토큰은 그 .p8 키로 ES256(P-256 곡선 위의 ECDSA와 SHA-256)을 사용해 서명해요. 헤더에는 kid가 들어가고, 페이로드에는 발급자를 나타내는 iss, 대상을 나타내는 리터럴 문자열 appstoreconnect-v1, 그리고 iat로부터 20분을 넘지 않는 exp가 들어가요. Apple은 더 긴 수명은 거부하니 자주 재발급해야 해요 — 하나를 계속 붙잡고 있기보다는 배치마다 새 토큰을 발급하는 게 나아요.

ES256을 지원하는 아무 JWT 라이브러리(jsonwebtoken, PyJWT 등 스택에 맞는 걸로)로 서명하고, .p8 내용을 키로 넘긴 뒤 결과를 아래 모든 호출의 Authorization 헤더에 넣으면 돼요.

1단계 — 스크린샷 세트 만들기

세트는 하나의 로케일과 하나의 디스플레이 유형에 한정돼요. attributes에 screenshotDisplayType을 넣고 채우려는 appStoreVersionLocalization을 가리키는 관계와 함께 /v1/appScreenshotSets로 POST하면 돼요.

자주 쓰는 디스플레이 유형으로는 현재의 큰 iPhone 사이즈인 APP_IPHONE_67(6.7인치, 1290 × 2796)과 APP_IPAD_PRO_3GEN_129(12.9인치, 2048 × 2732)가 있어요. Apple이 이제 제공하는 유용한 기능 하나는, 가장 큰 iPhone 사이즈의 스크린샷이 더 작은 iPhone 등급에도 재사용되고, 가장 큰 iPad 사이즈가 더 작은 iPad에도 재사용된다는 점이에요 — 그래서 실제로는 물리 기기마다가 아니라 로케일당 iPhone 세트 하나, iPad 세트 하나만 필요할 때가 많아요. 응답으로 다음 호출에 사용할 세트 id를 받아요. 해당 로케일/유형에 이미 세트가 있다면 새로 만들지 말고 재사용하세요.

2단계 — 스크린샷 예약하기

파일을 한 번에 업로드하지 않아요. 먼저 예약부터 해요. 파일 이름과 정확한 바이트 크기를 Apple에 알려주면 업로드 계획을 돌려줘요. attributes에 fileNamefileSize를 넣고 1단계의 세트를 가리키는 관계와 함께 /v1/appScreenshots로 POST하면 돼요.

흥미로운 부분은 응답이에요. 새 스크린샷의 attributes 안에 바이트를 어떻게 보내야 하는지 정확히 설명하는 배열인 uploadOperations가 들어 있어요. 작은 파일이면 작업이 하나, 큰 파일이면 Apple이 여러 개로 나눠줘요. 각 항목에는 method(PUT), 사전 서명된 url, 보내야 할 바이트 offsetlength, 그리고 함께 붙여야 할 requestHeaders가 담겨 있어요.

3단계 — 바이트를 PUT하기

각 작업마다 파일에서 offset부터 length 바이트만큼 잘라 읽어서, Apple이 준 requestHeaders를 그대로 붙여 주어진 url에 PUT하면 돼요. 이 URL은 사전 서명되어 있으니 여기서는 JWT를 보내면 안 돼요Authorization 헤더를 추가하면 서명된 요청이 오히려 깨질 수 있어요. 여러 작업을 병렬로 실행할 수 있지만 Apple이 공격적인 업로드에 속도 제한을 걸기 때문에, 한꺼번에 전부 쏘기보다는 재시도-백오프 로직으로 감싸는 게 좋아요.

4단계 — uploaded: true와 MD5로 확정하기

바이트를 업로드하는 것만으로는 아무 일도 일어나지 않아요. 예약이 끝났다고 Apple에 알려줘야 해요. uploaded: truesourceFileChecksum(전체 파일의 MD5를 소문자 16진수 문자열로)을 담아 스크린샷을 PATCH하면 돼요. 이건 Apple의 무결성 검사예요. 체크섬이 실제 도착한 것과 일치하지 않으면 처리가 실패해요.

PATCH 후 자산은 Apple 쪽에서 처리에 들어가요. 스크린샷의 assetDeliveryState가 완료 상태에 도달할 때까지 폴링하고, 실패하면 errors를 확인하세요. 잘못된 크기나 알파 채널 문제는 바로 여기서 드러나요. PUT과 PATCH가 둘 다 성공해도 처리 도중 몇 분 뒤에 스크린샷이 거부될 수 있어요. 커밋에서 2xx가 나왔다고 끝났다고 단정하지 말고 전달 상태를 지켜보세요.

5단계 — 표시 순서 정하기

스크린샷은 만들어진 순서대로 돌아오는데, 이건 보여주고 싶은 순서와 다를 때가 많아요. 세트의 순서는 별도의 관계예요. 스크린샷 ID 배열과 함께 /v1/appScreenshotSets/{id}/relationships/appScreenshots를 PATCH하면 돼요 — 배열 순서가 스토어에 표시되는 순서예요.

Apple이 실제로 강제하는 제약 조건

  • PNG 또는 JPEG만 허용. HEIC, WebP는 안 돼요. 렌더러가 다른 형식을 내놓는다면 먼저 변환하세요.
  • 알파 채널 없음. 투명도를 단색 배경으로 평탄화하고 RGB로 내보내세요. 알파 채널이 남아 있는 건 처리 시 가장 흔한 조용한 거부 사유 중 하나예요.
  • 디스플레이 유형별 정확한 크기. 이미지는 디스플레이 유형의 픽셀 크기와 정확히 일치해야 해요 — 약간의 오차도, 업스케일도 안 돼요. 1290 × 2796 파일은 APP_IPHONE_67 세트에만 들어가고 다른 곳에는 못 들어가요.
  • 세트당 최대 10개. 로케일·디스플레이 유형별로 스크린샷은 최대 10장이에요.
  • 편집 가능한 버전만. 세트는 편집 가능한 상태의 버전에서만 수정할 수 있어요. 이미 심사 중인 버전은 잠겨 있어요.

생각보다 손이 많이 가는 이유

개별 호출은 각각 단순해요. 비용은 그 주변 모든 것에서 나와요. 20분짜리 토큰을 서명하고 갱신하는 것, MD5를 계산하는 것, 파일을 uploadOperations에 맞게 자르는 것, 부분 업로드 재시도를 처리하는 것, 전달 상태를 폴링하는 것, 로케일을 Apple의 로케일 코드에 매핑하는 것, 그리고 제대로 현지화된 목록을 위해 이 모든 걸 150번 넘게 반복하는 것까지요. 안정적으로 돌아가게 만드는 데 주말 하나를 인증, 청크 업로드, 오류 처리에 쏟아야 하고, 그 뒤로도 Apple이 디스플레이 유형을 추가할 때마다 계속 유지보수해야 해요.

직접 만들지 않고 이 순서를 그대로 쓰고 싶다면, Fastlane의 deliver가 같은 API를 감싸주는 표준 오픈소스 방법이에요. 업로드는 해결해 주지만 스크린샷을 디자인하거나 캡션을 써주지는 않아요.

…아니면 이 전체를 건너뛰기

API는 완성된 이미지를 옮기기만 해요. 캐러셀을 디자인하고, 캡션을 쓰고, 로케일별로 번역하는 건 여전히 누군가 해야 하는 일이고, 사실 이게 진짜 시간이 드는 부분이에요. Mokbi는 지금 이 일을 대신해줘요. 스크린샷을 디자인하고, 스토어 목록 문구 초안을 작성하고, 전체를 50개 언어로 번역한 다음, 이 API가 요구하는 디스플레이 유형별 정확한 크기(PNG, RGB, 알파 없음, 정확한 사이즈)로 모든 이미지를 내보내요 — 그래서 전달 상태 단계에서 업로드가 튕겨나가지 않아요.

그다음 발행까지 해줘요. App Store의 경우, Mokbi는 내부적으로 바로 이 예약-확정 시퀀스를 실행해서 스크린샷과 메타데이터를 업로드하고 버전을 제출 준비 상태로 만들어 놓기 때문에, JWT나 청크 PUT을 직접 다룰 일이 없어요. 최종 제출(Submit)과 심사는 여전히 Apple이 요구하지만, 그 전까지의 모든 과정은 대신 처리돼요. Google Play의 경우엔 곧바로 스토어에 게시해요. 어느 쪽이든 코드를 직접 짜지 않고도 API의 결과를 얻는 거고, 거기에 디자인·문구·50개 언어 번역까지 이미 끝난 상태예요.

다음으로 읽으면 좋은 글

에디터 열기 →