1 分

Claude Codeで振る舞いからOpenAPIを正直に生成する

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

Claude Codeで振る舞いからOpenAPIを正直に生成する

なぜOpenAPI契約はずれていくのか(そしてそれが重要な理由)

OpenAPI契約はAPIの共通の説明です:どんなエンドポイントがあり、何を送り、何が返り、エラーがどう見えるか。これはサーバーとそれを呼び出すもの(Webアプリ、モバイルアプリ、別サービス)との間の合意です。

問題はドリフトです。稼働中のAPIは変わるのに、仕様が更新されない。あるいは仕様が見栄えよく“クリーンアップ”されていて、実際の実装は奇妙なフィールドを返し続けたり、ステータスコードが欠けていたり、エラーの形が一貫していなかったりします。時間が経つと人々はOpenAPIファイルを信用しなくなり、単なる無視されるドキュメントになります。

ドリフトは普通の圧力から生じます:仕様を更新せずにクイックフィックスを出す、新しいオプションフィールドを「一時的に」追加する、ページネーションが進化する、またはチームが異なる“真の情報源”(バックエンドコード、Postmanコレクション、OpenAPIファイル)を更新することなどです。

正直であることは、仕様が実際の振る舞いと一致していることを意味します。APIが時々競合で409を返すなら、それは契約に含めるべきです。フィールドがnullableなら、その旨を明記しましょう。認証が必要なら曖昧にしないでください。

良いワークフローは次をもたらします:

  • 意図された振る舞いと観察された振る舞いの両方を反映するOpenAPIファイル
  • クライアントが壊れる前にドリフトを検出するための簡単なチェック群
  • コードベースにコピーして使える具体的なクライアント/サーバー検証例

最後の点は重要です。契約は強制されて初めて役立ちます。正直な仕様と繰り返し可能なチェックを組み合わせれば、“APIドキュメント”がチームが頼れるものになります。

コードからではなく、意図された振る舞いから始める

コードを読んだりルートをコピーしたりして始めると、OpenAPIは今日存在するものを描写し、約束したくない細かい挙動まで含めてしまいます。代わりに、呼び出し元に対してAPIがどう振る舞うべきかをまず記述し、その後で実装が仕様に一致するか検証してください。

YAMLやJSONを書く前に、エンドポイントごとに小さな事実を集めます:

  • 何をするか(メソッドとパス)
  • 何を受け付けるか(ヘッダ、クエリ、パスパラメータ、ボディ)
  • 何を返すか(成功ステータスコード、レスポンスの形、重要なヘッダ)
  • どのように失敗するか(想定されるエラー、ステータスコード、エラーボディの形)
  • 誰が呼べるか(認証とロールが関連する場合)

その後、振る舞いを例として書いてください。例を書くことで具体性が生まれ、一貫した契約を下書きしやすくなります。

Tasks APIの例として、ハッピーパスは「titleでタスクを作成すると、idtitlestatuscreatedAtが返る」といった具合です。一般的な失敗も追加します:「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仕様を下書きする(動くプロンプト例)

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を実ファイルとして保存し、小さな差分で繰り返すと良いです。どの変更が承認された振る舞いに基づくものか、モデルの推測かがわかりやすくなります。

実装と比較する前に仕様を検証する

実際のドメインで共有する
共有準備ができたら、カスタムドメインの背後にAPIを公開します。

実稼働と比較する前に、まずOpenAPIファイルが内部的に一貫しているか確認します。ここで願望的な記述や曖昧さを見つけるのが最速です。

各エンドポイントをクライアント開発者の目で読みます。呼び出し元が何を送るべきか、何を受け取れると期待してよいかに注目します。

実用的なレビュー項目:

  • 必須と任意:必須フィールドを正しくマークし、「全部任意」にしない。\n- 型とフォーマット:UUID、メール、日付時刻、最小/最大値は明示する。\n- 例:各エンドポイントに少なくとも1つの現実的なリクエストとレスポンス例を入れ、例がスキーマに合っていることを確認する。\n- ステータスコード:振る舞いメモと合わせる(作成はしばしば201)。400と422の使い分けを一貫させる。\n- 認証:エンドポイントごとに必要か、ロールでアクセスが変わるかを明記する。

エラー応答は特に注意が必要です。1つの共通形を選び、全体で使い回してください。チームによっては非常にシンプルな{ error: string }を選ぶか、より構造化した{ error: { code, message, details } }を選ぶかがあります。どちらでも良いですが、エンドポイント間で混在させないでください。混在するとクライアントコードが特例を積み重ねて壊れやすくなります。

簡単な整合性シナリオを試しましょう。POST /taskstitleを必須としているなら、スキーマで必須にし、失敗レスポンスで実際に返すエラーボディを示し、認証が必要かどうかを明確に書きます。

実稼働API実装と仕様の比較

仕様が意図した振る舞いを反映して読めるようになったら、実稼働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"}}}
}

クライアント側検証(壊れを早期に検出する)

クライアント側では、サーバーが返す形が変わったときにユーザーに影響が出る前に検出することが目的です。テストでは本当に依存している要素に絞ってチェックします(例:タスクにidtitlestatusがあること)。オプショナルなフィールドすべてを検証すると、無害な追加でテストが壊れる原因になります。

