TL;DR
iOSアプリのアップデート版を ビルド → アップロード → ビルド紐付け → whatsNew設定 → 審査提出 まで、すべてコマンドラインとApp Store Connect REST APIで完結させた。初回リリース時と異なり、メタデータやスクリーンショットは既存のものが引き継がれるため、更新時に必要な操作は少ない。
前提: App Store Connect APIだけでiOSアプリを審査提出する完全ガイドのセットアップ(APIキー取得・JWT生成・ヘルパー関数)が完了しているものとする。
全体の流れ
- ビルド番号のインクリメント
- アーカイブ・IPA書き出し・アップロード(
xcodebuild+xcrun altool) - ビルドの処理完了を確認(API)
- ビルドをバージョンに紐付け(API)
- 暗号化コンプライアンスの設定(API)
- whatsNew(新機能)の設定(API)
- 審査提出(API)
1. ビルド番号のインクリメント
App Store Connectは同じビルド番号のアップロードを拒否する。CURRENT_PROJECT_VERSION を上げる必要がある。
XcodeGenを使っている場合は project.yml を編集する:
# project.yml
settings:
base:
MARKETING_VERSION: "1.1.0"
CURRENT_PROJECT_VERSION: "4" # 3 → 4 に変更
ポイント: マーケティングバージョン(
1.1.0)はユーザーに見えるバージョン番号、ビルド番号(4)は同一バージョン内の連番。不具合修正の再提出など、ユーザーから見た変更がない場合はビルド番号だけ上げればよい。
2. アーカイブ・アップロード
# 環境変数を設定
export APP_STORE_API_KEY="YOUR_KEY_ID"
export APP_STORE_API_ISSUER="YOUR_ISSUER_ID"
# XcodeGenでプロジェクト再生成
xcodegen generate
# アーカイブ
xcodebuild archive \
-project KotenOCR.xcodeproj \
-scheme KotenOCR \
-archivePath build/KotenOCR.xcarchive \
-destination "generic/platform=iOS" \
-quiet
# IPA書き出し
xcodebuild -exportArchive \
-archivePath build/KotenOCR.xcarchive \
-exportPath build/export \
-exportOptionsPlist scripts/ExportOptions.plist \
-quiet
# App Store Connectへアップロード
xcrun altool --upload-app \
--type ios \
--file build/export/KotenOCR.ipa \
--apiKey "$APP_STORE_API_KEY" \
--apiIssuer "$APP_STORE_API_ISSUER"
注意:
xcrun altoolに渡す環境変数はexportしておく必要がある。source .envだけではサブプロセスに渡らない。
アップロード成功時の出力例:
UPLOAD SUCCEEDED with no errors
Delivery UUID: cdf01bf8-...
Transferred 80033738 bytes in 7.487 seconds (10.7MB/s)
3. ビルドの処理完了を確認
アップロード後、App Store Connectがビルドを処理するまで数分かかる。processingState が VALID になるまで待つ。
result = api_request("GET",
f"builds?filter[app]={APP_ID}&filter[version]=4&sort=-uploadedDate&limit=1")
build = result["data"][0]
state = build["attributes"]["processingState"]
print(f"Build state: {state}") # VALID になれば完了
BUILD_ID = build["id"]
4. ビルドをバージョンに紐付け
既存のバージョン(PREPARE_FOR_SUBMISSION 状態)に新しいビルドを紐付ける。
# バージョンIDを取得
result = api_request("GET",
f"apps/{APP_ID}/appStoreVersions"
"?filter[versionString]=1.1.0&filter[platform]=IOS")
VERSION_ID = result["data"][0]["id"]
state = result["data"][0]["attributes"]["appStoreState"]
print(f"Version: {VERSION_ID}, state: {state}")
# ビルドを紐付け
api_request("PATCH",
f"appStoreVersions/{VERSION_ID}/relationships/build", {
"data": {
"type": "builds",
"id": BUILD_ID
}
})
print("Build assigned to version")
5. 暗号化コンプライアンスの設定
標準的なHTTPS通信のみで独自の暗号化を使用していない場合:
api_request("PATCH", f"builds/{BUILD_ID}", {
"data": {
"type": "builds",
"id": BUILD_ID,
"attributes": {
"usesNonExemptEncryption": False
}
}
})
注意: 既に設定済みの場合は 409 エラーが返る。その場合はスキップしてよい。
6. whatsNew(新機能)の設定
アップデート版の審査提出で最も重要なステップ。各ローカライズに対して whatsNew を設定しないと審査提出がブロックされる。
# ローカライズ一覧を取得
result = api_request("GET",
f"appStoreVersions/{VERSION_ID}/appStoreVersionLocalizations")
whats_new = {
"ja": "- チップ(Tip Jar)機能を追加しました\n- 不具合を修正しました",
"en-US": "- Added Tip Jar feature\n- Bug fixes"
}
for loc in result["data"]:
locale = loc["attributes"]["locale"]
loc_id = loc["id"]
if locale in whats_new:
api_request("PATCH",
f"appStoreVersionLocalizations/{loc_id}", {
"data": {
"type": "appStoreVersionLocalizations",
"id": loc_id,
"attributes": {
"whatsNew": whats_new[locale]
}
}
})
print(f"whatsNew set for {locale}")
初回リリースとの違い: 初回は
description(説明文)、keywords(キーワード)、スクリーンショットなども設定が必要だが、アップデートではこれらは前のバージョンから引き継がれる。whatsNewだけが新たに必要になる。
7. 審査提出
reviewSubmissions API を使って審査に提出する(旧 appStoreVersionSubmissions は非推奨)。
# Step 1: レビュー提出を作成
result = api_request("POST", "reviewSubmissions", {
"data": {
"type": "reviewSubmissions",
"attributes": {
"platform": "IOS"
},
"relationships": {
"app": {
"data": {
"type": "apps",
"id": APP_ID
}
}
}
}
})
SUBMISSION_ID = result["data"]["id"]
# Step 2: バージョンを提出に追加
api_request("POST", "reviewSubmissionItems", {
"data": {
"type": "reviewSubmissionItems",
"relationships": {
"reviewSubmission": {
"data": {
"type": "reviewSubmissions",
"id": SUBMISSION_ID
}
},
"appStoreVersion": {
"data": {
"type": "appStoreVersions",
"id": VERSION_ID
}
}
}
}
})
# Step 3: 審査に提出
result = api_request("PATCH", f"reviewSubmissions/{SUBMISSION_ID}", {
"data": {
"type": "reviewSubmissions",
"id": SUBMISSION_ID,
"attributes": {
"submitted": True
}
}
})
print(f"State: {result['data']['attributes']['state']}")
# => WAITING_FOR_REVIEW
8. 審査承認後のリリース
App Store Connectでリリース方法を「手動リリース」に設定している場合、審査承認後のステータスは PENDING_DEVELOPER_RELEASE になる。APIでリリースするには appStoreVersionReleaseRequests を使う。
# バージョンの状態を確認
result = api_request("GET",
f"apps/{APP_ID}/appStoreVersions"
"?filter[versionString]=1.1.0&filter[platform]=IOS")
version = result["data"][0]
VERSION_ID = version["id"]
state = version["attributes"]["appStoreState"]
print(f"State: {state}") # PENDING_DEVELOPER_RELEASE
# リリース
api_request("POST", "appStoreVersionReleaseRequests", {
"data": {
"type": "appStoreVersionReleaseRequests",
"relationships": {
"appStoreVersion": {
"data": {
"type": "appStoreVersions",
"id": VERSION_ID
}
}
}
}
})
print("Released!")
注意: リリース後、App Storeへの反映には数時間かかる場合がある。
9. 配信地域の設定
デフォルトでは配信地域が限定されている場合がある。全世界で利用可能にするには appAvailabilities API(v2)を使う。
# 全テリトリーを取得
result = api_request("GET", "territories?limit=200")
all_territories = [t["id"] for t in result["data"]]
print(f"Total territories: {len(all_territories)}")
# インライン作成用のペイロードを構築
included = []
territory_refs = []
for tid in all_territories:
local_id = f"${{territory-{tid}}}"
territory_refs.append({"type": "territoryAvailabilities", "id": local_id})
included.append({
"type": "territoryAvailabilities",
"id": local_id,
"attributes": {"available": True},
"relationships": {
"territory": {
"data": {"type": "territories", "id": tid}
}
}
})
# v2 APIで配信地域を設定
token = generate_token()
url = "https://api.appstoreconnect.apple.com/v2/appAvailabilities"
data = {
"data": {
"type": "appAvailabilities",
"attributes": {"availableInNewTerritories": True},
"relationships": {
"app": {
"data": {"type": "apps", "id": APP_ID}
},
"territoryAvailabilities": {
"data": territory_refs
}
}
},
"included": included
}
# v2エンドポイントなので直接リクエスト
import urllib.request
req = urllib.request.Request(
url, data=json.dumps(data).encode(), method="POST",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
)
resp = urllib.request.urlopen(req)
print(f"Status: {resp.status}") # 201 で成功
注意: このAPIは v2 エンドポイント(
/v2/appAvailabilities)を使用する。v1のapi_requestヘルパーはベースURLが/v1/のため、直接リクエストを送る必要がある。また、インライン作成ではidに${local-id}形式を使う。
IAP(アプリ内課金)の審査提出に関する注意
初回のIAPはブラウザでの操作が必須
アプリで初めてIAPを提出する場合、App Store ConnectのブラウザでバージョンにIAPを紐づけて提出する必要がある。 APIだけでは完結できない。
これは Apple の仕様で、inAppPurchaseSubmissions API を呼び出すと以下のエラーが返る:
STATE_ERROR.FIRST_IAP_MUST_BE_SUBMITTED_ON_VERSION
"The first In-App Purchase for an app must be submitted for review
at the same time that you submit an app version."
また、reviewSubmissionItems でIAPを指定するリレーションシップは存在しない:
# reviewSubmissionItems で指定できるリレーションシップ(Apple公式ドキュメントより)
- reviewSubmission(必須)
- appStoreVersion
- appEvent
- appCustomProductPageVersion
- appStoreVersionExperiment / V2
- gameCenterLeaderboardVersion 等
# IAP関連のリレーションシップは存在しない
初回IAP提出の手順
- APIでバージョンの作成・ビルド紐付け・whatsNew設定を行う
- App Store Connectのブラウザで該当バージョンのページを開く
- 「アプリ内課金とサブスクリプション」セクションで「アプリ内課金またはサブスクリプションを選択」をクリック
- IAP商品にチェックを入れて「完了」
- APIで
reviewSubmissionsを作成し、審査提出する
# 手順5: ブラウザでIAPを紐づけた後、APIで審査提出
result = api_request("POST", "reviewSubmissions", {
"data": {
"type": "reviewSubmissions",
"attributes": {"platform": "IOS"},
"relationships": {
"app": {"data": {"type": "apps", "id": APP_ID}}
}
}
})
SUBMISSION_ID = result["data"]["id"]
api_request("POST", "reviewSubmissionItems", {
"data": {
"type": "reviewSubmissionItems",
"relationships": {
"reviewSubmission": {"data": {"type": "reviewSubmissions", "id": SUBMISSION_ID}},
"appStoreVersion": {"data": {"type": "appStoreVersions", "id": VERSION_ID}}
}
}
})
api_request("PATCH", f"reviewSubmissions/{SUBMISSION_ID}", {
"data": {
"type": "reviewSubmissions",
"id": SUBMISSION_ID,
"attributes": {"submitted": True}
}
})
2回目以降のIAP追加
初回のIAP審査が承認されれば、以降のIAP追加は inAppPurchaseSubmissions APIで個別に提出できる(バージョン提出不要)。
api_request("POST", "inAppPurchaseSubmissions", {
"data": {
"type": "inAppPurchaseSubmissions",
"relationships": {
"inAppPurchaseV2": {
"data": {"type": "inAppPurchases", "id": IAP_ID}
}
}
}
})
確認方法
審査承認後、IAP商品の状態をAPIで確認する:
result = api_request("GET", f"apps/{APP_ID}/inAppPurchasesV2")
for iap in result["data"]:
state = iap["attributes"]["state"]
product_id = iap["attributes"]["productId"]
print(f"{product_id}: {state}")
# APPROVED なら正常、READY_TO_SUBMIT なら審査に含まれていない
教訓: IAP商品がアプリ審査に含まれたかは、審査承認後に
stateがAPPROVEDに変わったかどうかで判断する。READY_TO_SUBMITのままであれば、ブラウザでIAPを紐づけ直して再提出する。
参考: IAP商品の登録方法は App Store Connect APIでチップ(Tip Jar)機能を追加する完全ガイド を参照。
まとめ
アップデート版の審査提出で必要なAPI操作は以下の通り:
| 操作 | エンドポイント | メソッド |
|---|---|---|
| ビルド状態確認 | builds?filter[app]={id} | GET |
| ビルド紐付け | appStoreVersions/{id}/relationships/build | PATCH |
| 暗号化コンプライアンス | builds/{id} | PATCH |
| whatsNew設定 | appStoreVersionLocalizations/{id} | PATCH |
| 審査提出(作成) | reviewSubmissions | POST |
| 審査提出(アイテム追加) | reviewSubmissionItems | POST |
| 審査提出(確定) | reviewSubmissions/{id} | PATCH |
| リリース | appStoreVersionReleaseRequests | POST |
| 配信地域設定 | v2/appAvailabilities | POST |
初回リリースと比べて操作が少なく、ビルドのアップロードからリリースまでほぼコマンドラインで完結できる。ただし、初回のIAP提出時のみApp Store Connectブラウザでの紐づけ操作が必要(2回目以降はAPIで完結可能)。CI/CDパイプラインへの組み込みも容易である。
関連記事
- App Store Connect APIだけでiOSアプリを審査提出する完全ガイド — 初回リリース時の全手順
- App Store Connect APIでチップ(Tip Jar)機能を追加する完全ガイド — IAP商品の登録・設定