ホーム 記事一覧 ブック DH週間トピックス 検索 このサイトについて
English
App Store審査リジェクト後の修正・再提出をApp Store Connect APIで実行する

App Store審査リジェクト後の修正・再提出をApp Store Connect APIで実行する

App Store Connect の審査でリジェクトされた後、修正からの再提出までの全工程を API で実行しました。ブラウザでの操作は一切行っていません。 リジェクトの内容 JPS Explorer(ジャパンサーチ文化資源探索アプリ)の初回提出で、2つの問題を指摘されました。 チップ(Tip Jar)画面でエラーが表示される — In-App Purchase の商品が App Store Connect に未登録だったため カメラ検索の「撮影」ボタンでクラッシュ — iOS の Info.plist に NSCameraUsageDescription が未設定だったため 修正内容 カメラクラッシュの修正 Info.plist にカメラと写真ライブラリの権限記述を追加しました。 <key>NSCameraUsageDescription</key> <string>文化資源に似た画像を検索するためにカメラを使用します</string> <key>NSPhotoLibraryUsageDescription</key> <string>文化資源に似た画像を検索するために写真ライブラリを使用します</string> Flutter の image_picker パッケージを使ってカメラにアクセスする場合、この記述がないと実機でクラッシュします。シミュレータではカメラが使えないため、この問題には気づきにくいようです。 合わせて、PlatformException の camera_access_denied と photo_access_denied のハンドリングも追加しました。 チップ画面の修正 In-App Purchase の商品が未登録の場合、StoreKit がエラーを返します。エラーをそのまま表示するのではなく、「準備中です」というメッセージに変更しました。 API による再提出の手順 リジェクト後の状態では、App Store Connect のブラウザに「編集」ボタンと「App Reviewに再提出」ボタンが表示されます。これらの操作はすべて API で実行できます。 Step 1: ビルド番号を上げてアップロード リジェクトされたビルドと同じビルド番号では再アップロードできないため、pubspec.yaml のビルド番号を上げます。 # 変更前 version: 1.0.0+1 # 変更後 version: 1.0.0+2 ビルドしてアップロードします。 ...

Firebase不要:Apple標準ツールだけでiOSアプリを運用する

