本記事は生成AIと共同で執筆しています。事実関係は可能な範囲で公式ドキュメント等と照合していますが、誤りが含まれている可能性があります。重要な判断を行う前にご自身でも一次情報をご確認ください。

macOS アプリを Mac App Store に提出する で、版面画像を OCR して TEI/XML を生成する macOS アプリ TEI Scanner を Mac App Store に提出し、審査を通過させたところまでを書きました。提出は2026年5月10日、4日後に「審査が完了し、配信可能になった」旨の通知が届いています。

ところが、です。審査通過の通知が来たあと App Store Connect を開くと、バージョン 1.0 のページにこんな案内が出ていました。

このアプリは App Store の配信から削除されました。価格および配信状況に移動して、App Store に再度追加してください。

審査通過後のバージョン 1.0 ページに表示された「配信から削除されました」の警告

審査は通っている。バージョンの状態は「配信準備完了」。なのに「配信から削除されました」。本記事は、この食い違いを App Store Connect API で切り分け、原因だった配信地域(App Availability)の未設定を API から後追いで直すまでの記録です。

配信地域設定 API(appAvailabilities)そのものの基本的な使い方は App Store Connect API だけで iOS アプリの更新を提出する で扱っています。本記事は重複を避け、「審査を通過したのに配信されない」という症状の切り分けと、その過程で踏んだエラーに焦点を当てます。

「審査ステータス」と「配信」は別物

最初に整理しておくと、App Store でアプリが実際にユーザーへ届くまでには、独立した3つの軸があります。混同していると今回のような事象でハマります。