読みやすさを保つためのガイドライン:

  • 値の厳密な一致よりも存在と型をチェックすることを優先する。\n- 必須フィールドのみ断言し、オプショナルは必要がなければチェックしない。\n- エラーはステータスとエラーコードを確認し、全文メッセージには依存しない。\n- 契約で明示的に禁止していない限り、追加フィールドは許容する。

Koder.aiを使う場合、これらの例をOpenAPIファイルの横に置き、振る舞いが変わると同じレビューで更新できると便利です。

例シナリオ:シンプルなTasks APIのエンドツーエンド

仕様とコードを一元管理
ソースコードをエクスポートして、OpenAPIと実装をリポジトリに一緒に置きます。

小さなAPIを想定します:POST /tasksでタスクを作成、GET /tasksで一覧、GET /tasks/{id}で1件取得。

まず1つのエンドポイントについて、テスターに説明するように具体的な例をいくつか書きます。

POST /tasksの意図された振る舞いの例:

  • 成功:{ "title": "Buy milk" }を送ると201で新しいタスクオブジェクト(idtitledone:falseを含む)が返る。
  • 失敗1:{}を送ると400で{ "error": "title is required" }のようなエラーが返る。
  • 失敗2:{ "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がある場所では文字列、別では数値)。
  • 例がスキーマと矛盾する(列挙値が合わない、日付時刻例がISO 8601でない)。
  • 決定を先延ばしにして型が広くされすぎる(すべてをstringにする、すべてを任意にする)。
  • 仕様がマーケティング文のようになり(「高速」「安全」)、テスト可能な契約になっていない。

良い契約はテスト可能であるべきです。仕様から失敗するテストを書けないなら、それはまだ正直な仕様ではありません。

共有・公開前の簡単チェックリスト

より速く出荷してテストする
アプリとAPIを同時にデプロイ/ホストして、チームがエンドツーエンドで契約をテストできるようにします。

OpenAPIファイルを他チームに渡す前(またはドキュメントに貼る前)に、「誰かが仕様だけで迷わず使えるか?」を素早くチェックします。

まず例を確認します。有効な仕様でも、すべてのリクエストとレスポンスが抽象的だと役に立ちません。各操作につき少なくとも1つの現実的なリクエスト例と1つの成功レスポンス例を含め、認証やバリデーションエラーの一般的な失敗例も1つずつ用意します。

次に整合性を確認します。あるエンドポイントが{ "error": "..." }で、別のエンドポイントが{ "message": "..." }を返すとクライアントは分岐ロジックだらけになります。単一のエラー形を選び、ステータスコードも予測可能にします。

短いチェックリスト:

  • 必須フィールド、フォーマット(email、uuid、date-time)、enumが明示されている。
  • 同様のエンドポイントでステータスコードが意図的かつ一貫している。
  • エラー応答にスキーマと例が含まれている(ステータスだけで終わらせない)。
  • 各エンドポイントで2〜3回実際に呼び出して(テスト、curl、Postman、ログなど)ペイロードを仕様と比較する。
  • 仕様だけで1つの小さなクライアント呼び出しが書け、ヘッダやフィールド名、null許容性を推測しなくて済むこと。

実用的なコツ:1つのエンドポイントを選び、APIを知らないふりをして「何を送るか、何が返るか、何が壊れるか?」に答えてみてください。OpenAPIがそれに明確に答えられないなら準備不足です。

次のステップ:習慣にして変更を安全に保つ

このワークフローはリリースの混乱時だけでなく定期的に回すと効果が出ます。シンプルなルールを決めて守ること:エンドポイントが変わったときに実行し、更新前にもう一度実行することです。

オーナーシップを簡潔に保ちます。エンドポイントを変更する人が振る舞いメモと仕様ドラフトを更新し、別の人が“仕様対実装”の差分をコードレビューのようにチェックします。QAやサポートの人は曖昧なレスポンスやエッジケースを見つけるのが得意なのでレビュワーに向いています。

契約の編集はコード編集のように扱ってください。チャット駆動のビルダー(Koder.aiなど)を使っているなら、リスクの高い変更前にスナップショットを取り、必要ならロールバックして安全に反復できます。Koder.aiはソースコードのエクスポートもサポートしており、仕様と実装をリポジトリで並べて管理しやすくなります。

遅滞なく運用できるルーチンの例:

  • 新しいエンドポイントを追加したときに振る舞いメモを書く
  • 仕様を検証し、契約テストを実行してからマージする
  • リリース前に稼働APIと仕様を比較する
  • リスクの高い編集前にスナップショットを取り、差分が混乱したらロールバックする

次のアクション:すでに存在するエンドポイントを1つ選んでください。5〜10行の振る舞いメモ(入力、出力、エラーケース)を書き、そのメモからOpenAPIを生成し、検証してから実稼働と比較し、一つの不一致を直して再テストします。1つのエンドポイントがうまくいけば、その習慣は定着しやすくなります。

よくある質問

「OpenAPIドリフト」って簡単に言うと何?

OpenAPIドリフトとは、実際に稼働しているAPIが、チームで共有しているOpenAPIファイルと一致しなくなることです。仕様に新しいフィールドやステータスコード、認証ルールが反映されていなかったり、仕様が「理想」だけを説明していて実際のサーバーが従っていなかったりします。

重要なのは、クライアント(アプリ、他のサービス、生成されたSDK、テストなど)は契約に基づいて動作するため、仕様と実装がずれていると予期せぬ不具合が発生することです。

クライアントチームにとってドリフトはどんなふうに現れることが多いですか?

クライアント側では次のようにドリフトが現れます:モバイルアプリが201を期待して200を受け取る、フィールド名が変わってSDKがデシリアライズできなくなる、エラー処理が壊れるなどです。

たとえ即座にクラッシュしなくても、チームが仕様を信用しなくなり、仕様を使わなくなると早期警告の仕組みが失われます。

なぜOpenAPI仕様をバックエンドコードからコピーして始めるべきではないの?

バックエンドのコードをそのままコピーして仕様を書くべきでない理由は、コードは現状の振る舞い(望ましくない逸脱や一時的な修正を含む)を反映しているからです。

代わりに、まずは期待する振る舞い(入力、出力、エラー)を書き、それに対して実装が一致するかを確認するのが良い出発点です。そうすることで、約束できる契約が得られます。

エンドポイントごとに最低限どんな“振る舞いメモ”を書けばいい?

各エンドポイントについて最低限次を書きます:

  • 目的:1文で何をするか
  • 認証:トークンやロールが必要か、公開か
  • リクエスト:クエリ/パスパラメータ、ヘッダ、JSON本文の例
  • レスポンス:少なくとも1つの成功例と1〜2件の現実的なエラー例(ステータスコード付き)
  • エッジケース:存在しないID、重複(409?)、空結果、ページネーション制限など

具体的なリクエストと2つのレスポンスを書ければ、正直な仕様を作るのに十分なことが多いです。

エラー応答を仕様全体で一貫させるにはどうすればいい?

一つのエラーの形を選んで全体で使い回すことです。

単純なデフォルトの例としては:

  • { "error": "message" }、または
  • { "error": { "code": "...", "message": "...", "details": ... } }

そしてそれをエンドポイントと例の両方で一貫して使います。高度さよりも一貫性が重要です。クライアントはこの形に合わせて実装するため、ばらばらだと保守が難しくなります。

Claude Codeにエンドポイントやフィールドを作りすぎないように促すには?

Claude Codeに余計なフィールドを作らせないための指示例は次の通りです:

  • 「ソースオブトゥルースは振る舞いメモです。推測しないでください。」
  • 「不明点があれば仕様にTODOを入れ、ASSUMPTIONSに列挙してください。」
  • 「再利用可能なスキーマ(例:Error)を含め、それを参照してください。」
  • 「命名規約を守ってください(PascalCaseのスキーマ、lowerCamelCaseのフィールドなど)。」

生成後は必ずASSUMPTIONSを最初に確認してください。そこに妥協や推測が紛れ込むとドリフトの元になります。

実稼働APIと比較する前にOpenAPIファイルで何をチェックすべき?

まず仕様自体の整合性を検証します:

  • 必須/任意フィールドが正しくマークされているか
  • フォーマット(UUID、メール、ISO 8601など)が明示されているか
  • 例がスキーマと一致しているか(列挙値、型、null許容性)
  • ステータスコードが意図的かつ一貫しているか(例:作成はしばしば201
  • エンドポイントごとに認証要件が記載されているか

これで「願望的」な仕様を事前に潰せます。

仕様と実装が食い違ったとき、どちらを変えるべき?

不一致は現状を基準に1つずつ判断します:

  • 実装が正しい(ただし未記載):仕様を更新する
  • 仕様が合意された契約である:実装を修正する
  • どちらも正しくない:まず望ましい振る舞いを決め、それから両方を更新する

変更は小さく保ち(1エンドポイントや1レスポンス単位)、すぐに再テストできるようにします。

「サーバー側」対「クライアント側」の契約検証チェックはどんな感じ?

サーバー側の検証は、契約に違反するリクエストを早期に弾き、明確なエラーを返すことです。テストケースの形で入力、期待出力、期待されるエラー(正確な文言ではなくエラーコードやパターン)を示します。

クライアント側の検証は、サーバーが返す形が変わったときに早めに検出することです。頼っている必須要素に焦点を当て、存在と型をチェックするのが基本で、オプショナルなフィールド全部を検証すると不要な壊れやすさを生みます。

ドリフトを再発させないための簡単な習慣は?

習慣化するための実用的なルーチン例:

  • 新しいエンドポイントを追加するときに振る舞いメモを書く
  • OpenAPIファイル(スキーマ、例、ステータス、認証)を検証してからマージする
  • リリース前に実際のリクエスト/レスポンスを仕様と比較する
  • 小さな契約チェックをテストに追加して、ドリフトを自動で検出する

Koder.aiを使っているなら、OpenAPIをコードと並べて保持し、リスクの高い変更前にスナップショットを取り、必要ならロールバックするのが有効です。

Related posts