はじめに iOSアプリの運用を始めると、すぐに以下のような課題に直面します。 クラッシュしているのか、どこで落ちているのか知りたい ダウンロード数やセッション数を把握したい 古いバージョンのユーザーにアップデートを促したい レビューを書いてもらいたい 多くの記事が Firebase Crashlytics + Firebase Analytics の導入を勧めますが、これはGoogleのサーバーにユーザーのデバイス情報・使用状況・クラッシュログなどを送信することを意味します。App Storeへの提出時にも「データ収集・追跡」の申告が必要になります。 個人開発や学術目的のアプリでは、そこまでのデータ収集が本当に必要でしょうか? 本記事では、ユーザーデータを外部に送信せず、Apple標準ツールとOSSだけで運用する構成を、実際のApp Store公開アプリ KotenOCR での経験をもとに紹介します。 構成の全体像 目的 Firebase構成 本記事の構成 外部送信 クラッシュ計測 Crashlytics MetricKit なし アナリティクス Firebase Analytics App Store Connect Analytics API なし アップデート促進 Remote Config Siren(iTunes Lookup API) なし※ レビュー促進 In-App Messaging SKStoreReviewRequest なし クラッシュログ閲覧 Crashlytics Console Xcode Organizer なし ※ Sirenは iTunes Lookup API(Apple公式)にバージョン確認のリクエストを送りますが、ユーザーの個人情報は含まれません。 1. MetricKit — クラッシュ計測 MetricKit はiOS 13+で利用可能なApple標準フレームワークです。クラッシュレポート、ハング(応答なし)、起動時間などのパフォーマンスデータをアプリ内で受け取れます。 実装 import MetricKit class MetricKitManager: NSObject, MXMetricManagerSubscriber { static let shared = MetricKitManager() func start() { MXMetricManager.shared.add(self) } // iOS 15+: クラッシュ・ハングの即時通知 func didReceive(_ payloads: [MXDiagnosticPayload]) { for payload in payloads { if let crashDiagnostics = payload.crashDiagnostics { for crash in crashDiagnostics { // クラッシュのスタックトレースを処理 let description = crash.callStackTree.jsonRepresentation() // ログに記録、またはローカルに保存 } } } } // iOS 14+: 24時間ごとのパフォーマンスメトリクス func didReceive(_ payloads: [MXMetricPayload]) { for payload in payloads { // 起動時間、メモリ使用量、ディスクI/Oなど } } } ```text アプリの起動時に `MetricKitManager.shared.start()` を呼ぶだけです。 ### MetricKitの特徴 - **外部送信なし** — データはアプリ内で完結。Appleのサーバーにも送らない - **即時通知** — iOS 15以降、クラッシュ直後に `didReceive(_: [MXDiagnosticPayload])` が呼ばれる - **バッテリー・パフォーマンス** — クラッシュだけでなく、起動時間やメモリ使用量も計測できる ### Xcode Organizer との違い Xcode Organizer(Xcode → Window → Organizer → Crashes)も追加実装なしでクラッシュログを表示しますが、以下の制限があります。 - ユーザーが「診断データ共有」をオンにしている必要がある(オプトイン) - ある程度のユーザー数がないとデータが集まらない - 蓄積に数日〜数週間かかる ユーザー数が少ない段階では MetricKit の方が実用的です。 ## 2. App Store Connect Analytics API — アナリティクス App Store Connect のWebコンソールでもアナリティクスは見られますが、APIで取得すると自動化や詳細な分析が可能になります。 ### 準備:JWT認証 App Store Connect APIは JWT(JSON Web Token)で認証します。 ```python import jwt import time import requests KEY_ID = "YOUR_KEY_ID" ISSUER_ID = "YOUR_ISSUER_ID" with open("AuthKey.p8", "r") as f: private_key = f.read() payload = { "iss": ISSUER_ID, "iat": int(time.time()), "exp": int(time.time()) + 1200, "aud": "appstoreconnect-v1" } token = jwt.encode(payload, private_key, algorithm="ES256", headers={"kid": KEY_ID}) headers = {"Authorization": f"Bearer {token}"} ```text ### レポートリクエストの作成 Analytics APIを使うには、まずレポートリクエストを作成(または既存のものを取得)します。 ```python APP_ID = "YOUR_APP_ID" # レポートリクエストの確認 r = requests.get( f"https://api.appstoreconnect.apple.com/v1/apps/{APP_ID}/analyticsReportRequests", headers=headers ) report_requests = r.json()["data"] # なければ作成(ONGOINGで継続的にレポート生成) if not report_requests: r = requests.post( f"https://api.appstoreconnect.apple.com/v1/apps/{APP_ID}/analyticsReportRequests", headers=headers, json={ "data": { "type": "analyticsReportRequests", "attributes": {"accessType": "ONGOING"}, "relationships": { "app": {"data": {"type": "apps", "id": APP_ID}} } } } ) ```text ### レポートの取得 ```python import gzip import csv import io from collections import defaultdict REPORT_REQUEST_ID = report_requests[0]["id"] def fetch_report(report_name): """指定したレポートの全インスタンスを取得して結合""" # レポート一覧を取得 r = requests.get( f"https://api.appstoreconnect.apple.com/v1/analyticsReportRequests/" f"{REPORT_REQUEST_ID}/reports", headers=headers, params={"limit": 200} ) for report in r.json()["data"]: if report["attributes"]["name"] == report_name: # インスタンス(日付ごとのデータ)を取得 r2 = requests.get( f"https://api.appstoreconnect.apple.com/v1/analyticsReports/" f"{report['id']}/instances", headers=headers, params={"limit": 10} ) all_data = "" for inst in r2.json().get("data", []): # セグメント(実際のデータファイル)を取得 r3 = requests.get( f"https://api.appstoreconnect.apple.com/v1/" f"analyticsReportInstances/{inst['id']}/segments", headers=headers ) for seg in r3.json().get("data", []): url = seg["attributes"].get("url") if url: r4 = requests.get(url) text = gzip.decompress(r4.content).decode("utf-8") lines = text.strip().split("\n") if not all_data: all_data = text.strip() else: all_data += "\n" + "\n".join(lines[1:]) return all_data return None ```text ### 利用可能なレポートの例 | レポート名 | カテゴリ | 内容 | |-----------|---------|------| | App Downloads Standard | COMMERCE | ダウンロード数(新規/再DL、デバイス、地域別) | | App Sessions Standard | APP_USAGE | セッション数、平均時間 | | App Store Discovery and Engagement Standard | APP_STORE_ENGAGEMENT | インプレッション、ページ閲覧、タップ | | App Store Installation and Deletion Standard | APP_USAGE | インストール・削除数 | | App Crashes | APP_USAGE | クラッシュ数 | ### 実際の出力例 KotenOCRの公開直後のデータです。 ```text === ダウンロード数 === 2026-03-20: 新規 95 / 再DL 2 / 計 97 2026-03-21: 新規 348 / 再DL 14 / 計 362 2026-03-22: 新規 104 / 再DL 188 / 計 292 2026-03-23: 新規 23 / 再DL 231 / 計 254 合計: 新規 570 / 再DL 435 / 計 1,005 === セッション数 === 2026-03-20: 159 セッション / 平均 81秒 2026-03-21: 345 セッション / 平均 85秒 2026-03-22: 126 セッション / 平均 84秒 2026-03-23: 50 セッション / 平均 115秒 === App Store エンゲージメント === 2026-03-19: Impression: 79 / Page view: 14 / Tap: 19 2026-03-20: Impression: 172 / Page view: 315 / Tap: 336 2026-03-21: Impression: 596 / Page view: 576 / Tap: 600 2026-03-22: Impression: 193 / Page view: 106 / Tap: 101 === インストール / 削除 === 2026-03-20: インストール 63 / 削除 0 2026-03-21: インストール 249 / 削除 0 2026-03-22: インストール 66 / 削除 0 2026-03-23: インストール 56 / 削除 0 ```text データの遅延は約1〜2日です。Web UIではほぼ前日分まで閲覧できます。 ## 3. Siren — アップデート促進 [Siren](https://github.com/ArtSabintsev/Siren) は、App Storeの最新バージョンとアプリのバージョンを比較し、更新を促すダイアログを表示するOSSライブラリです。 ### 導入 Swift Package Manager で追加します。 ```text https://github.com/ArtSabintsev/Siren (6.1.0+) ```text ### 実装 ```swift import Siren // SwiftUIの場合、onAppear内で呼ぶこと(init()では動かない) private func configureSiren() { let siren = Siren.shared siren.rulesManager = RulesManager( majorUpdateRules: .critical, // メジャー: スキップ不可 minorUpdateRules: .annoying, // マイナー: スキップ不可(後で通知) patchUpdateRules: .default // パッチ: スキップ可能 ) siren.wail() } ```text ### 注意点 - **SwiftUIでは `onAppear` で呼ぶ** — `App.init()` の段階ではUIWindowが存在しないため、Sirenが正しく動作しません - **テスト方法** — Sirenは iTunes Lookup API で App Store上のバージョンと比較するため、テスト時は Info.plist の `CFBundleShortVersionString` を意図的に古いバージョンに設定する必要があります - **リリース直後の遅延** — デフォルトでリリースから1日経過しないとダイアログが表示されません(`showAlertAfterCurrentVersionHasBeenReleasedForDays` で変更可能) ### ダイアログの種類 | ルール | 動作 | |--------|------| | `.critical` | 「Update」ボタンのみ。スキップ不可 | | `.annoying` | 「Update」「Next time」の2ボタン。スキップ不可だが後回し可 | | `.default` | 「Update」「Next time」「Skip」の3ボタン | | `.relaxed` | `.default` と同じだが、表示頻度が低い | ## 4. SKStoreReviewRequest — レビュー促進 Apple標準のレビュー促進APIです。追加のフレームワークは不要です。 ```swift import StoreKit // OCR成功後など、ユーザーが価値を感じたタイミングで呼ぶ func requestReviewIfAppropriate() { let count = UserDefaults.standard.integer(forKey: "ocrSuccessCount") // 3回目のOCR成功後にレビューを依頼 if count == 3 { if let scene = UIApplication.shared.connectedScenes .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene { SKStoreReviewController.requestReview(in: scene) } } } ```text ### ポイント - Appleがダイアログの表示を制御するため、呼び出しても必ず表示されるとは限らない - **年3回まで**の制限がある - ユーザーが「成功体験」を感じた直後に呼ぶのが効果的 ## 5. 所感と限界 ### この構成でカバーできること - 基本的なダウンロード数・セッション数の把握 - クラッシュの検知と原因調査 - ユーザーへのアップデート促進 - レビュー促進 実際に KotenOCR をこの構成で運用していますが、個人開発アプリとしては十分な情報が得られています。 ### この構成の限界 - **画面単位・機能単位の詳細な計測ができない** — 「どの画面が最も使われているか」「翻訳機能の利用率は?」といった分析にはアプリ内のイベント計測が必要 - **リアルタイム性がない** — Analytics APIのデータには1〜2日の遅延がある - **MetricKitのクラッシュ情報は構造化されていない** — スタックトレースのJSON解析は自前で実装する必要がある - **A/Bテストや段階的ロールアウトは不可能** — これらが必要な場合はFirebase Remote Configなどが必要 ### Firebaseが必要になるケース - ユーザー行動の詳細な分析が必要な場合 - リアルタイムのクラッシュ通知が必要な場合 - Feature Flagsやリモート設定が必要な場合 - プッシュ通知を実装する場合 ## まとめ | ツール | 用途 | 実装コスト | |--------|------|-----------| | MetricKit | クラッシュ・パフォーマンス計測 | 小 | | App Store Connect Analytics API | DL数・セッション・エンゲージメント | 中(Python) | | Siren | アップデート促進ダイアログ | 小 | | SKStoreReviewRequest | レビュー促進 | 最小 | | Xcode Organizer | クラッシュログ閲覧 | なし | Firebase不要でも、個人開発アプリの運用に必要な情報はほぼ揃います。プライバシーポリシーの記載義務も最小限で済み、ユーザーに対して「データを収集していません」と言い切れるのは大きなメリットです。 まずはこの構成で始めて、必要に応じてFirebaseを追加する、という段階的なアプローチがおすすめです。 ## 関連記事 - [KotenOCR:くずし字をオフラインで認識するiOSアプリの開発と公開](/posts/kotenocr-ios-app/) - [App Store Connect APIだけでiOSアプリを審査提出する手順](/posts/appstore-connect-api-guide/) - [iOSアプリのメモリ最適化とクラッシュ修正の実践記録](/posts/ios-memory-crash-fixes/)

ジャパンサーチAPIを活用した文化資源探索アプリの開発とApp Store公開

ジャパンサーチAPIを活用した文化資源探索アプリの開発とApp Store公開

ジャパンサーチ( https://jpsearch.go.jp )のWeb APIを使い、日本の文化資源を探索するiOS/Androidアプリ「JPS Explorer」を開発しました。API調査からアプリ実装、App Storeリリースの自動化までの過程を記録します。 ジャパンサーチのAPI ジャパンサーチは国立国会図書館が運営する、3,200万件以上のデジタル文化資源のメタデータを横断検索できるサービスです。簡易Web APIが公開されており、以下のような検索が可能です。 パラメータ 機能 keyword キーワード検索 text2image テキストでモチーフを指定して画像検索 image 既存アイテムIDで類似画像検索 g-coordinates 緯度・経度・半径で場所検索 r-tempo 年代範囲で時代検索 API調査で気づいた点 座標フィールドのキー名 位置情報検索(g-coordinates)のレスポンスで座標データは common.coordinates に格納されています。経度のキーは lon で、lng や longitude ではありません。 "coordinates": { "lat": 35.669, "lon": 139.764 } ギャラリーAPIの多言語フィールド ギャラリー検索(/api/curation/search)のレスポンスでは、title と summary が文字列ではなくオブジェクトになっています。 "title": {"ja": "耳鳥斎", "en": "Jichosai"}, "image": {"url": "https://...", "thumbnailUrl": "https://..."} 単純に .toString() すると {ja: 耳鳥斎, en: Jichosai} のような文字列がUIに表示されてしまうため、言語キーでアクセスする必要があります。 ギャラリー詳細のアイテム構造 ギャラリー詳細(/api/curation/{id})のアイテムは contents ではなく parts 配列にネストされています。type: "jps-curation-list-item" を再帰的に探索してIDを収集する必要がありました。一部のギャラリーでは subPages にもアイテムが含まれているようです。 画像アップロードによる類似検索 公式APIガイドには記載されていませんが、Web UIのネットワーク通信を調査したところ、画像アップロードによる類似検索が3段階のAPIで実現されていることがわかりました。 POST /dl/api/imagefeatures/ で画像のBase64データから64次元の特徴量ベクトルを取得 POST /api/item/create-image-feature で特徴量から一時的な検索IDを生成 GET /api/item/search/jps-cross?image={ID} で通常の類似検索を実行 Step 2では X-Requested-With: XmlHttpRequest ヘッダーが必要で、レスポンスはプレーンテキストでIDが返ります。 ...

Apple Sales Reports APIのデータ反映時刻とYouTube APIのクォータリセットを実測した

外部APIを使って日次のデータ取得を自動化する場合、「いつデータが利用可能になるか」「いつクォータがリセットされるか」を把握しておくとスケジューリングに役立ちます。この記事では、Apple App Store Connect Sales Reports APIとYouTube Data API v3について、実際に観測したタイミングを記録します。 Apple App Store Connect Sales Reports API 公式ドキュメントの記載 Appleの公式ドキュメントでは、日次の売上レポートは翌日の太平洋時間 午前8時まで(by 8:00 AM Pacific Time)に利用可能になるとされています。 参照: Sales and Trends Reports Availability - Apple Developer 実測結果 2026年3月22日に、前日(2026年3月21日)分のデータがいつ取得可能になるかを1時間ごとに確認しました。 16:00 JST (0:00 PT) → 取得不可 17:00 JST (1:00 PT) → 取得不可 18:00 JST (2:00 PT) → 取得不可 19:00 JST (3:00 PT) → 取得不可 20:00 JST (4:00 PT) → 取得不可 21:09 JST (5:09 PT) → 取得可能 今回の計測では、太平洋時間の午前5時頃(日本時間 21時頃)にデータが利用可能になっていました。公式に記載されている午前8時よりも約3時間早いタイミングです。 ...

App Storeのスクリーンショット生成をPython+UIテストで完全自動化する

App Storeのスクリーンショット生成をPython+UIテストで完全自動化する

TL;DR XCUITestでiPhone・iPadシミュレータのスクリーンショットを日英両言語で自動撮影 PythonのPillowでグラデーション背景+デバイスフレーム+テキストオーバーレイのマーケティング画像を生成 xcrun simctl io recordVideoでデモ動画も録画 App Store Connect APIで自動アップロード すべてをシェルスクリプト1本で実行可能 はじめに iOSアプリのApp Storeスクリーンショットは、iPhone 6.7インチ、iPad 12.9インチの各サイズを日英2言語分用意すると、それだけで12枚以上の画像を作る必要があります。 アプリを更新するたびに手作業でスクリーンショットを撮り直し、FigmaやPhotoshopでマーケティング画像を作り、App Store Connectに1枚ずつアップロードするのは手間がかかります。 本記事では、撮影 → 画像生成 → アップロードの全工程をコマンド一発で実行できるパイプラインの構築方法を解説します。 全体構成 capture_screenshots.sh ├── Step 1: シミュレータの準備(起動 + テスト画像追加) ├── Step 2: XCUITestでスクリーンショット撮影(JA/EN × iPhone/iPad) ├── Step 3: sipsでApple規定サイズにリサイズ ├── Step 4: Pillow でマーケティング画像を生成 ├── Step 5: xcrun simctl io でデモ動画を録画 └── Step 6: App Store Connect APIでアップロード Step 1: XCUITestでスクリーンショットを撮影する テストコードの設計 UIテスト用のテストクラスを作成します。ポイントは以下の3つです。 テスト画像の自動読み込み: TEST_IMAGE_PATH環境変数で画像パスを渡し、PHPickerを経由せずに画像を直接ロードします 言語切り替え: xcodebuild -testLanguageで設定された言語を-AppleLanguagesとしてアプリに渡します レビューダイアログの抑制: 起動引数で不要なダイアログを抑制します final class ScreenshotTests: XCTestCase { private var app: XCUIApplication! private let screenshotDir = ProcessInfo.processInfo.environment["SCREENSHOT_DIR"] ?? "/tmp/myapp_screenshots" override func setUpWithError() throws { continueAfterFailure = false app = XCUIApplication() // オンボーディングをスキップ、レビューダイアログを抑制 app.launchArguments += ["-hasCompletedOnboarding", "YES"] // テスト言語をアプリの言語設定に反映 let preferredLang = Locale.preferredLanguages.first ?? "ja" let langCode = preferredLang.components(separatedBy: "-").first ?? "ja" app.launchArguments += ["-AppleLanguages", "(\(langCode))", "-AppleLocale", langCode] // テスト用画像パスを環境変数で渡す app.launchEnvironment["TEST_IMAGE_PATH"] = "/path/to/test_sample.jpg" try FileManager.default.createDirectory( atPath: screenshotDir, withIntermediateDirectories: true ) } func testCaptureScreenshots() throws { app.launch() // 処理完了を待機 let backButton = app.buttons["back_button"] XCTAssertTrue(backButton.waitForExistence(timeout: 300)) sleep(2) // メイン画面のスクリーンショット saveScreenshot(name: "04_result") // 他の画面に遷移してスクリーンショットを撮る // ... backButton.tap() sleep(1) saveScreenshot(name: "01_camera") } private func saveScreenshot(name: String) { let screenshot = app.windows.firstMatch.screenshot() let attachment = XCTAttachment(screenshot: screenshot) attachment.name = name attachment.lifetime = .keepAlways add(attachment) let path = "\(screenshotDir)/\(name).png" try? screenshot.pngRepresentation.write(to: URL(fileURLWithPath: path)) } } saveScreenshotメソッドは、XCTestのXCTAttachmentとしてテスト結果に添付すると同時に、指定ディレクトリにPNGファイルとして保存します。ファイル名のプレフィックス(01_, 04_等)は、後のマーケティング画像生成で優先順位を制御するために使います。 ...

App Store Connect APIでiOSアプリのアップデートを審査提出する方法

App Store Connect APIでiOSアプリのアップデートを審査提出する方法

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 だけではサブプロセスに渡らない。 ...

App Store Connect APIでiOSアプリにチップ(Tip Jar)機能を追加する完全ガイド

App Store Connect APIでiOSアプリにチップ(Tip Jar)機能を追加する完全ガイド

TL;DR iOSアプリにチップ(Tip Jar)機能を追加した。SwiftUI + StoreKit 2 でアプリ側を実装し、App Store Connect REST API を使って商品登録・ローカライズ・価格設定・審査用スクリーンショット・配信地域設定・TestFlight配信までをコマンドラインから完了させた。本記事ではその全手順を再現可能な形で記載する。 前提: App Store Connect APIだけでiOSアプリを審査提出する完全ガイドの続編として、APIキーの取得・JWT生成は既にセットアップ済みとする。 全体の流れ アプリ側の実装(StoreKit 2 + SwiftUI) App Store Connect APIで商品登録(3つの消費型アイテム) ローカライズ設定(日本語・英語) 価格設定($0.99 / $2.99 / $6.99) 審査用スクリーンショットのアップロード 配信地域の設定 有料アプリ契約の締結 TestFlightでの動作確認 1. アプリ側の実装 1.1 StoreKit設定ファイル Xcodeのテスト環境用に TipJar.storekit を作成する。これによりシミュレータでStoreKitのテストが可能になる。 { "products" : [ { "displayPrice" : "0.99", "familyShareable" : false, "internalID" : "tip_small_001", "localizations" : [ { "description" : "開発を応援する小さなチップ", "displayName" : "小さな応援", "locale" : "ja" }, { "description" : "A small tip to support development", "displayName" : "Small Tip", "locale" : "en_US" } ], "productID" : "com.example.app.tip.small", "referenceName" : "Small Tip", "type" : "Consumable" } ] } XcodeGen を使っている場合は project.yml の scheme に StoreKit 設定を追加する: ...

App Store Connect APIだけでiOSアプリを審査提出する手順

App Store Connect APIだけでiOSアプリを審査提出する手順

TL;DR App Store Connect の REST API を使い、コマンドラインからiOSアプリの審査提出に必要なほぼ全作業(メタデータ・スクリーンショット・年齢レーティング・ビルド紐付け・URL設定・暗号化コンプライアンス・価格設定)を完了させた。本記事ではその手順を再現可能な形で記載する。 注意: 「App Privacy(アプリのプライバシー)」のデータ使用状況の宣言だけは、2026年3月時点でAPIが提供されておらず、App Store Connectのブラウザから設定する必要がある。 前提条件 Apple Developer Program に登録済み App Store Connect で API キーを発行済み アプリの Bundle ID が登録済み Xcode でアーカイブ・アップロード済みのビルドが存在する(xcodebuild -exportArchive でアップロード可能) Python 3 + PyJWT + cryptography がインストール済み pip install PyJWT cryptography 1. API キーの準備 1.1 API キーの発行 App Store Connect → ユーザーとアクセス → 統合 → App Store Connect API から新しいキーを発行する。 名前: 任意(例: deploy-key) アクセス: Admin(メタデータ更新・提出に必要) 発行後、以下の情報をメモする: 項目 説明 Key ID APIキーの識別子(10文字程度の英数字) Issuer ID 組織の識別子(UUID形式) ダウンロードした .p8 ファイルは安全な場所に保存する(一度しかダウンロードできない): ...