何を表すか今回の状態
バージョンの審査ステータス(appStoreState審査が通ったか通過済み(配信準備完了)
公開タイミング(releaseType通過後に自動公開か手動リリースか設定済み
配信地域(App Availability)どの国・地域で買えるか未設定(0か所)

前回の記事の末尾でも「審査通過」と「App Store での公開」は別段階だと書きましたが、そこで触れたのは2軸目(releaseType)の話でした。今回詰まったのは3軸目です。配信地域がゼロのままだと、審査をいくら通しても買える国が存在しないので、App Store には並びません。App Store Connect の UI 上は「App Store の配信から削除されました」という案内文で出ます(これは正式なステータス名ではなく、UI 上の案内表示です)。

重要なのは、配信地域は審査提出物(reviewSubmission)に含まれていないということです。reviewSubmissionItems に紐付けるのはバージョンであって、配信地域は別リソース。つまり配信地域が空でも審査は提出でき、通過もします。Web UI からアプリを新規作成すると通常は初期設定で地域選択を促されますが、今回のように Bundle ID 登録・メタデータ投入・ビルド添付・審査提出まで API 中心で進めると、appAvailabilities を明示的に呼ばない限り配信地域はどこにも設定されません。これが今回の落とし穴でした。

API で症状を切り分ける

Web UI の文言だけでは「審査で落とされたのか」「設定漏れなのか」が判然としません。App Store Connect API で配信地域の状態を直接見にいきます。

配信地域は、アプリリソースの appAvailabilityV2 リレーションから取得できます。

# _asc.request は https://api.appstoreconnect.apple.com/v1/ にリクエストを送る既存ヘルパ
status, data = request("GET", f"apps/{app_id}/appAvailabilityV2",
                       raise_on_error=False)
print(status, data)

返ってきたのはこれでした。

404 {
  "errors": [{
    "status": "404",
    "code": "NOT_FOUND",
    "title": "The specified resource does not exist",
    "detail": "There is no resource of type 'appAvailabilities' with id '...'"
  }]
}

NOT_FOUND です。PATH_ERROR(URL の綴り間違い)ではなく、appAvailabilities リソースそのものが存在しない。配信地域が一度も作られていない、と確定できました。審査落ちでも設定ミスでもなく、単純な設定漏れだったわけです。

ちなみに appAvailabilityV2 ではなく appAvailability を引くと、そのリレーションが存在せず別のエラーになります。

404 {"errors": [{"code": "PATH_ERROR",
  "detail": "The relationship 'appAvailability' does not exist"}]}

NOT_FOUND(リソースが無い)と PATH_ERROR(パスが無い)は区別がつくので、切り分けの手がかりになります。

配信地域を後追いで作る

原因がわかれば、あとは appAvailabilities を作るだけです。手順自体は前掲の iOS 更新記事と同じで、

  1. GET /v1/territories?limit=200 で利用可能な国・地域(執筆時点で175か所)を取得
  2. POST /v2/appAvailabilities で全地域を available: true として登録

ですが、ここで2回エラーを踏んだので、その2点を書き残しておきます。

エラー1: エンドポイントは v2 にある

最初、既存の API ヘルパ(ベース URL が /v1/)でそのまま POST したら、こうなりました。

404 {"errors": [{"code": "PATH_ERROR",
  "detail": "The resource 'v1/appAvailabilities' does not exist"}]}

appAvailabilities の作成は /v2/ エンドポイントにしかありません。GET apps/{id}/appAvailabilityV2(v1 のリレーション経由の参照)は通るのに、作成は v2、という非対称があります。v1 用に作ったヘルパとは別に、v2 用のリクエスト関数を用意しました。

def request_v2(method, path, body=None):
    """App Availability は /v2/ API。_asc.request は /v1/ 固定なので別経路。"""
    url = f"https://api.appstoreconnect.apple.com/v2/{path}"
    data = json.dumps(body).encode() if body else None
    req = urllib.request.Request(
        url, data=data, method=method,
        headers={
            "Authorization": f"Bearer {token()}",  # 既存ヘルパの JWT 生成を流用
            "Content-Type": "application/json",
        },
    )
    ...

JWT(JSON Web Token)の組み立てやキーの読み込みは v1 と共通なので、token() だけ既存ヘルパから流用し、ベース URL を差し替えるだけで済みます。

エラー2: included の id は ${local-id} 形式

次に踏んだのがこれです。175地域分の territoryAvailabilitiesincluded に並べ、idJPNUSA といった地域コードをそのまま入れて POST したところ、全件 409 で弾かれました。

409 {"errors": [{"code": "ENTITY_ERROR.INCLUDED.INVALID_ID",
  "detail": "The provided included entity id 'AFG' has invalid format.
             For inline creation, the id must be a local id
             with the format '${local-id}'.",
  "source": {"pointer": "/included/0/id"}}]}

appAvailabilities の作成は、配信地域ごとの territoryAvailabilitiesincluded に同梱して一括で新規作成(inline creation)する形をとります。このとき included の各要素の id は、まだ存在しないリソースを指すローカル ID であって、${...} で囲んだプレースホルダにしなければなりません。地域の実コードは、その中の relationships.territory 側に書きます。

"included": [
    {
        "type": "territoryAvailabilities",
        "id": "${%s}" % t,                       # ← ローカル ID(プレースホルダ)
        "attributes": {"available": True},
        "relationships": {
            "territory": {
                "data": {"type": "territories", "id": t}   # ← 実コード(JPN 等)
            }
        },
    }
    for t in territories
]

そして data.relationships.territoryAvailabilities.data 側も、実コードではなく同じローカル ID(${JPN} など)で参照します。dataincluded がローカル ID で結びつき、サーバ側で一括生成される、というモデルです。included に実 ID を入れて 409 になるのは、JSON:API の inline creation を初めて使うとまず引っかかるところだと思います。

修正後の POST は通り、配信地域が作成されました。

created app availability: id=...
app availability exists: availableInNewTerritories=True
territories available: 175

availableInNewTerritories=True にしておくと、今後 Apple が新しい App Store 地域を追加したとき、その地域でも自動的に配信対象になります。Web UI の既定挙動と同じです。

冪等にしておく — 既存検出でスキップ

配信地域は一度作れば残るので、スクリプトは「既に存在するなら何もしない」ようにしておきます。診断に使った appAvailabilityV2 の GET をそのまま再利用できます。

status, data = request("GET", f"apps/{app_id}/appAvailabilityV2",
                       raise_on_error=False)
if status == 200:
    print("配信地域は設定済み — 何もしない")
elif status == 404:
    create_availability(app_id, list_territories(), available_in_new=True)

404 なら未設定なので作成、200 なら作成済みなのでスキップ。メタデータ投入スクリプトを冪等に書いておいたのと同じ方針で、配信地域スクリプトも何度流しても安全になります。

検証

作成後、再度 appAvailabilityV2 を引いて、配信地域が意図どおり並んでいるか確認します。territoryAvailabilities のサブリソースを取れば、available: true の件数を数えられます。

status, data = request_v2(
    "GET",
    f"appAvailabilities/{app_id}/territoryAvailabilities"
    f"?limit=200&fields[territoryAvailabilities]=available")
avail = [t for t in data["data"] if t["attributes"]["available"]]
print(f"available: {len(avail)}")   # -> available: 175

175件すべてが available になっていることを確認できました。この変化は、App Store Connect の「価格および配信状況」ページの「アプリの配信状況」セクションにもそのまま現れます。

設定前設定後
配信地域が未設定で「配信状況の設定」ボタンだけが出ている状態配信可能な国・地域が175個と表示された状態

設定前は「アプリの配信状況」セクションに「配信状況の設定」ボタンしかなく、配信地域がゼロであることがそのまま空欄として現れています。appAvailabilities を作成したあとは「配信可能: 175個」と表示されます。

あわせて、バージョン 1.0 のページからも冒頭の警告が消えます。

配信地域の設定後、バージョン 1.0 ページから「配信から削除されました」の警告が消えた状態

なお、ここで反映されているのは App Store Connect 上のデータです。公開ストアのカタログに実際に並ぶまでは、Apple 側の反映に時間がかかる場合があります。

まとめ

  • App Store でアプリが届くまでには「審査ステータス」「公開タイミング(releaseType)」「配信地域(App Availability)」という独立した3軸がある。配信地域は審査提出物に含まれないため、ゼロのまま審査を通過できてしまう
  • API 中心で提出フローを組むと、appAvailabilities を明示的に呼ばない限り配信地域は設定されない。Web UI 新規作成時のような地域選択の強制がない。
  • 症状の切り分けは GET apps/{id}/appAvailabilityV2NOT_FOUND なら未設定、PATH_ERROR ならパスの綴り違い、と区別できる。
  • 配信地域の作成/v2/appAvailabilities。参照(appAvailabilityV2 リレーション)は v1 から引けるのに作成は v2、という非対称に注意。
  • included を使った inline creation では、id${local-id} 形式のプレースホルダにする。実コードは relationships.territory 側に置く。

審査を通すところまでは前回の記事で「できた」と書きましたが、実際にユーザーが App Store で見つけられる状態になったのは、この配信地域を後追いで設定したあとでした。「審査通過 = 公開」ではない、というのを文字どおり踏み抜いた一件です。

参考