API バージョニング (詳解)

API の破壊的変更を管理する戦略の詳細な実装パターン

API設計

API バージョニング詳解

API の版管理で決めることは 2 つある。1 つは何を版として外に見せるか、つまり URL とヘッダーとクエリのどこに版を置くかという表現の問題、もう 1 つは古い版をいつ止めるかという運用の問題である。表現だけ決めて廃止の段取りを決めないと v1 が永久に残り、同じ機能を 2 系統で保守し続けることになる。

セマンティックバージョニングとの区別

セマンティックバージョニング は MAJOR.MINOR.PATCH でライブラリの版を表す規約で、HTTP API の版とは粒度が違う。API 側で外に見せるのはふつう MAJOR に相当する区切りだけで、互換な追加や修正は版を上げずに反映する。3 つの数字をそのまま URL に入れると、互換な修正のたびに呼び出し元が URL を書き換える必要が生じる。

混ざりやすい版はもう 2 つある。1 つは OpenAPI の info.version で、これは API 記述文書そのものの版であり、文書が準拠する仕様の版 (openapi フィールド) とも API 実装の版とも別だと仕様に明記されている。もう 1 つは SDK の版で、Stripe の各言語ライブラリはメジャー版ごとにリリース時点の API 版を固定する設計になっている (2026 年 8 月時点)。SDK を上げると API の版まで動くため、SDK の semver を API の版と同一視すると想定外の非互換を踏む。

非破壊的変更の活用

版を切るかどうかは「既存の呼び出し元が壊れるか」だけで決まる。フィールドの追加、任意パラメーターの追加、エラーメッセージの文言変更は壊れない側。フィールドの削除と改名、型の変更、任意から必須への変更、既定値の変更、エラーコードの意味の変更は壊れる側になる。

// v1 のレスポンス
{ "id": "123", "name": "Alice" }

// フィールド追加 (非破壊的): バージョンアップ不要
{ "id": "123", "name": "Alice", "email": "alice@example.com" }

// 既存クライアントは email を無視するだけ

ただし最後の 1 行は「クライアントが知らないフィールドを無視する」ことに依存している。生成コードのデシリアライザーが厳格な設定になっていると、JSON Schema の additionalProperties: false に相当する検証でフィールドの追加だけで失敗する。列挙値の追加も同種で、受け取り側が値を網羅一致で分岐していれば新しい値で落ちる。追加が原理的に非破壊なのではなく、呼び出し元の受け取り方まで含めて非破壊かどうかが決まる。

非推奨の告知に使うヘッダー

非推奨と廃止予定日は HTTP ヘッダーで機械可読に伝えられる。ただし 2 つのヘッダーは別々の RFC で決まっており、日付の書式まで違う。

  • Deprecation は RFC 9745 (2025 年 3 月 / 標準化過程) が定義する。値は構造化フィールドの Date 型でなければならず、@ に続く Unix 秒で書く。true のような真偽値は標準化前のドラフトで使われた形で、現行仕様には合わない。
  • Sunset は RFC 8594 (2019 年 5 月 / 情報提供) が定義する。値は HTTP-date で、曜日と月名を含む形式になる。RFC 9745 は、歴史的な理由で Sunset が異なる日付書式を使うと明記している。
// 旧バージョンに非推奨ヘッダーを付けて返す
if (version === '1') {
  return {
    statusCode: 200,
    headers: {
      // RFC 9745: 構造化フィールドの Date 型 (Unix 秒)
      'Deprecation': '@1798761599',
      // RFC 8594: HTTP-date
      'Sunset': 'Thu, 31 Dec 2026 23:59:59 GMT',
      'Link': '</v2/users>; rel="successor-version"',
    },
    body: JSON.stringify(v1Response),
  };
}

rel="successor-version" は RFC 5829 で登録されたリンク関係で、原義はバージョン履歴上の後続バージョンを指すものである。API の次の版を案内する用途は転用にあたるので、移行手順を説明したページへ導きたい場合は RFC 9745 が定義する rel="deprecation" の方が意図に合う。

いずれのヘッダーもヒントであり、RFC 8594 はクライアントがこれに従うことを保証しないと述べている。ヘッダーを出しただけで移行は進まないため、廃止日を決める前に版ごとの呼び出し件数と呼び出し元を計測しておく。

