先に要点
- OpenAPingは、HTTP APIの仕様を機械にも人にも読める形で書くための標準仕様。Swaggerはそれを表示・編集・コード生成するツール群の名前として使われます。
- 自動生成に任せると、認証は出力されても「本人だけ」「管理者だけ」といった権限条件が抜け落ちます。ここは仕様書を見ても分からず、本番事故になりやすい盲点です。
- 仕様書は作ることより更新され続けることが本質。実装とズレるとチームは見なくなり、ズレが放置され、さらに見なくなる悪循環に入ります。
- CIに Spectral(lint)・oasdiff(破壊的変更検出)・Schemathesis(契約テスト)を組み込み、exit code 1 でビルドを落とすと、仕様書が「守られる契約」になります。
APIを作っていると、このエンドポイントは何を受け取るのか このレスポンスの項目は必須なのか エラー時は何が返るのか がチーム内で曖昧になりがちです。
口頭やチャットで補足しているうちは何とかなっても、フロントエンド、バックエンド、QA、外部連携先が増えると、同じ認識を保つのが難しくなります。
そこで使われるのが OpenAPI です。
OpenAPIは、API の仕様をJSONや YAML で書き、ドキュメント表示、モック、テスト、クライアントコード生成などにつなげやすくするための標準です。
この記事では、OpenAPI Specification v3.2.0、OpenAPI Initiative、Swagger公式情報を確認しながら、OpenAPI / Swaggerとは何かを整理したうえで、実務で必ずつまずく「自動生成で権限が抜ける」「仕様書がズレてチームが見なくなる」という失敗を回復手順つきで掘り下げ、最後にCIでスキーマ検証を組み込んでビルドを落とす具体的なやり方まで踏み込みます。
REST API設計の前提が曖昧な場合は、先に GraphQLとは?REST APIとの違いと向いている場面を初心者向けに解説 を読むとつながりやすいです。仕様書があっても実装がそろわない理由まで広げたい場合は、仕様書があるのに実装がぶれるのはなぜか? もつながります。
OpenAPIとは何か
OpenAPIは、HTTP APIの仕様を標準的な形式で記述するための仕様です。
OpenAPI Initiativeの定義では、OpenAPI Specificationは、HTTP APIの能力を人間とコンピューターの両方が理解できるようにする、言語に依存しないインターフェース記述として説明されています。
ざっくり言うと、OpenAPIは APIの取扱説明書を、ツールでも読める形で書くルール です。
OpenAPIで書くと、たとえば次のような情報を1つの仕様書にまとめられます。
これにより、実装を読まないと分からない 担当者に聞かないと分からない 状態を減らせます。
Swaggerとは何か
Swaggerは、もともとAPI仕様の名前として広く使われていました。
その後、仕様としての名前は OpenAPI になり、Swagger は主にツール群の名前として残っています。
実務では、今でも Swaggerを見てください Swaggerを書いてください と言われることがあります。
この場合、多くは OpenAPI形式のAPI仕様書 や Swagger UIで表示されたAPIドキュメント を指しています。
整理すると、次の理解で大きく外しません。
| 名前 | ざっくりした意味 | 実務での見え方 |
|---|---|---|
| OpenAPI | API仕様を書くための標準仕様 | openapi.yaml、openapi.json、API定義ファイル |
| Swagger | OpenAPIを扱うツール群や旧称として使われる名前 | Swagger UI、Swagger Editor、Swagger Codegen |
会話ではSwaggerという名前が残りやすいですが、新しく文書化するときは OpenAPI仕様書 と呼ぶ方が誤解は少ないです。
API仕様書に書く内容
OpenAPIの仕様書は、細かく書こうと思えばかなり多くの情報を書けます。
ただ、最初から全部を完璧に埋めようとすると止まりやすいので、まずはチームが困りやすいところから書くのが現実的です。
エンドポイントとHTTPメソッド
まず、どのURLに、どのHTTPメソッドでアクセスするのかを書きます。
たとえば、ユーザー一覧を取得するなら GET /users、ユーザーを作成するなら POST /users のように整理します。
ここが曖昧だと、フロントエンド側もテスト側も実装を進めにくくなります。
リクエスト
次に、リクエストで何を送るのかを書きます。
クエリパラメータ、パスパラメータ、ヘッダー、JSONボディなどを分けて書くと、利用側が迷いにくくなります。
特に、必須項目、任意項目、型、最大文字数、許可される値は重要です。
name は文字列 だけでなく、必須なのか、空文字を許すのか、何文字までか、まで決めると、後続の実装が安定します。
レスポンス
レスポンスでは、成功時に返るJSONの形、ステータスコード、エラー時の形式を書きます。
実務では成功時だけでなく、バリデーションエラー、認証エラー、権限エラー、存在しないIDを指定したときの挙動が大事です。
エラー形式が画面ごとに違うと、フロントエンド側の処理が増えます。
共通のエラーレスポンスをOpenAPIに書いておくと、実装レビューでも確認しやすくなります。
認証と権限
API仕様書には、認証方式も書きます。
Bearer token、APIキー、Cookieベースのセッションなど、どの方式で呼ぶのかを明確にします。
ただし、ここが本記事で一番強調したい落とし穴です。OpenAPIに 認証が必要(securitySchemes と security)と書いただけでは、権限設計までは伝わりません。
本人だけ見られる 管理者だけ更新できる 組織メンバーだけ取得できる のような認可(authorization)条件は、仕様の語彙に標準的な置き場所がなく、説明文や別の設計資料と組み合わせて残す必要があります。後述するように、ここは自動生成が最も取りこぼす場所です。
OpenAPIの小さな例
最小限の雰囲気だけ見ると、OpenAPI仕様書は次のような形です。
openapi: 3.2.0
info:
title: Sample API
version: 1.0.0
servers:
- url: https://api.example.com
paths:
/users/{userId}:
get:
summary: Get a user
description: |
認可: 本人または同一組織の管理者のみ取得可。
他人のIDを指定すると 403 を返す(404にはしない方針)。
parameters:
- name: userId
in: path
required: true
schema:
type: string
responses:
'200':
description: A user
content:
application/json:
schema:
type: object
required:
- id
- name
properties:
id:
type: string
name:
type: string
'403':
description: 権限がない(他人の情報)
'404':
description: User not found
この例では、GET /users/{userId} があり、userId が必須のパスパラメータで、成功時は id と name を返すことが分かります。
そして重要なのは description に書いた認可条件です。型やステータスコードは機械が検証できますが、「誰がアクセスできるか」は人が言葉で残さないと消えます。本番の仕様書では、共通のレスポンスやスキーマを components に切り出し、同じユーザー形式・エラー形式・認証方式を何度も書かずに済むようにすることが多いです。
失敗1: 自動生成に任せたら権限条件が抜けた
ここからが、監査でも指摘された実務の本丸です。フレームワーク(FastAPI、NestJS、Spring、Laravelのアノテーションなど)からOpenAPIを自動生成する方法は、型とエンドポイントを正確に出してくれて非常に便利です。しかし、便利さの裏で必ず取りこぼすものがあります。
現象
自動生成された仕様書には security: [bearerAuth] と「認証が必要」とは書いてある。が、GET /orders/{id} が「本人の注文だけ見える」のか「全注文が見える」のかは一切書かれていない。フロントが他人のIDで叩いて200が返り、情報漏えいに気づく。
原因
認証(authentication)はミドルウェアやデコレータから機械的に抽出できるが、認可(authorization)はコントローラ内の if user.id != order.user_id のような業務ロジックに埋もれている。ジェネレータはそこを読まないため、仕様書に出力されない。
ポイントは「自動生成は禁止」ではなく「自動生成 + 認可の上書きをパイプラインに固定する」ことです。よくあるのは、誰かが手で description を足したのに、翌週の再生成で全部消えて元に戻る、という回復不能パターンです。これを防ぐには、生成された素の仕様(openapi.gen.yaml)と、人が管理する差分(overlay.yaml)を分け、ビルド時に常にマージして最終成果物を作る構成にします。OpenAPI 3.x では認可そのものを表す標準フィールドがないため、認可は「仕様書に残すべき情報」だと明示的に決めておかないと、構造的に抜けます。
失敗2: 仕様書がズレてチームが見なくなった
もうひとつの典型は、最初にきれいなSwagger UIを用意したのに、実装が進むたびにズレていき、やがて誰も見なくなるパターンです。これは一度始まると自己強化します。ズレる → 信用されない → 見られない → 更新されない → さらにズレる、という下り坂です。
現象
「Swaggerに status は文字列って書いてあったのに、実際は数値で返ってきた」「もう仕様書あてにならないから実装読むわ」という会話がチャットに増える。新メンバーが仕様書ではなく先輩に直接聞き始める。
原因
仕様書の更新がコードと別タイミングの「気が向いたら作業」になっている。PRに仕様書の差分が含まれず、レビューでも誰も気づかない。ズレを検出する仕組みがないので、ズレているという事実すら可視化されない。
確認手順
直近のAPI変更PRを5本ほど開き、コード差分に対して openapi.yaml が同じPRで変わっているか数える。3本以上で仕様書が変わっていなければ、もう運用が壊れていると判断してよい。
回復の肝は「人の善意に頼らない」ことです。レビューチェックリストに APIを変えたらOpenAPIも更新する と書くだけでは、忙しいときに必ず飛ばされます。次章のように、レスポンスがスキーマに合わなければCIが赤くなってマージできない状態にして初めて、仕様書は維持されます。
CIでスキーマ検証を組み込む
「実装とのズレを検出する」を具体的なツールとコマンドに落とします。CIに入れる検証は、役割の違う3段で考えると整理しやすいです。いずれもズレや破壊的変更があれば exit code 1 を返し、ビルドを落とせます。
| 段階 | ツール | 何を見るか | 落とし方 |
|---|---|---|---|
| 1. lint | Spectral | 仕様書自体の書き方・命名・必須記述の欠落 | error が1件でも exit 1(--fail-severity で調整) |
| 2. 破壊的変更 | oasdiff | 旧版との差分が後方互換を壊していないか | --fail-on ERR で破壊的変更時に exit 1 |
| 3. 契約テスト | Schemathesis | 動いているAPIの実レスポンスが仕様に合うか | チェック失敗で exit 1(stateful/fuzzing 含む) |
Spectral で仕様書を lint する
Spectral(Stoplight製)はJSON/YAMLのリンターで、OpenAPI v2/v3.0/v3.1の組み込みルールを持ちます。リポジトリ直下に .spectral.yaml を置くと自動で読まれます。
$ npx @stoplight/spectral-cli lint openapi.yaml
/repo/openapi.yaml
12:7 warning operation-tags Operation should have tags
40:11 error oas3-valid-schema "type" must be string paths./users.get
✖ 2 problems (1 error, 1 warning)
Spectral v5以降、ビルドを失敗させる(exit 1)のはエラーが存在するときだけで、警告は既定では落としません。閾値は --fail-severity=error|warn|info|hint|off で変えられます。「tagsが無い」程度を最初から落とすと運用が回らないので、まずは error 止まり、慣れたら warn に上げる、という順が現実的です。
oasdiff で破壊的変更を止める
oasdiffは旧版と新版のOpenAPIを比較し、後方互換を壊す変更(必須項目の追加、レスポンス項目の削除、型変更など)を検出します。250以上のチェックを ERR(確実な破壊的変更)と WARN(潜在的)に分類します。
$ oasdiff breaking old/openapi.yaml openapi.yaml --fail-on ERR
1 changes: 1 error, 0 warning
error, in API GET /users/{userId} response property 'name' removed
$ echo $?
1
--fail-on ERR で破壊的変更があれば exit 1、--fail-on WARN で潜在的変更も含めて落とせます。これで「気づかずにレスポンス項目を消してフロントを壊す」事故をマージ前に止められます。
Schemathesis で実装と仕様を突合する
最後の段が、失敗2(ズレ)を機械的に潰す本命です。Schemathesisは仕様書からテストケースを自動生成し、動いているAPIに投げて、実レスポンスが仕様に合うかを検証します(プロパティベーステスト)。
$ st run openapi.yaml --url http://localhost:8000
GET /users/{userId} . [ 50%]
POST /users F [100%]
FAILED: response_schema_conformance
POST /users -> 200 but body is missing required property 'id'
=== 1 passed, 1 failed in 3.42s ===
チェック失敗時は exit 1 を返すのでCIが赤くなります。--phases で examples・coverage・fuzzing・stateful を選べ、stateful を有効にすると「作成→取得」のような連鎖もテストします。これが回っていれば、仕様書と実装がズレた瞬間にPRが落ちるため、「いつの間にか嘘の仕様書」になりません。
GitHub Actions に並べる
GitHub Actions での最小構成はこうなります。lintと破壊的変更チェックはサーバーを立てずに走り、契約テストだけアプリを起動します。
name: openapi-check
on: pull_request
jobs:
spec:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 1. lint
- run: npx @stoplight/spectral-cli lint openapi.yaml --fail-severity error
# 2. 破壊的変更(mainの版と比較)
- run: git show origin/main:openapi.yaml > base.yaml || true
- uses: oasdiff/oasdiff-action/breaking@main
with:
base: base.yaml
revision: openapi.yaml
fail-on-diff: true
# 3. 契約テスト(アプリ起動後)
- run: docker compose up -d && sleep 5
- run: pipx run schemathesis run openapi.yaml --url http://localhost:8000
どれか1段でも非ゼロ終了すればジョブが失敗し、PRはマージできません。最初から3段全部を入れる必要はなく、まずSpectralのlintだけを入れて緑にし、次にoasdiff、最後にSchemathesis、と段階導入するのが現実的です。
サンプルを入れる
仕様書には、型だけでなくサンプル(example)もあると理解しやすくなります。
特に、日付形式、金額、ステータス値、エラー形式は、サンプルがあるだけで認識ズレが減ります。
ただし、サンプルだけが正にならないよう注意します。サンプルは理解の補助であり、必須項目や型の定義はスキーマとして明確に残す方が安全です。SchemathesisはサンプルもCIで実APIに投げて検証できるので、嘘のサンプルも炙り出せます。
どこから始めるとよいか
既存APIにOpenAPIを入れるなら、最初から全APIを対象にしなくても大丈夫です。
まずは利用頻度が高いAPI、外部連携に使うAPI、フロントエンドとの認識ズレが起きやすいAPIから始めると効果が見えやすいです。既存の REST API に後から書くコストの目安は、エンドポイント1本あたり5〜30分。重要な10本に絞れば半日〜1日で初版ができます。
最初の一歩としては、次の順番が現実的です。
OpenAPIは、最初から大掛かりなAPI管理基盤を作るためだけのものではありません。小さく始めても、APIの認識をチームでそろえる という目的には十分役立ちます。
OpenAPI / Swaggerに関するよくある質問
Q. OpenAPI と Swagger は同じものですか?
A. 元々は Swagger という名前でしたが、2016年に標準化されて OpenAPI Specification に名称変更されました。Swagger UI、Swagger Editor などのツール名は今もそのまま使われています。仕様を指すなら OpenAPI、ツールを指すなら Swagger と呼び分けると誤解が減ります。
Q. 自動生成された仕様書をそのまま正にしてよいですか?
A. 型とエンドポイントは信頼できますが、認可条件(本人だけ・管理者だけ等)とエラー方針は抜けがちです。生成物(openapi.gen.yaml)と人手の差分(overlay)を分けてビルド時にマージし、再生成で手書きが消えない構成にしてください。
Q. CIで仕様書のズレをどう落とせばよいですか?
A. 3段が定番です。Spectral で仕様書を lint(error で exit 1)、oasdiff で破壊的変更を検出(--fail-on ERR で exit 1)、Schemathesis で動いているAPIの実レスポンスを仕様と突合(失敗で exit 1)。どれかが非ゼロ終了すればPRはマージできません。
Q. 一度ズレて誰も見なくなった仕様書はどう立て直しますか?
A. 部分修正より、1日かけて実装に合わせて作り直すのが速いです。そのうえでSchemathesisをCIに入れ、「ここからはズレたらCIが落ちる」状態にして再スタートします。仕組みで守らない限り、信用は戻りません。
Q. YAML と JSON、どちらで書くべきですか?
A. 人が読み書きするなら YAML、機械が出力するなら JSON が一般的です。YAML はコメントが書け、インデントで構造が分かるのが利点で、手で管理する overlay 側は YAML が扱いやすいです。
Q. ツールはどれを使えば良いですか?
A. 表示は Swagger UI や Redoc、編集は Swagger Editor や Stoplight、CI は Spectral・oasdiff・Schemathesis が定番です。ドキュメント表示では Scalar や RapiDoc も人気です。役割ごとに分けて選ぶと迷いません。
Q. OpenAPI と GraphQL のスキーマ、どちらを選ぶべきですか?
A. REST API なら OpenAPI、GraphQL API なら GraphQL SDL です。プロトコルが違うので「どちらが優れているか」ではなく「使うAPIの種類で選ぶ」のが正しい考え方です。
まとめ
OpenAPIは、API仕様書を標準的な形で書き、チームやツールで共有しやすくするための仕様です。Swaggerは、そのOpenAPI仕様書を表示・編集・生成するツール群の名前として使われることが多いです。
ただし価値は「書いたかどうか」では決まりません。自動生成に任せると認可条件が抜け、放置するとズレてチームが見なくなります。だからこそ、生成物と手書き差分を分けて認可を守り、Spectral・oasdiff・Schemathesis をCIに並べて exit code でビルドを落とす。ここまでやって初めて、OpenAPIは単なるドキュメントから「破ったらCIが赤くなる契約」へ変わります。
参考リンク
- OpenAPI Initiative: OpenAPI Specification v3.2.0
- OpenAPI Initiative: What is OpenAPI?
- Swagger: What Is the Difference Between Swagger and OpenAPI?
- Stoplight: Spectral CLI ドキュメント
- oasdiff: Breaking Changes ドキュメント
- Schemathesis: CLI ドキュメント