モバイルアプリ向けAPIバージョニング:エンドポイントを安全に進化させる
モバイルアプリ向けAPIバージョニングを分かりやすく解説。段階的ロールアウト、後方互換の変更方法、廃止手順を示し、古いアプリ版が動き続けるようにする実践的なガイドです。

なぜAPIの変更がモバイルユーザーを壊すのか
モバイルアプリは一斉に更新されません。修正を今日リリースしても、かなりの人数が古いバージョンを数日や数週間使い続けます。自動更新を切っている人、ストレージが足りない人、単にアプリを開かない人もいます。アプリストアの審査や段階的リリースがさらに遅延を生みます。
このギャップは重要で、なぜならバックエンドは通常モバイルクライアントより速く変わるからです。サーバーがエンドポイントを変えて古いアプリがそれを呼ぶと、ユーザーの端末では何も変えていなくてもアプリが壊れることがあります。
壊れ方はきれいなエラーメッセージで出ることは稀で、日常的なプロダクトの不具合として現れることが多いです:
- バックエンドのリリース後にログインやサインアップが失敗する
- フィールド名が変わったり移動したため一覧が空に見える
- 欠けた値を読んでアプリがクラッシュする
- バリデーションが厳しくなり決済が通らない
- レスポンス形状の変化で機能がひっそり消える
バージョニングの目的は単純です:全員に即時アップデートを強制せずにサーバーの改善を続けられるようにすること。APIを長期にわたる契約のように扱ってください。新しいアプリは新しいサーバー挙動で動き、古いバージョンは現実の更新サイクルが追いつくまで動き続けるべきです。
ほとんどの消費者向けアプリでは、同時に複数のアプリバージョンをサポートすることを見込んでください。社内向けアプリは速く移行できることもありますが、それでも即時移行は稀です。重なりを想定する計画があれば、段階的なロールアウトが落ち着いて進み、毎回のバックエンドリリースがサポート地獄になりません。
API契約で「互換性」が意味すること
API契約はモバイルアプリとサーバーの約束事です:呼ぶURL、受け付ける入力、レスポンスの形、各フィールドの意味。アプリがその約束に依存していて、サーバーがそれを変えると、ユーザーはクラッシュやデータ欠落、機能停止として感じます。
古いアプリがコード変更なしにAPIを使い続けられるなら、その変更は互換的です。実務では、サーバーが古いアプリが送るものを理解し、古いアプリが解析できるレスポンスを返し続けることを意味します。
安全な変更とリスクのある変更を分ける簡単な指標:
- 破壊的変更:フィールドの削除や改名、型の変更(数値→文字列など)、任意フィールドを必須にする、エラーフォーマットを変える、古いアプリが満たせないようなバリデーションの厳格化。
- 通常は安全な変更:新しい任意フィールドの追加、新しいエンドポイントの追加、旧/新のリクエスト形式の両方を受け入れる、新しい列挙値の追加(アプリが未定義値を「その他」として扱う場合のみ)。
互換性には廃止計画も必要です。古い挙動を退役させるのは構いませんが、スケジュールを決めておくべきです(例:「v2公開後90日間はv1を維持」など)。そうすることでユーザーを驚かせずに進化できます。
よくあるバージョニング手法とトレードオフ
バージョニングは古いビルドに安定した契約を与えつつ前へ進むためのものです。いくつか一般的な方法があり、それぞれどこに複雑さを置くかが違います。
URLバージョニング
パスにバージョンを入れる方法(例: /v1/ と /v2/)は最も見やすくデバッグしやすいです。キャッシュ、ログ、ルーティングとも相性が良いメリットがあります。欠点は、差分が小さくてもチームが並行処理を長く維持してしまう傾向がある点です。
ヘッダーベースのバージョニング
クライアントがヘッダー(Acceptヘッダーやカスタムヘッダー)でバージョンを送る方式です。URLがきれいに保たれ、全てのパスを変える必要がない利点があります。欠点は可視性で、プロキシやログ、担当者がバージョンを見落とすことがあり、モバイルクライアントが毎回ヘッダーを確実に設定する必要があります。
クエリパラメータによるバージョニング
?v=2 のようなパラメータは簡単に見えますが、ブックマークや分析ツール、スクリプトにコピーされやすく、複数の「バージョン」が浮遊して管理が曖昧になることがあります。
簡単な比較:
- URLバージョニング: 検査が容易だが、並列APIが長く残りやすい
- ヘッダーバージョニング: URLがクリーンだがトラブルシュートで見落としやすい
- クエリバージョ닝: すぐ始められるが誤用されやすい
機能フラグは別の道具です。契約(リクエスト/レスポンスの形)を変えずに挙動を切り替えられます(例: 新しいランキングアルゴリズム)。ただし、リクエストやレスポンス形状を変える必要がある場合はバージョニングに代替できません。
重要なのは一つの方式を選んで徹底することです。一貫性は「完璧な選択」よりも重要です。
後方互換な変更の経験則
最も安全な考え方は:古いクライアントは新機能を知らなくても動き続けるべき、です。つまり既存のものを変えるより追加することを優先します。
追加的な変更を好みましょう:新しいフィールド、新しいエンドポイント、新しい任意パラメータ。追加時はサーバー側で本当に任意扱いにしてください。古いアプリが送らなくてもサーバーは以前と同じように振る舞うべきです。
破損を防ぐ習慣:
- フィールドを追加しても既存フィールドの型や意味を変えない
- 欠けた入力を通常のケースとして扱い、妥当なデフォルトを使う
- 未知のリクエストフィールドは無視して、古い/新しいクライアントが共存できるようにする
- エラーフォーマットは安定させる。どうしても変えるならエラーペイロードにバージョンを付ける
- 挙動を変える必要があるなら新しいエンドポイントやバージョンを導入し、サイレントな変更は避ける
既存フィールドの意味を変えるのはバージョンを上げるまで避けてください。例:status=1 が以前は「paid」を意味していて、それを「authorized」に再定義すると、古いアプリは誤った判断をしてしまいます。
改名や削除には計画が必要です。安全なパターンは古いフィールドを残して新しいフィールドを並列で追加することです。レスポンスでは両方を埋め、リクエストでは両方を受け入れ、誰が古いフィールドを使っているかログで追跡し、廃止ウィンドウが終わったら古いフィールドを削除します。
小さく強力な習慣:新しい必須ビジネスルールを導入するとき、初日からクライアントに責任を負わせないでください。最初はサーバーでデフォルトを適用し、後でほとんどのユーザーが更新したらクライアント側に値を送らせるようにします。
単純なバージョニングと廃止ポリシーを決める
ルールは退屈で書かれているほど運用されます。ポリシーは短くして、プロダクト・モバイル・バックエンドチームが実際に従えるようにしてください。
まずサポート期間を決めます。新バージョン公開後、どれくらい古いAPIを維持するか(例:6〜12か月)、例外(セキュリティ問題や法的変更)なども定めます。
次にクライアントに破壊的変更を知らせる方法を一つに統一します。よく使われるのはレスポンスヘッダーに Deprecation: true と廃止日を入れる方法、あるいは特定レスポンスに "deprecation": {"will_stop_working_on": "2026-04-01"} のようなJSONフィールドを入れる方法です。重要なのは一貫性:クライアントが検出でき、ダッシュボードで報告でき、サポートが説明できること。
最低サポートアプリバージョンを設定し、施行方法を明確にしてください。驚きのハードブロックは避けます。現実的な方法の例:
- ソフトな警告を返す(アプリ内更新促進のためのフィールドなど)
- 告知した期限を過ぎてから強制する
もしリクエストをブロックするなら、人間向けメッセージと機械判別可能なコードを含む明確なエラーペイロードを返してください。
最後に、誰が破壊的変更を承認するか、どんなドキュメントが必要かを決めます。シンプルに:
- 破壊的変更は1人のオーナーが承認する
- 変更内容、影響範囲、移行パスを説明する短いノートを残す
- テストプランには少なくとも1つ古いアプリバージョンを含める
- 廃止の開始時に退役日を設定する
古いアプリを動かし続けるための段階的ロールアウト手順
モバイルユーザーは初日で全員更新するわけではないため、安全な方法は古いAPIを触らずに新しいAPIを出し、徐々にトラフィックを移すことです。
まず、v2 で何が変わるか定義し、v1 の振る舞いをロックします。v1 は約束として扱ってください:フィールド、意味、エラーコードを同じに保ちます。v2 が別のレスポンス形を必要とするなら、v1 をいじって合わせようとしないでください。
次に v2 を並列で動かします。これは別ルート(例: /v1/... と /v2/...)にするか、同じゲートウェイの裏で別ハンドラを用意する形でも良いです。共通ロジックは一か所にしても、契約は分けておいてください。そうしないと v2 のリファクタで v1 が影響を受けることがあります。
その後、モバイルアプリを更新して v2 を優先させます。シンプルなフォールバックを入れてください:v2 が "not supported"(または既知のエラー)を返したら v1 を再試行する、という具合です。段階的リリースや実ネットワークの乱れに有効です。
リリース後は採用率とエラーを監視します。便利なチェック項目は:
- アプリバージョン別の v1 と v2 のリクエスト量
- v2 のエラー率とレイテンシ
- レスポンス解析エラーの発生
- ネットワークリンク画面に結び付いたクラッシュ
v2 が安定したら v1 に明確な廃止警告を出してスケジュールを伝えます。v1 は使用量が許容閾値(例:数週間にわたり1〜2%未満)を下回ってから退役させます。
例:GET /orders にフィルタや新ステータスを追加する場合。v2 は status_details を追加し、v1 はそのままです。新しいアプリは v2 を呼びますが、エッジケースがあると v1 にフォールバックして一覧を表示し続けます。
サーバ側の実装に関するヒント
ほとんどのロールアウト破綻はバージョン処理がコントローラやヘルパー、DBコードに散らばっていることが原因です。“このリクエストはどのバージョンか”の判定を一か所にまとめ、残りのロジックは予測可能に保ってください。
バージョンルーティングを一箇所にまとめる
一つの信号(URLセグメント、ヘッダー、アプリのビルド番号)を選び、早い段階で正規化します。どのリクエストも同じ流れを通るように、1つのモジュールやミドルウェアでハンドラへ振り分けます。
実用的なパターン:
- バージョンを一度だけ解析してログに残す
- バージョンからハンドラを一つのレジストリでマップする(v1, v2...)
- 共通ユーティリティはバージョンに依存しない(日付解析や認証チェックなど)ようにし、レスポンス形状ロジックは分ける
バージョン間でコードを共有する際は注意してください。"共有"コードで v2 のバグを直すと、意図せず v1 の挙動が変わることがあります。出力フィールドやバリデーションに影響するロジックはバージョンごとに分けるか、バージョン特有のテストでカバーしてください。
ロールアウト中はデータの互換性を保つ
DBマイグレーションは両方のバージョンで動くように設計します。まずカラムを追加し、必要ならバックフィルし、後で制約を厳しくするか古いフィールドを削除します。ロールアウト途中で形式を切り替える必要があるなら、短期間両形式を書き続けることを検討してください。
エラーは予測可能に保ちます。古いアプリは未知のエラーを "何かがおかしい" と扱いがちです。ステータスコード、安定したエラー識別子、短く分かりやすいメッセージでクライアントが再試行/再認証/更新促進を判断できるようにします。
最後に、古いアプリが送らないフィールドに対しては安全なデフォルトを使い、明確で安定したエラー詳細で検証してください。
バージョニングに影響するモバイル側の注意点
ユーザーが数週間古いビルドを使い続ける可能性があるため、複数クライアントバージョンが同時にサーバーにアクセスすることを前提に設計します。
クライアント側の寛容さは大きな効果を発揮します。サーバーがフィールドを追加したときにアプリがクラッシュしたりパースに失敗すると、ロールアウトでランダムな不具合が発生します。
- 未知の JSON フィールドは無視する
- 欠けたフィールドを通常ケースとして安全なデフォルトで扱う
- マイグレーション中は null を安全に扱う
- 配列の順序に依存しない(契約で保証されない限り)
- エラーハンドリングはユーザーフレンドリーにする(再試行状態を表示する方が空白画面より良い)
ネットワーク挙動も重要です。ロールアウト中はロードバランサやキャッシュの裏で混在したサーバーバージョンが一時的に出ることがあり、モバイルネットワークは小さな問題を増幅します。
明確なタイムアウトと再試行ルールを決めてください:取得系は短めのタイムアウト、アップロードはやや長め、再試行はバックオフ付きで回数を限定します。決済や作成系は冪等性を標準にして、再試行で二重送信にならないようにします。
認証の変更は古いアプリを素早くロックアウトしてしまいます。トークン形式、必要スコープ、セッションルールを変える場合は、旧トークンと新トークンが両方使えるオーバーラップ期間を用意してください。鍵やクレームを回す必要があるなら、一日で切り替えるのではなく段階的な移行を計画します。
リクエストごとにアプリメタデータ(例: アプリバージョンやプラットフォーム)を送らせると、ターゲットを絞った警告を返せて大規模なAPI分岐を避けられます。
監視と段階的ロールアウトでの不意打ちを防ぐ
段階的ロールアウトは、どのアプリバージョンがどんな動きをしているかを見られることが前提です。目標は単純:古いエンドポイントを誰が使っているかを把握し、問題が広がる前に捕まえること。
まず日次でAPIバージョンごとの利用状況を追跡します。総リクエスト数だけでなくアクティブデバイス数やログイン・プロフィール・決済といった主要エンドポイント別の内訳も見てください。これで古いバージョンが「まだ生きている」かが分かります。
次にバージョン別・タイプ別のエラーを監視します。4xx の急増は契約不一致(必須フィールド変更、列挙値シフト、認証ルールの強化)を示すことが多く、5xx の急増はサーバーの回帰(デプロイミス、遅いクエリ、依存障害)を示します。バージョン別に見ると迅速に適切な対処を選べます。
アプリストアの段階的リリースを活用して被害範囲を限定し、5%→25%→50% と公開を広げながら各ステップのダッシュボードを監視します。新バージョンに問題が出たら、全面公開の前にロールアウトを停止します。
ロールバックのトリガーは事前に書き出しておき、インシデント時に判断を迫られないようにしてください。一般的なトリガーの例:
- 15〜30分でエラー率が閾値を超える
- ログイン成功率が下がる(トークン更新失敗が増える)
- 決済失敗が増える、またはチェックアウトのタイムアウトが増える
- 特定バージョンに紐づくサポートチケットが急増する
- 重要エンドポイントのレイテンシが上がる
バージョン関連障害の短いプレイブックも用意しておきます:誰にページするか、危険なフラグを無効にする方法、どのサーバーリリースにロールバックするか、古いクライアントがまだ残っている場合の廃止延長方法など。
例:実際のリリースでエンドポイントを進化させる
チェックアウトは実務でよくある変更例です。最初はシンプルなフローで、後に強化された支払いステップを追加し、フィールド名をビジネス用語に合わせて変更したいことがあります。
モバイルアプリが POST /checkout を呼ぶとします。
v1 に残すもの vs v2 で変えるもの
v1 では既存のリクエストと挙動を保ち、古いアプリが支払いを完了できるようにします。v2 では新しいフローとより明確な名前を導入します。
- v1 に残す:
amount,currency,card_token、status=paid|failedのような単一レスポンス - v2 で追加:
payment_method_id(card_tokenの代替)と、アプリが追加ステップ(verify, retry, redirect)に対応できるようにするnext_actionフィールド - v2 で改名:
amountをtotal_amount、currencyをbilling_currencyに変更
古いアプリが動き続けるのはサーバーが安全なデフォルトを適用するためです。v1 のリクエストが next_action を知らなくても、サーバーは可能なら支払いを完了して従来どおりのv1スタイルの結果を返します。新しいステップが必須の場合、v1 には requires_update のような明確で安定したエラーコードを返し、意味不明な汎用エラーは避けます。
採用、退役、ロールバック
バージョン別の採用率(checkout 呼び出しの v2 割合)、エラー率、まだ v1 のみをサポートするビルドの割合を追跡します。v2 の利用が数週間にわたり一貫して高く(例:95%以上)なり、v1 の利用が低いなら v1 の退役日を決めて告知します(リリースノート、アプリ内メッセージなど)。
リリース後に問題が発生したらロールバックは平常運転にします:
- v1 振る舞いへトラフィックを戻す
- サーバー側フラグで新しい支払いステップを無効化する
- 両方のフィールドセットを受け入れ続け、自動変換した内容をログに残す
サイレントな壊れ方を引き起こす一般的なミス
多くのモバイルAPI障害は派手ではありません。リクエストは成功し、アプリは動き続けるが、ユーザーはデータ欠落、誤った合計、ボタンが何もしないといった症状を体験します。これらは段階的ロールアウト中に古いアプリにだけ起きるため発見が遅れがちです。
よくある原因:
- フィールドの変更や削除、型変更を明確なバージョンプランなしに行う
- 新しいリクエストフィールドを即座に必須にする
- 新しいアプリのみ存在することを前提としたデータベースマイグレーションを流す
- インストール数で v1 を退役判断してしまい、アクティブ利用を見落とす
- まだ古いペイロードを送るバックグラウンドジョブやWebhookを忘れる
具体例:レスポンスフィールド total が文字列 ("12.50") で、これを数値 (12.5) に変えると、新しいアプリは問題ない一方で古いアプリがゼロ扱いにしたり隠したり、ある画面でのみクラッシュすることがあります。アプリバージョン別のクライアントエラーを監視していなければ見逃します。
クイックチェックリストと次のステップ
バージョニングは凝ったエンドポイント命名より、毎回同じ安全チェックを繰り返すことが重要です。
リリース前の簡単チェック
- 変更は可能な限り追加的にする。古いアプリが既に読んでいるフィールドを削除・改名しない。
- 新しいフィールドが欠けていても古いフローと同じ振る舞いになる安全なデフォルトを用意する。
- エラーレスポンスは安定させる(ステータス+形+意味)。
- 列挙値は慎重に扱い、既存値の意味を変えない。
- 古いアプリからの実際のリクエストをいくつか再生して、レスポンスが引き続きパースできることを確認する。
ロールアウト中および退役前の簡単チェック
- アプリバージョン別の採用状況を追跡する。v1 から v2 への明確な移行曲線が欲しい。
- バージョン別のエラー率を監視する。スパイクは古いクライアントのパースや検証の破綻を示すことが多い。
- 最も失敗しているエンドポイントを優先的に修正し、それからロールアウトを広げる。
- アクティブ利用が本当に低いと判断できるまで退役しない。退役日はきちんと告知する。
- フォールバックコードは最後に削除する(退役ウィンドウの後)。
バージョニングと廃止ポリシーを一ページにまとめ、そのチェックリストをチームのリリースゲートにしてください。
内部ツールやノーコードの顧客向けアプリでも、APIを契約として扱い明確な廃止ウィンドウを設けると役立ちます。AppMaster (appmaster.io) を使うチームでは、v1 と v2 を並べて運用するのが容易なことが多く、バックエンドやクライアントを再生成しつつ古い契約を保ちながらロールアウトできます。
よくある質問
モバイルユーザーは一斉に更新しないため、古いビルドがサーバーへリクエストを送り続けます。エンドポイントやバリデーション、レスポンスの形を変えると、古いビルドは対応できず、空白画面、クラッシュ、決済失敗などの形で問題が現れます。
「互換性がある」とは、古いアプリが同じリクエストを送り続けても、解析・利用できるレスポンスを受け取れることです。最も安全な考え方はAPIを契約と見なすことで、新機能は追加できても既存のフィールドや挙動の意味を変えてはいけません。
既存のアプリが依存している要素を変更すると破壊的です。典型はフィールドの削除や改名、型の変更、バリデーションの厳格化、エラーペイロードの変更などです。古いアプリがレスポンスを解析できなかったり、リクエストルールを満たせなければ、それは破壊的変更です。
デフォルトでは URL バージョニングが扱いやすいことが多いです。ログやデバッグでバージョンが目に見え、ルーティングやキャッシュと相性が良いからです。ヘッダーバージョンはURLがきれいですが、トラブルシュートで見落とされやすく、クライアントが毎回正しくヘッダーを送る必要があります。
実際のモバイルの更新挙動に合わせたサポート期間を決め、それを守ることです。多くのチームは日単位ではなく月単位(数か月)で設定します。重要なのは公開された廃止日があり、アクティブ利用状況を計測して安全にオフにすることです。
一貫した廃止シグナルを使ってクライアントとダッシュボードが検出できるようにします。よくある方法はレスポンスヘッダー(例: Deprecation: true と廃止日)や、特定のレスポンスフィールド(例: "deprecation": {"will_stop_working_on": "2026-04-01"})です。シンプルで予測可能であることが大事です。
付加的な変更を優先します。新しい任意フィールドや新しいエンドポイントを追加し、旧フィールドはしばらく並存させるのが安全です。改名が必要なら新旧両方のフィールドをしばらく併存させ、両方に値を入れて誰が古いフィールドを使っているかログで追跡します。
v1 と v2 が同時に動作することを前提にマイグレーションを設計します。まずカラムを追加し、必要ならバックフィルし、後で制約を厳しくしたり古いフィールドを削除します。ロールアウトの途中で意味を変えたりリネームするのは避けてください。
クライアント側で耐性を持たせることが大きな効果を生みます。未知の JSON フィールドは無視し、欠如したフィールドは安全なデフォルトで扱い、null を安全に処理するようにします。これでサーバーが一時的に変化しても「ランダムな」ロールアウト不具合を減らせます。
ロールアウト中はAPIバージョンごとの利用状況とエラーを必ず追跡します。ログイン・決済など重要エンドポイントをバージョン別に監視し、v2 の採用率が十分に高まるまで v1 を維持します。採用率が高く、アクティブ利用が低くなったら退役日を決めて告知します。