移行のタイムライン

期間の目安は呼び出し元の性質で変わる。社内の数サービスだけなら数週間で足りるが、外部に公開した API では相手の開発計画に載せてもらう時間が必要になり、数か月単位になる。順序として大事なのは、通知より先に計測を置くことである。誰が旧版を呼んでいるか分からないうちは、廃止日を宣言しても影響範囲が読めない。

Month 1: v2 をリリース、v1 に Deprecation / Sunset ヘッダー追加
Month 2: v1 の呼び出し件数と呼び出し元を版ごとに計測
Month 3: 残っている呼び出し元へ個別に移行を通知
Month 6: v1 を廃止 (410 Gone を返す)

廃止後に返すのは 404 ではなく 410 が適切である。RFC 9110 は 410 (Gone) を、対象リソースがオリジンサーバーで利用できなくなり、その状態がおそらく恒久的であることを示す状態コードと定義し、恒久かどうか判断できない場合は 404 を使うべきとしている。版の廃止は意図した恒久的な取り下げなので 410 に当てはまり、受け取った側も再試行ではなく実装の修正が必要だと判断できる。GitHub の REST API も、サポート外の版を指定した要求には 410 を返すと案内している (2026 年 8 月時点)。

GraphQL でのバージョニング

GraphQL はフィールドの追加が非破壊的で、クライアントが必要なフィールドを選択するため、バージョニングが不要になりやすい。@deprecated ディレクティブで非推奨フィールドを示す。

type User {
  id: ID!
  name: String @deprecated(reason: "Use firstName and lastName")
  firstName: String!
  lastName: String!
}

ただし版管理が消えるのではなく、スキーマ全体の版からフィールド単位の非推奨へ置き換わるだけである。@deprecated はスキーマに印を付ける手段でしかなく、そのフィールドを返さなくなる時期は別に決める必要がある。削除の判断にはフィールドごとの参照状況が要るので、クエリを組み立てる側が多いほど「誰も使っていない」の確認が難しくなる。REST で v1 を止められない状況が、GraphQL では特定のフィールドを消せない状況として現れる。

バージョニング戦略の比較

REST API で版を表す場所は主に 4 つある。判断軸は、版がログと画面に見えるか、キャッシュが版ごとに分かれるか、版を省略した要求に何を返すかの 3 点である。

戦略方法メリットデメリット
URL パス/v1/users版がログとブラウザーで見える、キャッシュ鍵が自然に分かれる同じ資源に版ごとの URL ができる
独自ヘッダーX-GitHub-Api-Version: 2026-03-10URL が変わらない、既定版を決められる発見しにくい、Vary の指定が要る
コンテンツネゴシエーションAccept: application/vnd.api.v1+jsonHTTP の仕組みに沿うメディアタイプの設計が煩雑、Vary: Accept が要る
クエリパラメーター?version=1実装が簡単、ブラウザーで試せる省略時の既定が曖昧になりやすい

ヘッダー方式とコンテンツネゴシエーション方式の落とし穴は同じで、同じ URL が版によって違う表現を返す点にある。応答に版を決めるヘッダー名を Vary で示さないと、CDN や中間キャッシュが先に届いた版の応答を別の版の要求へ配ってしまう。URL パス方式にこの問題が無いのは、キャッシュ鍵に版が入っているからである。

日付を版とする方式は実際に使われている。GitHub の REST API は X-GitHub-Api-Version に日付を指定する形で、省略した要求は 2022-11-28 の版として扱われる (2026 年 8 月時点)。Stripe は Stripe-Version ヘッダーで上書きし、指定が無ければアカウントの既定版が使われる。さらに Stripe は月次リリースを後方互換な変更だけに限り、非互換な変更を名前付きのメジャーリリースにまとめている (2026 年 8 月時点)。版の粒度を 2 段にすると、こまめな改善と非互換な整理を同じ番号体系に押し込まずに済む。

省略時の既定を「常に最新」にしない点も両者に共通している。既定が最新へ追従すると、こちらのリリースが呼び出し元を無断で壊す。既定は固定した版にし、新しい版は明示的に指定させる設計が安全である。

基礎から学ぶなら関連書籍が手がかりになる。

共有:

この記事は役に立ちましたか?

関連用語

関連する記事