Use Cases (更新: 2026/6/3)

Claude CodeでOpenAPI 3.1仕様書を作る実践ガイド: Swagger検証とTypeScript生成まで

Claude CodeでOpenAPI 3.1仕様書を作り、Swagger検証とTypeScript生成まで進める実践ガイド。

Claude CodeでOpenAPI 3.1仕様書を作る実践ガイド: Swagger検証とTypeScript生成まで

APIの仕様書をClaude Codeに作らせると、最初は驚くほど速く見えます。けれども、画面のフォーム名だけを見てuserNameを勝手に必須にしたり、実装にないstatus列を生やしたり、エラー形式を毎回変えたりすると、OpenAPIはすぐ「きれいな嘘」になります。

この記事では、Claude CodeをOpenAPI/Swaggerの下書き係ではなく、実装と仕様の差分を詰めるレビュー相手として使います。対象はOpenAPI 3.1です。OpenAPIはREST APIのエンドポイント、リクエスト、レスポンス、認証、エラーを機械が読める形で表す仕様です。Swaggerは、その仕様を編集、表示、検証するツール群の名前として使われることが多いです。

Masaが社内向けの小さな注文APIで試したとき、最初の失敗は「既存コードを読んでOpenAPIを作って」とだけ頼んだことでした。Claude CodeはそれらしいOrderStatus enumを作りましたが、実DBにはpendingpaidしかなく、画面側にはcancelledの分岐がありませんでした。うまくいったのは、読ませるファイル、禁止する推測、検証コマンド、レビュー観点を先に固定した後です。

公式情報は必ず作業中に確認してください。2026年6月3日時点でOpenAPI Specificationのlatestは3.2.0ですが、この記事のサンプルは生成ツールとの相性を優先してopenapi: 3.1.0に固定します。3.1系の細部はOpenAPI Specification 3.1.2、視覚確認はSwagger Editor documentation、コード生成はOpenAPI Generator usagetypescript-fetch generator、サーバースタブはtypescript-nestjs-server generatorが基準になります。Swagger EditorでOpenAPI 3.1を扱う場合は、現行ドキュメント上でもSwagger Editor Next側の対応状況を確認してから使うのが安全です。

関連する実装全体の流れはClaude Code API開発ガイド、テスト観点はClaude Code APIテスト自動化、権限と公開範囲はClaude Codeセキュリティベストプラクティス、チームでの指示固定はCLAUDE.mdベストプラクティスも合わせて読むと実務に落とし込みやすくなります。

flowchart LR
  A["実装・DB・テスト"] --> B["Claude CodeでOpenAPI草案"]
  B --> C["Swagger/CLIで仕様検証"]
  C --> D["TypeScript client生成"]
  C --> E["契約テスト"]
  C --> F["セキュリティレビュー"]
  D --> G["フロント/外部連携"]
  E --> H["CIの公開ゲート"]
  F --> H

使いどころを決める

OpenAPIをClaude Codeで扱う価値が出る場面は、単にYAMLを書く場面ではありません。仕様を中心に、実装、テスト、クライアント、レビューを同じ言葉でつなぐ場面です。

ユースケースClaude Codeに任せること人間が決めること
既存APIの仕様化ルート、型、ステータスコード、認証境界の棚卸し公開してよい項目、互換性、命名方針
新規APIの契約先行開発OpenAPI 3.1の草案、リクエスト/レスポンス例、生成コマンド業務ルール、エラー方針、リリース範囲
フロントエンド連携TypeScript client生成、呼び出し例、型の差分確認UI上の体験、再試行、表示メッセージ
契約テストoperationId、4xxレスポンス、enum、認証指定の機械チェックどの破壊的変更を止めるか、例外承認
セキュリティレビュー公開schema、認証方式、PII候補、内部項目名の洗い出し法務/契約上の公開可否、監査証跡
パートナーAPI公開Swagger UIで読める説明、サンプル、認証説明契約、レート制限、サポート範囲

ここでのコツは「Claude Codeが決めてよいこと」と「人間が決めること」を分けることです。特にスキーマ、つまりJSONの形を定義する部分は、実装やDBから確認できないなら推測させないでください。

