App Storeの解約画面に開発者のメッセージと割引を表示できるように|App Store Connect API 4.6を使う設定手順と、審査で断られる文面

解約の途中に開発者のメッセージを出す機能を、Appleが11月4日に提供開始

Appleは2026年11月4日、自動更新サブスクリプションの解約の途中に開発者のメッセージを表示できる「リテンションメッセージ」の提供開始を発表しました。利用者が解約を進めると確認画面に文章や画像、割引が表示され、解約をやめるきっかけを作れます。

設定はApp Store Connectの画面からでき、自分のサーバーは要りません。同じ日に公開されたApp Store Connect API 4.6を使えば、同じ設定をプログラムから進められます。この4.6では、Sales and Trendsの INSTALLS レポートの新規生成が止まる変更も入りました。

この記事は、iOSアプリでサブスクリプションを提供している開発者に向けたものです。Appleの文書をもとに表示の条件と審査で断られる文面を整理します。4.5.1と4.6のOpenAPI仕様を手元で比べ、リクエストの本文を4.6の仕様で検証した結果も載せます。

記事の要点

サブスクリプションの解約画面に出す文章を、審査の条件に沿って用意する

2026年11月6日時点のAppleの文書と、API 4.6の仕様をもとにしています。

設定の方法

サーバーなしで設定できる

App Store Connectの画面かAPIで設定し、App Reviewの審査を通します。利用者ごとに出し分けるときだけ、自分のサーバーを使います。

メッセージに書けないこと

割引と価格

見出しと本文は割引の対象外の人にも表示されるので、書くと審査で断られます。

API 4.6の追加

26パス、34操作

4.5.1と比べて、削除されたパスはありません。

レポートの変更

INSTALLSの新規生成が終了

過去の分は取得でき、代わりのレポートはありません。

解約の確認画面に出る内容は3通り

利用者がサブスクリプションの詳細画面で解約を押すと、確認画面が開きます。リテンションメッセージはこの画面に表示されます。利用者はそのまま解約するか、解約をやめるかを選びます。表示される内容は、設定と利用者の条件で次のように決まります(App Store Connectのヘルプ)。

文章だけ

画像を設定しておらず、利用者が受けられる割引もない場合です。

文章と画像

画像を設定していて、利用者が受けられる割引がない場合です。割引を設定していても、利用者が条件に合わなければ画像が表示されます。

文章と割引

利用者が割引の条件に合う場合です。割引は画像の代わりに表示されます。

表示されるのは、iOSとiPadOSの15.1以降、visionOS 1以降、macOS 14以降の端末です(Retention Messaging APIの文書)。AppleはWWDC26のセッションで、リテンションメッセージを導入したサブスクリプションの平均で、解約の確認画面まで来た人のうち解約をやめた割合が1.4ポイント上がったと紹介しました。割引を付けたメッセージは、観測された中で最も高い5.5ポイントでした。11月の提供開始より前に示された数字で、測定の時期や対象は説明されていません。開発者によって結果は異なるとも述べています。

サーバーなしならApp Store Connect、人ごとに変えるならRetention Messaging API

設定の方法は3つあります。4.6で増えたAPIは、画面の操作をプログラムに置き換えるものです。利用者ごとに出し分けるには、別のRetention Messaging APIを使います。

方法 必要なもの 向いている場合
App Store Connectの画面 Account Holder、Admin、App Manager、Marketingのいずれかの権限 まず試す場合。プレビューで見た目を確かめられる
App Store Connect API 4.6 App Store Connect APIのキー 多言語の文章や複数のアプリの設定をまとめて管理する場合
Retention Messaging API 利用の申請、自分のサーバー、性能テストの合格 契約内容に応じてメッセージや別プランの提案を変える場合

複数を設定した場合、1番目に解約時に自分のサーバーが返す応答、2番目にApp Store Connectの画面かAPIで設定したメッセージ、3番目にRetention Messaging APIで登録した既定のメッセージが使われます。自分のサーバーの応答が遅いときや形式が誤っているときは、2番目以降が順に使われます。Appleのセッションはこの理由から、サーバーを使う場合もApp Store Connectの設定を用意するよう勧めています。

API 4.6で増えた操作は、すべてリテンションメッセージ関連

Appleが配布している4.6のOpenAPI仕様を、4.5.1と比べました。4.5.1は有志が保管しているリポジトリから取得しました。同じリポジトリの4.6がAppleの配布物と一致することを確かめたうえで使っています。

