App Store Connect API で iOS リリースを自動化する:版作成から『承認済みビルドの差し替え』の壁まで
.p8 鍵から ES256 の JWT を自作すれば、新バージョン作成・ビルド紐付け・リリースノート記入まで CLI で自動化できる。ただし API には越えられない壁が一つある——『リリース待ち(Pending Developer Release)』のロックだ。
iOS の提出は、xcodebuild archive → altool でアップロードするところまでは CLI で完結する。だがその先——新バージョンの作成、ビルドの紐付け、リリースノートの記入——を App Store Connect(以下 ASC)の Web 画面で手作業している人は多い。ここは App Store Connect API でほぼ全部自動化できる。ただし最後に一つだけ、API では越えられない壁がある。
1. ASC API は .p8 鍵 + ES256 の JWT で叩ける
事象
ASC API を使いたいが、認証が OAuth ではなく独自の JWT で、ライブラリを入れないと叩けないと思い込みがちだ。
解決策
専用ライブラリは要らない。発行した API キー(.p8 ファイル)から ES256 の JWT を Node 標準の crypto だけで自作できる。
ポイントは署名の出力形式だ。JWT が要求するのは r‖s を連結した固定長(IEEE P1363)形式で、crypto の既定(DER)ではない。dsaEncoding: 'ieee-p1363' を指定する。
import crypto from "node:crypto";
const b64url = (buf) =>
Buffer.from(buf).toString("base64")
.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
function makeToken({ keyId, issuerId, privateKey, now }) {
const header = { alg: "ES256", kid: keyId, typ: "JWT" };
const payload = {
iss: issuerId,
iat: now,
exp: now + 60 * 15, // 20 分以内(超えると 401)
aud: "appstoreconnect-v1",
};
const signingInput =
`${b64url(JSON.stringify(header))}.${b64url(JSON.stringify(payload))}`;
const sig = crypto.sign(
"sha256",
Buffer.from(signingInput),
{ key: privateKey, dsaEncoding: "ieee-p1363" } // ← DER だと ASC が弾く
);
return `${signingInput}.${b64url(sig)}`;
}
あとは Authorization: Bearer <token> を付けて https://api.appstoreconnect.apple.com を叩くだけだ。exp は発行から最大 20 分。長めに振ると 401 になるので、ここでは 15 分にしている。
2. リリース準備を API で組み立てる
事象
ビルドのアップロードは済んだ。ここから先(版を作る → ビルドを紐付ける → リリースノートを書く)を Web 画面で毎回手でやっている。
解決策
3 回のリクエストで組み立てられる。順番が決まっている。
(1) 新しいバージョンを作る — POST /v1/appStoreVersions
POST /v1/appStoreVersions
{
"data": {
"type": "appStoreVersions",
"attributes": { "platform": "IOS", "versionString": "1.0.1" },
"relationships": {
"app": { "data": { "type": "apps", "id": "<APP_ID>" } }
}
}
}
(2) アップロード済みビルドを紐付ける — PATCH /v1/appStoreVersions/{versionId}/relationships/build
PATCH /v1/appStoreVersions/{versionId}/relationships/build
{ "data": { "type": "builds", "id": "<BUILD_ID>" } }
紐付けたいビルドの ID は GET /v1/builds?filter[app]=<APP_ID>&sort=-uploadedDate で取れる。processingState が VALID になってから紐付ける。
(3) リリースノート(whatsNew)を書く — PATCH /v1/appStoreVersionLocalizations/{locId}
PATCH /v1/appStoreVersionLocalizations/{locId}
{
"data": {
"type": "appStoreVersionLocalizations",
"id": "<LOC_ID>",
"attributes": { "whatsNew": "軽微な不具合を修正しました。" }
}
}
ローカライゼーションの ID は版から辿る(GET /v1/appStoreVersions/{versionId}/appStoreVersionLocalizations)。言語ごとに 1 レコードあるので、対象言語の ID に対して PATCH する。
ここまでで、残る手作業は輸出コンプライアンス等の申告と「提出」ボタンだけになる。
補足:版を作る前に必ず存在確認する
POST /v1/appStoreVersions は、同じバージョン番号の版が既にあると衝突する。スクリプトを二度走らせたとき、あるいは前回の版が中途半端に残っているときに事故りやすい。作る前に GET /v1/appStoreVersions?filter[app]=<APP_ID> で既存の版と状態を確認してから作るほうが安全だ。
なお、公開済みアプリの更新では versionString(マーケティングバージョン)自体を上げないとビルドが弾かれる。これは別記事「アップロードしたのに配信されない・弾かれる iOS ビルドの3つの原因」で扱った。
3. API が越えられない壁:「リリース待ち」のロック
ここからが本題だ。自動化を組んでいくと、API ではどうやっても動かせない状態に突き当たる。
事象
審査を通したバージョンを「手動でリリース」設定にしておくと、状態が PENDING_DEVELOPER_RELEASE(デベロッパによるリリース待ち) になる。この状態で「やっぱり別のビルドに載せ替えたい」と思っても、API は全方向で拒否してくる。
- 新しい版を作ろうとする →
409(枠が埋まっている) - ビルドを差し替えようとする(
PATCH .../relationships/build)→409(状態が不正) - 版を削除しようとする → 削除できるのは最初の版だけ。公開済みバージョンがある以上、消せない
API のどの入口も塞がっていて、八方塞がりに見える。
原因
PENDING_DEVELOPER_RELEASE は「審査に通った確定版」で、リリースの引き金を引く一歩手前の状態だ。確定版なので、API からの編集・差し替え・削除を一切受け付けない。新しい版を作る枠も、この版が占有している。
解決策
この状態を戻す手段は API には無い。ASC の Web 画面でしか操作できない。
版のページ上部に出る 「Cancel This Release(このリリースをキャンセル)」 を押す。すると状態が DEVELOPER_REJECTED(編集可能) に戻る。ここまで来れば、API でも Web でもビルドを差し替えて再提出できる。
PENDING_DEVELOPER_RELEASE ← API では一切動かせない(409 / 削除不可)
│ 「Cancel This Release」(Web のみ)
▼
DEVELOPER_REJECTED ← 編集可能。ビルド差し替え → 再提出
「キャンセル」という言葉が強く見えるが、アプリが取り下げられて公開停止になるわけではない。すでに公開中のバージョンはそのまま生きていて、この操作で戻るのは「リリース待ちだった新バージョンの承認」だけだ。承認が取り消されて編集可能な状態に戻る、それだけだ。
補足
- ASC API のリクエストはどれも
.p8鍵さえあれば叩ける。processingStateの確認(GET /v1/builds)にも同じ JWT を使い回せるので、一度署名関数を組んでおくと Web 画面を見に行く回数が減る - 自動化できる範囲とできない範囲の線引きを覚えておくと早い。版の作成・紐付け・メタデータの記入までは API、確定版(リリース待ち)の取り消しだけは Web。この一線だけは API に手段が無い
※ 本記事にはアフィリエイトリンクが含まれます。