初心者向けに言い換えると、OpenAPIは「APIの取扱説明書」ではなく「機械も読める約束表」です。約束表にまだ実装していない項目を書くと、フロントエンドや外部パートナーはそれを本当に使えるものとして開発を始めます。Claude Codeには表を整える作業を頼み、人間は約束してよい内容だけを残す、という分担にすると失敗しにくくなります。

最小プロジェクトを作る

次の例は、コピーして動かせる小さな注文APIの契約です。Node.js 20以上を想定します。OpenAPI GeneratorはJava実行環境を使うため、生成コマンドまで動かす場合はJavaも入れてください。

mkdir openapi-claude-demo
cd openapi-claude-demo
npm init -y
npm install -D @openapitools/openapi-generator-cli js-yaml
mkdir specs generated scripts

package.jsonscriptsだけ次のように整えます。

{
  "type": "module",
  "scripts": {
    "validate:openapi": "openapi-generator-cli validate -i specs/openapi.yaml",
    "lint:contract": "node scripts/check-openapi-rules.mjs",
    "test:contract": "node --test scripts/contract.test.mjs",
    "generate:client": "openapi-generator-cli generate -i specs/openapi.yaml -g typescript-fetch -o generated/client --additional-properties=supportsES6=true,npmName=@example/orders-client,npmVersion=0.1.0",
    "generate:server": "openapi-generator-cli generate -i specs/openapi.yaml -g typescript-nestjs-server -o generated/server",
    "contract": "npm run validate:openapi && npm run lint:contract && npm run test:contract && npm run generate:client"
  },
  "devDependencies": {
    "@openapitools/openapi-generator-cli": "latest",
    "js-yaml": "latest"
  }
}

specs/openapi.yamlを作ります。OpenAPI 3.1ではJSON Schemaとの整合が強くなっているため、nullableを書きたいときはnullable: trueではなくtype: [string, 'null']のように表現します。

openapi: 3.1.0
info:
  title: Orders API
  version: 0.1.0
  description: Contract-first sample API for order intake.
servers:
  - url: https://api.example.com/v1
paths:
  /orders:
    post:
      operationId: createOrder
      summary: Create an order
      tags: [orders]
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOrderRequest"
            examples:
              minimum:
                value:
                  customerEmail: buyer@example.com
                  items:
                    - sku: SKU-001
                      quantity: 2
      responses:
        "201":
          description: Order created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
    get:
      operationId: listOrders
      summary: List orders
      tags: [orders]
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        "200":
          description: Orders list
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Order"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  responses:
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: Missing or invalid token
  schemas:
    CreateOrderRequest:
      type: object
      additionalProperties: false
      required: [customerEmail, items]
      properties:
        customerEmail:
          type: string
          format: email
        note:
          type: [string, "null"]
          maxLength: 500
        items:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/OrderItemInput"
    OrderItemInput:
      type: object
      additionalProperties: false
      required: [sku, quantity]
      properties:
        sku:
          type: string
          minLength: 1
        quantity:
          type: integer
          minimum: 1
    Order:
      type: object
      additionalProperties: false
      required: [id, customerEmail, status, items, createdAt]
      properties:
        id:
          type: string
          format: uuid
        customerEmail:
          type: string
          format: email
        status:
          type: string
          enum: [pending, paid]
        note:
          type: [string, "null"]
        items:
          type: array
          items:
            $ref: "#/components/schemas/OrderItemInput"
        createdAt:
          type: string
          format: date-time
    ErrorResponse:
      type: object
      additionalProperties: false
      required: [code, message]
      properties:
        code:
          type: string
          enum: [invalid_request, unauthorized]
        message:
          type: string

次に、Claude Codeの幻覚を検出するための薄いルールを置きます。公式バリデータは仕様違反を見つけますが、「TODOのまま」「仮のexample.com以外の謎ドメイン」「TBD説明」のような運用上の危険は別途見るほうが実務では効きます。

// scripts/check-openapi-rules.mjs
import { readFileSync } from "node:fs";

const spec = readFileSync("specs/openapi.yaml", "utf8");
const forbidden = [
  { pattern: /\bTBD\b|\bTODO\b|あとで|仮置き/i, reason: "unfinished placeholder" },
  { pattern: /nullable:\s*true/, reason: "OpenAPI 3.1 should use JSON Schema null types" },
  { pattern: /password|secret|clientSecret|apiKey/i, reason: "review sensitive fields before publishing" }
];