App Store Connect API 4.5.1と4.6の仕様の差

+26

パス(1,001→1,027)

+34

操作(1,303→1,337)

+32

スキーマ(1,473→1,505)

0

削除されたパスとスキーマ

筆者が仕様のJSONを比べて数えました。操作はGET、POST、PATCH、DELETEの合計です。既存のスキーマのうち15個は変わりましたが、差分はリテンションメッセージへの関連の追加です。

増えたパスの名前はいずれも retention を含みます。Appleの手順書に沿うと、設定は次の順に進みます。

1. メッセージを作る

POST /v1/retentionMessages。管理用の名前とアプリを指定します。

2. 版を作る

POST /v1/retentionMessageVersions。画像を出すときは、アセットライブラリへ登録した画像の配置(placement)が要ります。手順書の例は版の作成時に placement を指定しています。一方、4.6の仕様では配置の作成(POST /v1/appAssetLibraryPlacements)に版(retentionMessageVersion)の指定が必須で、どちらを先に作るかは文書に書かれていません。今回は送っていないので、最初は文章だけの版か画面での設定で試します。ヘルプが挙げる画像の大きさは3840×2160と5244×2950です。

3. 言語ごとの文章を足す

POST /v1/retentionMessageLocalizations。見出し(header)と本文(body)を言語ごとに登録します。

4. 割引を作る(任意)

POST /v1/retentionOffers。識別子(offerId)、期間、方式、対象者の条件、価格を一度に指定します。

5. サブスクリプションに結び付ける

PATCH /v1/subscriptions/{id}/relationships/retentionMessage。割引を使うなら POST /v1/subscriptions/{id}/relationships/retentionMessageOffers も実行します。

6. 審査へ出す

POST /v1/reviewSubmissions で提出を作り、POST /v1/reviewSubmissionItems に版を指定して加えます。そのあと PATCH /v1/reviewSubmissions/{id} で submitted を true にすると審査へ送られます。状態は GET /v1/retentionMessageVersions/{id} の state で追えます。

割引は作っただけでは表示されません。手順5で結び付け、審査で承認されたあとに、条件に合う利用者に表示されます。版そのものは作成後に変更も削除もできません。見出しと本文は PATCH /v1/retentionMessageLocalizations/{id} で編集できますが、言語は変更できません。承認済みの版には言語を足せないので、承認済みの内容を変えるときは新しい版を作ります。

OpenAPIの検証では、文字数と月数の範囲外の値を検出できない

OpenAPI仕様のスキーマで送信前に検証すれば足りるのかを確かめました。Appleの手順書の例と筆者が作った日本語の例は、4.6の仕様のスキーマの検証をすべて通りました。次の表は、それらの値をわざと崩したリクエストの結果です。クライアントの生成とビルド、App Store Connectへの送信はしていません。

リクエストの内容 Appleの文書の条件 スキーマの検証
割引の prices がない必須エラーになる
offerMode が HALF_PRICE3種類のみエラーになる
header が67文字66文字まで通る
body が145文字144文字まで通る
header と body が null文章は必須(ヘルプ)通る
referenceName が65文字64文字まで通る
割引を受けられる最低の支払い月数(customerEligibilityPaidSubscriptionDurationInMonths)が251〜24、36、48、60通る
割引を再び受けるまでの待ち月数(customerEligibilityWaitBetweenOffersInMonths)が12〜24通る

Python の jsonschema で、App Store Connect API 4.6 の OpenAPI 仕様のスキーマに照らしました。仕様の nullable: true は、JSON Schema の型に null を足す形に変換してから検証しました。2026年11月6日に実行しました。条件の値はAppleの手順書とヘルプによるものです。

型と、必須の指定がある項目はスキーマで検出できますが、文字数と月数の範囲、空の文章は検出できません。言語ごとの文章を翻訳の担当者やスプレッドシートから流し込む運用なら、送る前に次のような確認を足すと、App Store Connectのエラーを待たずに気づけます。文字数はPythonの文字の数え方で試したもので、絵文字などをAppleがどう数えるかは文書に書かれていません。

# App Store Connect の文書にある条件を、送信前に確かめる
LIMITS = {"referenceName": 64, "header": 66, "body": 144}
PAID_MONTHS = set(range(1, 25)) | {36, 48, 60}
WAIT_MONTHS = set(range(2, 25))

