OpenAPI は、HTTP API を YAML または JSON のテキストファイルで記述するための形式です。API がどのパスを公開しているか、それぞれがどのメソッドを受け付けるか、リクエストにどんなフィールドを載せるか、レスポンスで何が返るかを、そのファイルに書き並べます。人間も機械も読めるため、ドキュメント、クライアントライブラリ、テスト用のモックサーバーを同じ一つの原本から生成できます。

正式名称は OpenAPI Specification、略して OAS です。Linux Foundation 傘下のオープンガバナンスなプロジェクト、OpenAPI Initiative が管理しています。

以下では、ドキュメントの構造、Swagger との違い、3.1 と 3.2 での変更点、そしてこの形式の中心的な約束——ドキュメントが古くならないこと——が、それだけでは果たされない理由を扱います。

OpenAPI ドキュメントの中身

動くファイルは、思われているより短いものです。

openapi: 3.1.0

info:
  title: リンク
  version: "1.0"

paths:
  /links:
    post:
      summary: 短縮リンクを作成する
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url:
                  type: string
                  format: uri
      responses:
        '201':
          description: リンクを作成しました

トップレベルで必須のフィールドは openapiinfo の二つだけで、さらに pathscomponentswebhooks のうち少なくとも一つを含む必要があります。OAS 3.1.1 仕様にそう定められています。

この冒頭数行に、すでに落とし穴があります。openapiinfo.version は別物です。前者は形式そのもののバージョンで、ツールはこれを見てドキュメントの解釈方法を決めます。後者はあなたの API のバージョンです。仕様には明記されているのに直感は逆を示すため、openapi: 1.0 と書かれたファイルが生まれます。

規模が大きくなると、仕様は $ref で複数ファイルに分割するのが普通です。ルートのドキュメントが ./paths/links.yaml を指し、それがさらに ./schemas/link.yaml を指す、という具合に。便利ですが、ある問題の原因にもなります。これは最後に触れます。

OpenAPI と Swagger は同じものではない

この話題で最もよくある混同です。

何であるか 誰が管理しているか
OpenAPI 標準そのもの、つまり形式 OpenAPI Initiative、Linux Foundation
Swagger その形式を読み込むツール群 SmartBear

混乱の理由は経緯にあります。当初 Swagger は仕様とツールの両方を指していました。その後、仕様が OpenAPI Initiative に移されて OpenAPI Specification と改称され、Swagger という名前はツールの側に残りました。Swagger UI、Swagger Editor などがそれです。

実務では、「うちは Swagger を使っている」はほぼ必ず「OpenAPI のファイルがあり、それを Swagger UI で表示している」を意味します。両者は結び付いてはいません。同じファイルは Redoc でも Scalar でも、クライアントジェネレーターでも同じように開けます。

バージョン:3.0、3.1、3.2

系統ごとの違いは見た目の問題ではありません。

3.0 は今も最も広く使われています。弱点はデータスキーマで、JSON Schema に似ていながら完全には互換でないため、ツール間の相互運用がたびたび壊れます。

3.1 はその隙間を埋めました。Schema オブジェクトが JSON Schema Draft 2020-12 の上位集合になり、すでに JSON Schema で管理しているスキーマを書き直さずに再利用できます。現行の改訂版 3.1.1 は 2024 年 10 月 24 日に公開されました。

3.2 は 2025 年 9 月に登場しました。OpenAPI Initiative の発表から目立つものを挙げると、

  • query メソッド。パラメータが URL に収まらない冪等な読み取りのためのもの;
  • 非標準メソッド向けの additionalOperations
  • ストリーミング系のメディアタイプ。Server-Sent Events、JSON Lines、JSON シーケンス;
  • タグの階層化。summaryparentkind が追加されました;
  • 入力しづらい機器向けの OAuth 2.0 デバイスフロー。

バージョンは番号の大きさではなく、手元のツールが実際に対応している水準で選ぶべきです。3.1 はエコシステム側の対応が十分に厚く、3.2 は新しいぶん一部のツールがまだ追随中です。私たち自身の仕様は 3.1.0 と宣言しています。

実際に何が得られるのか

一つのファイルが同時に複数の役割を果たします。

  • 対話的なドキュメント — Swagger UI や Redoc がファイルからそのままページを組み立て、リクエストを試すフォームまで用意します;
  • クライアントライブラリ — ジェネレーターが目的の言語で SDK を出力するので、ラッパーを手書きせずに済みます;
  • モック — バックエンドが完成する前に記述からモックサーバーを立てられ、フロントエンドが待たされません;
  • 契約テスト — 実際のレスポンスをスキーマと突き合わせ、記述と食い違い始めた瞬間を検出します。

OpenAPI を推す主な論拠は、この一覧から来ています。ドキュメントが、誰かが更新を忘れる別個の文章ではなくなる、というわけです。

