App Store Connect APIでスクリーンショットをアップロードする方法
appScreenshotSetを作成し、appScreenshotを予約する(Appleが署名付きのチャンクURLを返します)、バイトをPUTする、そしてuploaded: trueとファイルのMD5で予約をPATCHして確定する、という流れです。認証には.p8キーで署名した20分間有効なJWTを使います。どれも難しくはありませんが、どれも細かく面倒です。ここでは実際の手順を説明します — そして、自分で構築したくない場合の近道も紹介します。Appleのドキュメントは正確ですが、ほとんど読み解けません — フローは十数ページの参照資料に分散していて、まとまった実例が1つもないのです。この記事は、正確なエンドポイントとフィールド名を順序立てて示す唯一の解説記事です。この統合を自分で構築するか、すでに用意されているツールを使うかを検討している人のために書きました。
アップロード先のオブジェクトモデル
スクリーンショットはアプリに直接紐づくわけではありません。オブジェクトの連鎖にぶら下がっていて、上から順にたどる必要があります。
- appStoreVersion — 編集対象の特定バージョン(編集可能な状態である必要があります)。
- appStoreVersionLocalization — App Storeのロケールごとに1つ(en-US、ja、など)。
- appScreenshotSet — ロケール内のディスプレイタイプごとに1つ(6.7インチiPhone、13インチiPadなど)。
- appScreenshot — 個々の画像。1セットにつき最大10枚。
つまり、50言語×iPhoneサイズ2種類×iPadサイズ1種類で完全にローカライズされたリスティングは、50 × 3セットになり、各セットに最大10枚のスクリーンショットが入ります。この掛け算こそが、Webの管理画面をクリックし続ける代わりに自動化を選ぶ理由です。
ステップ0 — 20分間有効なJWT
バイトのアップロード自体を除くすべてのリクエストには、自分で署名するJSON Web Tokenを乗せたAuthorization: Bearerヘッダーが必要です。App Store Connectから必要なのは3つ — issuer ID、key ID、そしてキー作成時に一度だけダウンロードできる.p8秘密鍵ファイルです(Appleは二度と表示しません)。
トークンはES256(P-256曲線上のECDSA + SHA-256)で、その.p8キーを使って署名します。ヘッダーにはkidを、ペイロードにはissuerを表すiss、audienceとして文字列appstoreconnect-v1、そしてiatから20分以内のexpを含めます。それより長い有効期限のトークンはAppleに拒否されるため、頻繁に再生成する必要があります — 1つのトークンを長く保持しようとするより、バッチごとに新しいトークンを発行しましょう。
ES256に対応したJWTライブラリ(jsonwebtoken、PyJWTなど、使っているスタックに合わせて)で署名し、.p8の中身をキーとして渡し、その結果を以下すべての呼び出しのAuthorizationヘッダーに設定します。
ステップ1 — スクリーンショットセットを作成する
セットは1つのロケールと1つのディスプレイタイプに紐づきます。/v1/appScreenshotSetsにPOSTし、attributesにscreenshotDisplayTypeを、relationshipに埋めたいappStoreVersionLocalizationを指定します。
よく使うディスプレイタイプは、APP_IPHONE_67(6.7インチ、1290 × 2796)— 現行の大型iPhoneサイズ — とAPP_IPAD_PRO_3GEN_129(12.9インチ、2048 × 2732)です。Appleが今対応してくれている便利な仕様として、最大サイズのiPhone用スクリーンショットは小さいiPhoneクラスにも流用され、最大サイズのiPadも同様に小さいiPadに流用されます — つまり実務上、物理デバイスごとに1セット用意する必要はなく、ロケールごとにiPhone用1セットとiPad用1セットで済むことが多いです。レスポンスにはセットのidが含まれ、次の呼び出しに使います。すでにそのロケール/タイプ用のセットが存在する場合は、重複作成せず再利用してください。
ステップ2 — スクリーンショットを予約する
ファイルを一気にアップロードするわけではありません。まず予約します — ファイル名と正確なバイトサイズをAppleに伝えると、アップロードプランが返ってきます。/v1/appScreenshotsにPOSTし、fileNameとfileSizeのattributes、そしてステップ1のセットへのrelationshipを指定します。
興味深いのはレスポンスの中身です。新しいスクリーンショットのattributes内にuploadOperationsがあります — バイトの送り方を正確に示す配列です。小さいファイルなら1つの操作ですが、大きいファイルの場合Appleは複数に分割します。各エントリにはmethod(PUT)、署名付きのurl、送信すべきバイトのoffsetとlength、そして付与すべきrequestHeadersが含まれます。
ステップ3 — バイトをPUTする
各操作について、ファイルのoffsetからlengthバイト分を読み取り、Appleが指定したrequestHeadersを正確に付けて、指定されたurlにPUTします。これらのURLは署名済みなので、ここでJWTを送ってはいけません — Authorizationヘッダーを追加すると署名付きリクエストが壊れることがあります。複数の操作は並列で実行できますが、Appleは積極的すぎるアップロードをレート制限するため、一斉に発行するのではなくバックオフ付きリトライでラップしましょう。
ステップ4 — uploaded: trueとMD5でコミットする
バイトをアップロードしただけでは何も起きません。予約が完了したことをAppleに伝える必要があります。スクリーンショットに対してuploaded: trueとsourceFileChecksum(ファイル全体のMD5を小文字16進文字列にしたもの)でPATCHします。これはAppleの整合性チェックです — チェックサムが実際に届いたものと一致しなければ、処理が失敗します。
PATCH後、アセットはApple側で処理に入ります。スクリーンショットのassetDeliveryStateが完了状態になるまでポーリングし、失敗した場合はerrorsを確認してください — サイズ不一致やアルファチャンネルの問題はここで表面化します。PUTとPATCHの両方が成功しても、処理中に数分後にスクリーンショットが却下されることがあります。コミットで2xxが返ったからといって完了したと思い込まず、delivery stateを監視してください。
ステップ5 — 表示順を設定する
スクリーンショットは作成した順番で返ってきますが、それが表示したい順番とは限りません。セットの順序は別のrelationshipです。/v1/appScreenshotSets/{id}/relationships/appScreenshotsにスクリーンショットIDの配列でPATCHします — 配列の順序そのものがストア上の表示順になります。
Appleが実際に課している制約
- PNGまたはJPEGのみ。 HEICやWebPは不可。レンダラーがそれ以外を出力するなら先に変換してください。
- アルファチャンネル不可。 透明部分は単色背景にフラット化し、RGBでエクスポートします。アルファチャンネルの混入は、処理時に静かに却下される最も多い原因の1つです。
- ディスプレイタイプごとの正確な寸法。 画像はディスプレイタイプのピクセルサイズに正確に一致させる必要があります — 数ピクセルのズレも拡大もNGです。1290 × 2796のファイルは
APP_IPHONE_67セット専用で、それ以外には使えません。 - 1セットにつき最大10枚。 ロケール×ディスプレイタイプごとに最大10枚のスクリーンショット。
- 編集可能なバージョンのみ。 セットを変更できるのは編集可能な状態のバージョンだけです。すでに審査中のバージョンはロックされています。
見た目より手間がかかる理由
個々の呼び出し自体はシンプルです。コストはその周辺すべてにあります — 20分トークンの署名とローテーション、MD5の計算、uploadOperationsに合わせたファイルの分割、部分アップロードの失敗時のリトライ処理、delivery stateのポーリング、自分のロケールをAppleのロケールコードにマッピングすること、そしてきちんとローカライズされたリスティングのためにこれら全部を150回以上繰り返すことです。信頼できる仕組みにするには、認証・チャンクアップロード・エラーハンドリングだけで週末が丸ごとかかります — しかもAppleがディスプレイタイプを追加するたびに保守が発生します。
自分で書かずにこの一連の処理を済ませたいなら、fastlaneのdeliverが同じAPIをラップしていて、標準的なオープンソースの選択肢です。アップロードは解決してくれますが、スクリーンショットのデザインやキャプションの作成まではしてくれません。
……あるいは全部スキップする
このAPIは完成した画像を動かすだけです。カルーセルをデザインし、キャプションを書き、それをロケールごとに翻訳する作業は別途必要で — 実際に時間がかかるのはここです。Mokbiは今日それを実現しています。スクリーンショットをデザインし、リスティング文案を作成し、そのすべてを50言語に翻訳したうえで、このAPIが要求するディスプレイタイプごとの正確な寸法で全画像をエクスポートします — PNG、RGB、アルファなし、正確なサイズ — アップロードがdelivery stateの段階で弾かれることはありません。
そして公開まで行います。App Storeについては、Mokbiが裏側でこのreserve-and-commitの手順をそのまま実行し — スクリーンショットとメタデータをアップロードして、あなたが提出できる状態までバージョンを準備します。JWTやチャンク単位のPUTに触れる必要は一切ありません。最終的なSubmitとその審査はAppleが依然として要求しますが、そこに至るまではすべて代行されます。Google Playについては、そのままストアに直接反映されます。いずれの場合も、コードを書くことなくAPIと同じ結果が得られ、デザイン・文案・50言語の翻訳もすでに完了した状態で手に入ります。