def check_text(field, value):
    if not value:
        raise ValueError(f"{field} が空です")
    if len(value) > LIMITS[field]:
        raise ValueError(f"{field} は{LIMITS[field]}文字まで: {len(value)}文字")

def check_offer(paid_months, wait_months=None):
    if paid_months not in PAID_MONTHS:
        raise ValueError(f"支払い期間の条件に使えない値: {paid_months}")
    if wait_months is not None and wait_months not in WAIT_MONTHS:
        raise ValueError(f"待ち期間に使えない値: {wait_months}")

個数の上限もあります。メッセージは1つのアプリに10個まで、1つのサブスクリプションに結び付けられるのは1個までです。割引は、1つのサブスクリプションの請求プランの種類(通常のプランと、1年契約を月払いにするプラン)ごとに10個までです。

メッセージの見出しと本文に割引や価格を書くと審査で断られる

すべてのメッセージはApp Reviewの審査を通ってから表示されます。ヘルプには文面の条件が具体的に書かれています。最も気をつけたいのは、割引や価格に触れないことです。文章は割引の対象外の人にも表示されるためです。

審査で断られる(ヘルプより)

割引や価格に触れる文面(「$9.99」「50% off」「1か月無料」など)

避けるよう書かれている(ヘルプより)

不安をあおる主張や、確かめられない保証

根拠のない最上級や統計

すべて大文字の表記、感嘆符の連続

書ける内容の例(筆者が作成)

解約で使えなくなる機能を事実として伝える

見出し:解約すると記録の同期が止まります

本文:次回の更新日以降は、記録の同期と書き出しが使えなくなります。

例文は形を示すために筆者が作ったもので、審査には出していません。自分のアプリで実際に起きることに合わせて書き換えてください。実際には使える機能を「使えなくなる」と書くと、事実と異なる文面になります。公開中のメッセージを直すときは新しい版を作ります。審査の間は承認済みの版が表示され続け、承認されると置き換わります。置き換わったあとは、前に公開していた版へ戻せません。

INSTALLSレポートは11月4日で新規生成が終了した

4.6のもう一つの変更は、Sales and Trendsの INSTALLS レポートの廃止です。Appleの文書によると、11月4日以降は新しいレポートが作られません。過去の分はダウンロードでき、代わりのレポートはないと明記されています。

仕様を比べると、filter[reportType] の選択肢には4.6でも INSTALLS が残り、廃止の印(deprecated)も付いていません。仕様から型を作るクライアントでは、コンパイルの時点で気づきにくいと考えられます。新しい期間を指定したときに空の結果とエラーのどちらが返るかは文書に書かれておらず、今回は送っていません。レポートを定期的に取得している場合は、スクリプトと設定を検索して、使っている箇所を確かめます。

# レポートを取得するリポジトリで、INSTALLS を指定している箇所を探す
grep -rn 'INSTALLS' --include='*.sh' --include='*.py' --include='*.rb' \
  --include='*.yml' --include='*.yaml' --include='*.ts' --include='*.js' \
  --include='*.swift' .

見つけた処理は、取得の対象を最後に取得できたレポートの期間までにします。Appleの文書が載せる INSTALLS の頻度は月次と年次です。11月4日はレポートの生成が止まった日で、どの期間のレポートまで作られたかは書かれていません。集計や社内の報告では、最後のレポートより後の期間を0件と分けて、データなしとして扱います。

公開前にSandboxで解約の流れを確かめる

Appleのセッションによると、リテンションメッセージはSandboxで解約を試して確かめられます。ヘルプによると、Sandboxのテスターには公開前の版を含む最新の版が表示されます。割引を設定したら、Sandboxで実際に割引を受け、署名付きのトランザクションや更新情報で offerType が 5 になり、offerIdentifier に手順4で指定した識別子が入ることを確かめます。

この記事で手元で確かめたのは、仕様の差分とリクエスト本文の形までです。App Store Connectへの送信とSandboxでの解約は実際のアプリの設定を変えるので、自分のアプリで次の順に確かめます。

導入の順番

1. 解約で使えなくなる機能を、割引や価格に触れない見出しと本文にする

2. ヘルプに沿ってApp Store Connectの画面でメッセージを作り、サブスクリプションに結び付けて、プレビューとSandboxで確かめる

3. 承認済みの版には言語を足せないので、提供するすべての地域の言語を足してから審査へ出す

4. 複数のアプリや言語を管理するようになったら、APIでの設定へ移す