Claude Codeを使って振る舞いからOpenAPIを生成し、それを実装と比較して、簡単なクライアントおよびサーバー検証例を作る方法を学びます。

OpenAPI契約はAPIの共通の説明です:どんなエンドポイントがあり、何を送り、何が返り、エラーがどう見えるか。これはサーバーとそれを呼び出すもの(Webアプリ、モバイルアプリ、別サービス)との間の合意です。
問題はドリフトです。稼働中のAPIは変わるのに、仕様が更新されない。あるいは仕様が見栄えよく“クリーンアップ”されていて、実際の実装は奇妙なフィールドを返し続けたり、ステータスコードが欠けていたり、エラーの形が一貫していなかったりします。時間が経つと人々はOpenAPIファイルを信用しなくなり、単なる無視されるドキュメントになります。
ドリフトは普通の圧力から生じます:仕様を更新せずにクイックフィックスを出す、新しいオプションフィールドを「一時的に」追加する、ページネーションが進化する、またはチームが異なる“真の情報源”(バックエンドコード、Postmanコレクション、OpenAPIファイル)を更新することなどです。
正直であることは、仕様が実際の振る舞いと一致していることを意味します。APIが時々競合で409を返すなら、それは契約に含めるべきです。フィールドがnullableなら、その旨を明記しましょう。認証が必要なら曖昧にしないでください。
良いワークフローは次をもたらします:
最後の点は重要です。契約は強制されて初めて役立ちます。正直な仕様と繰り返し可能なチェックを組み合わせれば、“APIドキュメント”がチームが頼れるものになります。
コードを読んだりルートをコピーしたりして始めると、OpenAPIは今日存在するものを描写し、約束したくない細かい挙動まで含めてしまいます。代わりに、呼び出し元に対してAPIがどう振る舞うべきかをまず記述し、その後で実装が仕様に一致するか検証してください。
YAMLやJSONを書く前に、エンドポイントごとに小さな事実を集めます:
その後、振る舞いを例として書いてください。例を書くことで具体性が生まれ、一貫した契約を下書きしやすくなります。
Tasks APIの例として、ハッピーパスは「titleでタスクを作成すると、id、title、status、createdAtが返る」といった具合です。一般的な失敗も追加します:「titleがないと400で{ "error": "title is required" }が返る」「認証なしでは401が返る」など。すでにエッジケースが分かっているなら、それも含めます:重複タイトルを許すか、存在しないタスクIDにどう応答するかなど。
ルールはコードの詳細に依存しない簡単な文で記録してください:
titleは必須で1〜120文字。”limitが設定されていなければ最大50件を返す(最大200)。”dueDateはISO 8601の日付時刻形式。”最後にv1スコープを決めます。迷うならv1は小さく明確に(作成、取得、一覧、ステータス更新)。検索や一括更新、複雑なフィルタは後に回して、契約を信頼できるものに保ちましょう。
Claude Codeに仕様を書かせる前に、振る舞いメモを小さく繰り返し使える形式で書いてください。目的はモデルが勝手に“穴埋め”して想像で埋めてしまうのを防ぐことです。
良いテンプレートは十分に短くて実際に使えるものであり、二人が同じエンドポイントを似たように記述できる程度に一貫しています。実装ではなくAPIが何をするかに焦点を当ててください。
Use one block per endpoint:
METHOD + PATH:
Purpose (1 sentence):
Auth:
Request:
- Query:
- Headers:
- Body example (JSON):
Responses:
- 200 OK example (JSON):
- 4xx example (status + JSON):
Edge cases:
Data types (human terms):
少なくとも1つの具体的なリクエストと2つのレスポンスを書いてください。ステータスコードと実際的なJSONボディ(フィールド名を実際に使う)を含めます。フィールドがオプションなら、1つの例で欠けている状態を示しましょう。
エッジケースを明示してください。ここは仕様がゆっくりと不正確になりやすい箇所です:空の結果、無効なID(400か404か)、重複(409か冪等化か)、バリデーション失敗、ページネーション制限など。
またスキーマを考える前に、プレーンな言葉でデータ型を示します:文字列か数値か、日付時刻か、ブールか列挙(許可される値のリスト)。こうすると“見た目は綺麗だが実データと合わない”スキーマを避けられます。
Claude Codeは注意深い書記のように扱うと最もよく動きます。振る舞いメモとOpenAPIの形に関する厳格なルールを与えてください。「OpenAPIを書け」とだけ指示すると、推測や命名の不一致、欠落が生じることが多いです。
まず振る舞いメモを貼り、その後に厳密な指示ブロックを追加します。実用的なプロンプトは次のようになります:
You are generating an OpenAPI 3.1 YAML spec.
Source of truth: the behavior notes below. Do not invent endpoints or fields.
If anything is unclear, list it under ASSUMPTIONS and leave TODO markers in the spec.
Requirements:
- Include: info, servers (placeholder), tags, paths, components/schemas, components/securitySchemes.
- For each operation: operationId, tags, summary, description, parameters, requestBody (when needed), responses.
- Model errors consistently with a reusable Error schema and reference it in 4xx/5xx responses.
- Keep naming consistent: PascalCase schema names, lowerCamelCase fields, stable operationId pattern.
Behavior notes:
[PASTE YOUR NOTES HERE]
Output only the OpenAPI YAML, then a short ASSUMPTIONS list.
ドラフトが出たらまずASSUMPTIONSを確認してください。そこが正直さの分かれ目です。正しい部分を承認し、間違いを修正して、更新されたメモで再実行します。
命名の一貫性を保つには、事前に規約を明示して守ることが重要です。たとえば安定したoperationIdパターン、名詞だけのタグ名、単数のスキーマ名、共通のErrorスキーマ、全てで使う1つの認証スキーム名などを決めておきます。
Koder.aiのようなvibe-codingワークスペースで作業しているなら、早い段階でYAMLを実ファイルとして保存し、小さな差分で繰り返すと良いです。どの変更が承認された振る舞いに基づくものか、モデルの推測かがわかりやすくなります。
実稼働と比較する前に、まずOpenAPIファイルが内部的に一貫しているか確認します。ここで願望的な記述や曖昧さを見つけるのが最速です。
各エンドポイントをクライアント開発者の目で読みます。呼び出し元が何を送るべきか、何を受け取れると期待してよいかに注目します。
実用的なレビュー項目:
エラー応答は特に注意が必要です。1つの共通形を選び、全体で使い回してください。チームによっては非常にシンプルな{ error: string }を選ぶか、より構造化した{ error: { code, message, details } }を選ぶかがあります。どちらでも良いですが、エンドポイント間で混在させないでください。混在するとクライアントコードが特例を積み重ねて壊れやすくなります。
簡単な整合性シナリオを試しましょう。POST /tasksがtitleを必須としているなら、スキーマで必須にし、失敗レスポンスで実際に返すエラーボディを示し、認証が必要かどうかを明確に書きます。
仕様が意図した振る舞いを反映して読めるようになったら、実稼働APIをクライアントが実際に体験している“真実”として扱います。目的は仕様とコードで勝ち負けを決めることではなく、差異を早期に明らかにし、各差異について明確な判断を下すことです。
最初のパスでは、実際のリクエスト/レスポンスサンプルが最も簡単な方法です。信頼できるログや自動テストがあればそれも使えます。
よくある不一致を観察してください:一方にしか存在しないエンドポイント、フィールド名や形の違い、ステータスコードの不一致(200 vs 201、400 vs 422)、ドキュメント化されていない振る舞い(ページング、ソート、フィルタ)、認証の違い(仕様は公開だが実装はトークン必須)など。
例:OpenAPIはPOST /tasksが201で{id,title}を返すとしているのに、実際は200で{id,title,createdAt}が返ってくる。これは「ほぼ同じ」では済まされません。生成されたクライアントが壊れる原因になります。
何かを編集する前に、どう解決するか決めます:
変更は小さく、レビューしやすく保ちます:1つのエンドポイント、1つのレスポンス、1つのスキーマ変更単位に留めると再テストしやすくなります。
信頼できる仕様ができたら、それを小さな検証例に変換します。これがドリフトを再び生じさせないための実利です。
サーバーでは、契約に合わないリクエストを早期に失敗させ、明確なエラーを返すことが検証の目的です。これがデータを守り、バグを見つけやすくします。
サーバー検証例は通常、入力、期待出力、期待エラー(エラーコードやメッセージパターン)という3部分で書きます。
例(契約がtitleを必須で1〜120文字とする場合):
{
"name": "Create task without title returns 400",
"request": {"method": "POST", "path": "/tasks", "body": {"title": ""}},
"expect": {"status": 400, "body": {"error": {"code": "VALIDATION_ERROR"}}}
}
クライアント側では、サーバーが返す形が変わったときにユーザーに影響が出る前に検出することが目的です。テストでは本当に依存している要素に絞ってチェックします(例:タスクにid、title、statusがあること)。オプショナルなフィールドすべてを検証すると、無害な追加でテストが壊れる原因になります。
読みやすさを保つためのガイドライン:
Koder.aiを使う場合、これらの例をOpenAPIファイルの横に置き、振る舞いが変わると同じレビューで更新できると便利です。
小さなAPIを想定します:POST /tasksでタスクを作成、GET /tasksで一覧、GET /tasks/{id}で1件取得。
まず1つのエンドポイントについて、テスターに説明するように具体的な例をいくつか書きます。
POST /tasksの意図された振る舞いの例:
{ "title": "Buy milk" }を送ると201で新しいタスクオブジェクト(id、title、done:falseを含む)が返る。{}を送ると400で{ "error": "title is required" }のようなエラーが返る。{ "title": "x" }(短すぎ)を送ると422で{ "error": "title must be at least 3 characters" }が返る。Claude CodeがOpenAPIを下書きするとき、そのスニペットはスキーマ、ステータスコード、現実的な例を取り込みます:
paths:
/tasks:
post:
summary: Create a task
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateTaskRequest'
examples:
ok:
value: { "title": "Buy milk" }
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/Task'
examples:
created:
value: { "id": "t_123", "title": "Buy milk", "done": false }
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
missingTitle:
value: { "error": "title is required" }
'422':
description: Unprocessable Entity
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
tooShort:
value: { "error": "title must be at least 3 characters" }
よくある微妙な不一致は、稼働中のAPIが201ではなく200を返す、あるいは{ "taskId": 123 }のようにフィールド名が違う、といったものです。これは「ほぼ同じ」ではなく、クライアント生成に致命的です。
解決は真実を一つにすることです。意図した振る舞いが正しいなら実装を修正して201と合意のTask形に合わせます。もし稼働中の振る舞いが既に依存されているなら、仕様を現実に合わせ(振る舞いメモも更新して)、不足している検証やエラーを追加してクライアントが驚かないようにします。
契約が規則を説明するのではなく、ある日のレスポンスのスナップショットを記述し始めると不誠実になります。簡単なテスト:今日の挙動をコピーするだけで、新しい実装がこの仕様を満たせてしまうなら、それは正しい契約になっていません。
過学習(overfitting)は罠の一つです。ある一回のレスポンスを捕まえてそれを法則化してしまう例:現在のAPIがすべてのタスクでdueDate: nullを返しているから仕様にもnull可と書いてしまう。実際のルールは「statusがscheduledのとき必須」という可能性があり、契約はルールを表現すべきであって現在のデータセットではありません。
エラーは特に破綻しやすい部分です。成功レスポンスだけ仕様化する誘惑がありますが、クライアントは最低限必要なエラーを知る必要があります:トークンがないと401、権限不足で403、存在しないIDで404、バリデーションエラーは400か422で一貫した形、といった基本を漏らさないでください。
その他の問題パターン:
taskId、別の場所ではid、またはpriorityがある場所では文字列、別では数値)。stringにする、すべてを任意にする)。良い契約はテスト可能であるべきです。仕様から失敗するテストを書けないなら、それはまだ正直な仕様ではありません。
OpenAPIファイルを他チームに渡す前(またはドキュメントに貼る前)に、「誰かが仕様だけで迷わず使えるか?」を素早くチェックします。
まず例を確認します。有効な仕様でも、すべてのリクエストとレスポンスが抽象的だと役に立ちません。各操作につき少なくとも1つの現実的なリクエスト例と1つの成功レスポンス例を含め、認証やバリデーションエラーの一般的な失敗例も1つずつ用意します。
次に整合性を確認します。あるエンドポイントが{ "error": "..." }で、別のエンドポイントが{ "message": "..." }を返すとクライアントは分岐ロジックだらけになります。単一のエラー形を選び、ステータスコードも予測可能にします。
短いチェックリスト:
実用的なコツ:1つのエンドポイントを選び、APIを知らないふりをして「何を送るか、何が返るか、何が壊れるか?」に答えてみてください。OpenAPIがそれに明確に答えられないなら準備不足です。
このワークフローはリリースの混乱時だけでなく定期的に回すと効果が出ます。シンプルなルールを決めて守ること:エンドポイントが変わったときに実行し、更新前にもう一度実行することです。
オーナーシップを簡潔に保ちます。エンドポイントを変更する人が振る舞いメモと仕様ドラフトを更新し、別の人が“仕様対実装”の差分をコードレビューのようにチェックします。QAやサポートの人は曖昧なレスポンスやエッジケースを見つけるのが得意なのでレビュワーに向いています。
契約の編集はコード編集のように扱ってください。チャット駆動のビルダー(Koder.aiなど)を使っているなら、リスクの高い変更前にスナップショットを取り、必要ならロールバックして安全に反復できます。Koder.aiはソースコードのエクスポートもサポートしており、仕様と実装をリポジトリで並べて管理しやすくなります。
遅滞なく運用できるルーチンの例:
次のアクション:すでに存在するエンドポイントを1つ選んでください。5〜10行の振る舞いメモ(入力、出力、エラーケース)を書き、そのメモからOpenAPIを生成し、検証してから実稼働と比較し、一つの不一致を直して再テストします。1つのエンドポイントがうまくいけば、その習慣は定着しやすくなります。
OpenAPIドリフトとは、実際に稼働しているAPIが、チームで共有しているOpenAPIファイルと一致しなくなることです。仕様に新しいフィールドやステータスコード、認証ルールが反映されていなかったり、仕様が「理想」だけを説明していて実際のサーバーが従っていなかったりします。
重要なのは、クライアント(アプリ、他のサービス、生成されたSDK、テストなど)は契約に基づいて動作するため、仕様と実装がずれていると予期せぬ不具合が発生することです。
クライアント側では次のようにドリフトが現れます:モバイルアプリが201を期待して200を受け取る、フィールド名が変わってSDKがデシリアライズできなくなる、エラー処理が壊れるなどです。
たとえ即座にクラッシュしなくても、チームが仕様を信用しなくなり、仕様を使わなくなると早期警告の仕組みが失われます。
バックエンドのコードをそのままコピーして仕様を書くべきでない理由は、コードは現状の振る舞い(望ましくない逸脱や一時的な修正を含む)を反映しているからです。
代わりに、まずは期待する振る舞い(入力、出力、エラー)を書き、それに対して実装が一致するかを確認するのが良い出発点です。そうすることで、約束できる契約が得られます。
各エンドポイントについて最低限次を書きます:
具体的なリクエストと2つのレスポンスを書ければ、正直な仕様を作るのに十分なことが多いです。
一つのエラーの形を選んで全体で使い回すことです。
単純なデフォルトの例としては:
{ "error": "message" }、または{ "error": { "code": "...", "message": "...", "details": ... } }そしてそれをエンドポイントと例の両方で一貫して使います。高度さよりも一貫性が重要です。クライアントはこの形に合わせて実装するため、ばらばらだと保守が難しくなります。
Claude Codeに余計なフィールドを作らせないための指示例は次の通りです:
TODOを入れ、ASSUMPTIONSに列挙してください。」Error)を含め、それを参照してください。」生成後は必ずASSUMPTIONSを最初に確認してください。そこに妥協や推測が紛れ込むとドリフトの元になります。
まず仕様自体の整合性を検証します:
201)これで「願望的」な仕様を事前に潰せます。
不一致は現状を基準に1つずつ判断します:
変更は小さく保ち(1エンドポイントや1レスポンス単位)、すぐに再テストできるようにします。
サーバー側の検証は、契約に違反するリクエストを早期に弾き、明確なエラーを返すことです。テストケースの形で入力、期待出力、期待されるエラー(正確な文言ではなくエラーコードやパターン)を示します。
クライアント側の検証は、サーバーが返す形が変わったときに早めに検出することです。頼っている必須要素に焦点を当て、存在と型をチェックするのが基本で、オプショナルなフィールド全部を検証すると不要な壊れやすさを生みます。
習慣化するための実用的なルーチン例:
Koder.aiを使っているなら、OpenAPIをコードと並べて保持し、リスクの高い変更前にスナップショットを取り、必要ならロールバックするのが有効です。