const errors = forbidden
  .filter((rule) => rule.pattern.test(spec))
  .map((rule) => `- ${rule.reason}`);

if (!spec.includes("additionalProperties: false")) {
  errors.push("- schemas should explicitly decide additionalProperties");
}

if (errors.length > 0) {
  console.error("Contract review failed:\n" + errors.join("\n"));
  process.exit(1);
}

console.log("Contract review passed.");

次に、CIで動かせる契約テストを追加します。契約テストは、実装の中身を全部叩くテストではなく「公開APIとして約束した形が壊れていないか」を見るテストです。ここでは、operationIdの重複、更新系APIの認証漏れ、4xxレスポンスの共通化、注文状態のenumを確認します。

// scripts/contract.test.mjs
import { readFileSync } from "node:fs";
import assert from "node:assert/strict";
import test from "node:test";
import { load } from "js-yaml";

const doc = load(readFileSync("specs/openapi.yaml", "utf8"));
const httpMethods = new Set(["get", "put", "post", "delete", "patch", "options", "head", "trace"]);

function operations() {
  return Object.entries(doc.paths ?? {}).flatMap(([path, pathItem]) =>
    Object.entries(pathItem)
      .filter(([method]) => httpMethods.has(method))
      .map(([method, operation]) => ({ path, method, operation }))
  );
}

test("operationId is unique and camelCase", () => {
  const ids = operations().map(({ operation }) => operation.operationId);
  assert.equal(new Set(ids).size, ids.length);
  for (const id of ids) {
    assert.match(id, /^[a-z][A-Za-z0-9]*$/);
  }
});

test("mutating operations require security", () => {
  for (const { path, method, operation } of operations()) {
    if (["get", "head", "options"].includes(method)) continue;
    assert.ok(operation.security?.length, `${method.toUpperCase()} ${path} must declare security`);
  }
});

test("client-visible 4xx responses reuse shared components", () => {
  for (const { path, method, operation } of operations()) {
    for (const [status, response] of Object.entries(operation.responses ?? {})) {
      if (!status.startsWith("4")) continue;
      assert.ok(response.$ref?.startsWith("#/components/responses/"), `${method.toUpperCase()} ${path} ${status}`);
    }
  }
});

test("Order status enum matches the current implementation decision", () => {
  const status = doc.components.schemas.Order.properties.status;
  assert.deepEqual(status.enum, ["pending", "paid"]);
});

実行します。

npm run validate:openapi
npm run lint:contract
npm run test:contract
npm run generate:client
npm run generate:server

生成物はレビュー対象ですが、直接手で直す場所ではありません。仕様を直して再生成するのが基本です。生成されたTypeScript clientを編集してしまうと、次の生成で差分が消えます。

Claude Codeへの指示テンプレート

Claude Codeには、最初から制約を渡します。曖昧な「OpenAPIを作って」ではなく、確認、生成、検証、未確定点の扱いを含めます。

このリポジトリの注文APIをOpenAPI 3.1で仕様化してください。

必ず読むファイル:
- src/routes/orders.ts
- src/domain/order.ts
- prisma/schema.prisma
- tests/orders.test.ts

ルール:
- 実装、DB、テストから確認できないフィールド、enum、エラーコードは作らない。
- 不明点はx-claude-reviewにメモし、スキーマには入れない。
- OpenAPI 3.1として書く。nullable: trueは使わない。
- すべてのoperationIdは安定したcamelCaseにする。
- 400/401/500のエラー形式を既存実装と合わせる。
- 認証が必要な操作にはsecurityを明記する。内部用フィールド、個人情報、secret候補は公開schemaに入れる前にx-claude-reviewへ分ける。
- 作成後にnpm run validate:openapi、npm run lint:contract、npm run test:contractを実行し、失敗したら仕様を直す。

出力:
- specs/openapi.yamlの差分
- 生成されたTypeScript clientでの呼び出し例
- セキュリティレビューと人間が確認すべき未確定点

このテンプレートで大事なのは「不明なら作らない」です。Claude Codeは文脈から補完するのが得意ですが、契約書で補完が混ざると事故になります。x-claude-reviewのような拡張フィールドに未確定点を退避させると、仕様として公開する部分とレビュー待ちの部分を分けられます。

契約テストとセキュリティレビュー