論拠自体は正しい。ただし、勝手に実現するものではありません。

仕様とコードを結び付けるものは何もない

「OpenAPI とは」を説明する記事がほとんど触れず、本番で牙をむくのがこの部分です。

仕様ファイルは、ただのファイルです。ルーターはその存在を知りません。コントローラーも同じです。エンドポイントを削っても、追加しても、必須フィールドを任意に変えても、テストは一つも落ちません。ドキュメントは自動的に同期しません。誰かが直すことを覚えている間だけ、正確であり続けます。

ずれは二つの方向に生じ、それぞれ別の害をもたらします。

  • 書いてあるのに動かない。 読んだとおりにリクエストを送ると 404 が返る。残りのドキュメントへの信頼はその場で崩れます。ここが嘘なら、他はどうなのか、と。
  • 動くのに書かれていない。 探している人にとって、その機能は存在しないのと同じです。誰も見つけず、誰も対価を払いません。

厄介なのは後者です。誰も苦情を言わないからです。言いようがありません。利用者はそのエンドポイントがあること自体を知らないのですから。

私たちがまさにそれをやりました。Lix.li の API には、コンバージョン関連のエンドポイントが三つ動いていました。イベントの一覧と、ポストバックの受け取りが単発と一括の二つです。そのどれもが仕様に載っていませんでした。コードは動き、テストも通り、その一方で API のランディングページには、コードに存在しないまったく別のパスが二つ並んでいました。見つかったのは不具合の報告ではなく、全数の突き合わせによってです。

ずれを自動で捕まえる

これは心構えではなくテストで解決します。考え方は単純で、仕様からパスの一覧を取り、ルーターからルートの一覧を取り、二つの集合を双方向で突き合わせます。

openapi.yaml のパス   →   そのルートはあるか?   →   なければドキュメントが嘘
ルーターのルート      →   仕様に書かれているか? →   なければ機能が見えない

「書いてあるものは動く」側は、実際のリクエストで確かめるのが手軽です。認証情報なしでエンドポイントを叩き、返るのが 404 ではなく 401 であることを確認します。この区別が肝心です。401 はルートが見つかって鍵を求めている状態、404 はそのパスが存在しない状態を指します。副作用は起きません。認証はコントローラーより先に走るからです。

逆方向はルート定義を読めば済みます。この作業は、例外を明示的に列挙することも強います。公開する契約に載せるべきでない内部向けエンドポイントのことです。この例外リスト自体に価値があります。何を自分たちの公開 API と見なすのか、一度きちんと決めさせられるからです。

コンバージョンの件のあと、私たちはこのテストを書きました。効果はすぐに出ました。仕様からコンバージョンを再び外すと、テストは失敗し、欠けている二つのパスを名指しします。

気づくのが遅れる落とし穴が二つ

$ref と相対パス。 仕様を複数ファイルに分けると、内部の参照は、そのファイルが配信された URL を基準に解決されます。/openapi に置いたルートドキュメントは ./paths/links.yaml/paths/links.yaml に探しに行き、何も見つけられません。ブラウザでは正常に見えます。Swagger UI はファイルを本来の置き場所から読み込むからです。ところが同じドキュメントを指定したクライアントジェネレーターはつまずきます。仕様は自分のディレクトリからだけ配信するか、外部 $ref を持たない単一ファイルに束ねるか、どちらかにしてください。

Swagger UI はクローラーから見ると空です。 Swagger UI のページは、数行のマークアップと、中身をブラウザ上で描画するスクリプトでできています。配信される HTML にエンドポイントのパスは一つも含まれません。読む人にとっては普通のページでも、索引付けの観点では白紙です。さらに robots.txt でよくある誤りが重なります。JSON エンドポイントを隠すつもりで書いた Disallow: /api/ が、同じ接頭辞の下にある人間向けドキュメントまで一緒に隠してしまうのです。塞ぐべきはバージョン付きの正確なパス、たとえば /api/1.0/ であって、区画全体ではありません。

どこから始めるか

API がすでにあって記述がない場合、おおよそこの順序になります。

  1. エンドポイントを一つ手で書いてみる。構造を掴むにはそれで足ります。
  2. 手元のツールが確実に扱えるバージョンを選ぶ。既定では 3.1。
  3. ファイルが一画面に収まらなくなったら $ref で分割し、どの URL から配信するかを同時に決める。
  4. 仕様とルーターを双方向で突き合わせるテストを置く。この段階を踏むまで、ドキュメントが最新だという話はすべて約束であって、性質ではありません。

実際の API でどうなるかは、Lix.li の API ドキュメントで見られます。リンク、グループ、A/B テスト、コンバージョンを扱っています。この API を軸にしたサービスについては短縮リンク API のページに、近い話題としてはwebhook とは何かと、301・302・307・308 リダイレクトとリクエストメソッドの解説があります。