SaaSや外部連携APIでは、OpenAPIのレビューを「読み物の校正」として扱うと足りません。仕様レビュー、生成クライアント確認、契約テスト、セキュリティレビューを別の観点として持つと、責任分界がはっきりします。

観点見るもの止めるべき例
仕様レビューpath、method、schema、status、example実装にないenum、曖昧なobject、説明だけのエラー
クライアント生成typescript-fetchの型、呼び出し関数、optional/nullの扱いnullable誤用、operationId変更、破壊的な型差分
契約テスト4xx共通レスポンス、認証指定、enum、公開フィールド認証漏れ、レスポンス形の揺れ、外部顧客を壊す変更
セキュリティレビューsecuritySchemes、PII、内部ID、secret候補、server URL管理者用フィールド公開、内部ホスト公開、token例の直書き

Claude Codeには、差分の棚卸し、候補リスト、テスト追加、レビュー表の作成を任せられます。一方で、契約上公開できる項目、個人情報の扱い、レート制限、監査ログの保存期間は人間が決める領域です。API統合の責任者なら、Pull Requestの完了条件に「生成clientで1回呼ぶ」「契約テストを通す」「security欄と公開schemaを確認する」の3点を入れておくと運用が安定します。

落とし穴

一つ目は、実装にないステータスやフィールドを足すことです。UIに「キャンセル」ボタンがあるだけでAPIにcancelledを追加してはいけません。DB、ドメインモデル、テストのどこにもないなら未確定です。

二つ目は、エラー形式が揺れることです。あるエンドポイントは{ message }、別のエンドポイントは{ error: { code } }になると、フロントエンドの分岐が壊れます。components.schemas.ErrorResponsecomponents.responsesを先に固定してください。

三つ目は、OpenAPI 3.0の癖を3.1に持ち込むことです。nullable: true、曖昧なobjectadditionalProperties未決定のスキーマは、生成コードやバリデーションで後から効いてきます。

四つ目は、生成コードを手で修正することです。クライアントやサーバースタブは成果物であり、正本はspecs/openapi.yamlです。直す場所を間違えると、次回の生成で修正が消えます。

五つ目は、Swagger UIで見た目だけ確認して安心することです。見た目が整っていても、operationId重複、認証漏れ、レスポンス例の不足、互換性のないenum追加は残ります。CLI検証とレビュー観点をCIに入れてください。

収益化につながる運用

OpenAPI整備は、受託開発や社内研修と相性が良いテーマです。成果物が「仕様書」「生成クライアント」「スタブ」「契約テスト」「検証CI」と目に見えるため、研修後の価値を説明しやすいからです。

個人でまず型を手元に置きたい場合は、ClaudeCodeLabの商品一覧にあるプロンプトやレビュー用テンプレートを使うと、毎回同じ説明をClaude Codeに渡す手間を減らせます。チームで既存APIの棚卸し、CLAUDE.md整備、生成クライアント導入、APIテスト自動化、セキュリティレビューまで一緒に固めたい場合は、Claude Code研修・導入相談で実リポジトリを前提に進めるのが近道です。

実際に試した結果

この記事の手順を小さな注文APIで試したところ、Claude Codeだけに自由生成させた場合はcancelledrefundedなど未実装の状態が混ざりました。一方で、読ませるファイルと禁止ルールを指定し、openapi-generator-cli validate、独自ルール、node --testの契約テストを通す形にすると、差分レビューはかなり楽になりました。最終的に人間が確認したのは、公開してよいフィールド、enumの将来拡張、認証エラーの文言、内部情報がschemaに出ていないかの四点です。OpenAPIはAIに丸投げする資料ではなく、AIと人間が同じ契約を見て作業するための足場として使うのが一番安定しました。

まとめ

Claude CodeでOpenAPI/Swaggerを扱うなら、最初に契約、検証、生成、契約テスト、セキュリティレビューの流れを固定してください。OpenAPI 3.1の仕様を公式ドキュメントで確認し、Swagger系ツールで見た目を確認し、OpenAPI GeneratorでTypeScript clientやサーバースタブを生成し、生成物ではなく仕様を直す。この流れにすると、AIが作った「それらしいAPI仕様」を、実装に追従する運用可能な契約へ近づけられます。

#Claude Code #OpenAPI #Swagger #API設計 #REST
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。