Use Cases (Updated: 6/3/2026)

Claude Code OpenAPI 3.1 Guide: Swagger Validation and TypeScript Generation

Build OpenAPI 3.1 specs with Claude Code, validate with Swagger tooling, and generate TypeScript clients.

Claude Code OpenAPI 3.1 Guide: Swagger Validation and TypeScript Generation

Asking Claude Code to write an OpenAPI file is fast. Trusting that file without checks is where teams get hurt. The model may infer fields from UI labels, invent enum values that do not exist in the database, or change the error envelope from one endpoint to the next.

This guide treats Claude Code as a contract maintenance assistant, not a magic schema author. You will create an OpenAPI 3.1 spec, validate it, generate a TypeScript client and server stub, and add a small review script that catches the most common AI-generated contract mistakes. OpenAPI describes REST endpoints, request bodies, response bodies, authentication, and errors in a machine-readable format. Swagger is the ecosystem many teams use to edit, preview, and validate those OpenAPI documents.

Masa tested this on a small order API. The first prompt, “read the repo and make OpenAPI,” produced a plausible but wrong cancelled status and a response field that did not exist in the domain model. The better workflow was stricter: tell Claude Code which files to read, forbid unverified fields, run validation, and list unresolved questions outside the public schema.

Keep official references close while working. As of June 3, 2026, the latest OpenAPI Specification is 3.2.0, but this guide pins examples to openapi: 3.1.0 for generator compatibility. Use the OpenAPI Specification 3.1.2, Swagger Editor documentation, OpenAPI Generator usage, typescript-fetch generator, and typescript-nestjs-server generator as the baseline. For OpenAPI 3.1 previews, check Swagger Editor Next support in the current Swagger docs before assuming an older editor handles every 3.1 feature.

For the wider workflow, pair this with the production API development guide, API testing guide, security best practices, and CLAUDE.md best practices.

flowchart LR
  A["Implementation, DB, tests"] --> B["Claude Code drafts OpenAPI"]
  B --> C["Swagger and CLI validation"]
  C --> D["TypeScript client generation"]
  C --> E["Contract tests"]
  C --> F["Security review"]
  D --> G["Frontend or partner integration"]
  E --> H["CI publish gate"]
  F --> H

Where This Pays Off

Claude Code is most useful when OpenAPI becomes the shared contract between backend, frontend, tests, and partner documentation.

Use caseLet Claude Code doKeep human-owned
Document an existing APIInventory routes, models, status codes, auth boundariesPublic fields, compatibility, naming policy
Contract-first new APIDraft OpenAPI 3.1, examples, generator commandsBusiness rules, error policy, release scope
Frontend integrationGenerate TypeScript client, sample calls, type diffsUX, retries, user-facing messages
Contract testingCheck operationIds, 4xx responses, enums, and auth declarationsBreaking-change policy and exception approval
Security reviewList public schemas, auth schemes, PII candidates, internal identifiersLegal exposure, audit evidence, partner promises
Partner API publishingSwagger-readable descriptions, examples, auth notesContracts, rate limits, support promises

The important line is simple: if the implementation, database, tests, or product decision do not prove a schema detail, Claude Code should not publish it.

Copy-Paste Setup

The example below is a small order API contract. It runs on Node.js 20 or newer. OpenAPI Generator uses Java, so install Java if you want to run the generation commands locally.

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

Update package.json with these scripts:

{
  "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"
  }
}

Create specs/openapi.yaml:

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

OpenAPI validation catches specification errors. It does not catch every operational risk, so add a tiny rule script:

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

const spec = readFileSync("specs/openapi.yaml", "utf8");
const forbidden = [
  { pattern: /\bTBD\b|\bTODO\b|placeholder/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.");

Add a small contract test that runs in CI. It checks the public contract itself: stable operation IDs, security on mutating operations, shared 4xx responses, and the current Order.status 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"]);
});

Run the workflow:

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

Generated clients and server stubs are review artifacts, not the source of truth. Fix specs/openapi.yaml, then regenerate.

Prompt Claude Code With Guardrails

Use a prompt that forces evidence-based schema work:

Create an OpenAPI 3.1 contract for the order API in this repository.

Read these files first:
- src/routes/orders.ts
- src/domain/order.ts
- prisma/schema.prisma
- tests/orders.test.ts

Rules:
- Do not invent fields, enum values, or error codes that are not proven by code, schema, tests, or a product note.
- Put unresolved questions in x-claude-review instead of public schemas.
- Use OpenAPI 3.1. Do not use nullable: true.
- Make every operationId stable camelCase.
- Match the existing 400/401/500 error envelope.
- Declare security on protected operations. Put internal fields, PII, and secret candidates in x-claude-review before public schemas.
- Run npm run validate:openapi, npm run lint:contract, and npm run test:contract, then fix failures.

Return:
- the specs/openapi.yaml diff
- a TypeScript client call example
- security review notes and open questions for human review

The key rule is “do not invent.” Claude Code is useful because it fills gaps, but an API contract is exactly where unverified gap-filling becomes expensive.

Contract Tests And Security Review

For SaaS and partner APIs, OpenAPI review is not just proofreading. Split it into spec review, generated client review, contract tests, and security review so each lead knows what they own.

Review laneWhat to inspectStop the release when
Spec reviewpaths, methods, schemas, status codes, examplesenums are unimplemented, objects are vague, errors are descriptive only
Client generationtypescript-fetch types, call functions, optional/null handlingoperationIds change or generated types break consumers
Contract tests4xx response reuse, auth declarations, enums, public fieldsauth is missing, error shape drifts, partner-facing fields change
Security reviewsecuritySchemes, PII, internal IDs, secret candidates, server URLsadmin-only fields, internal hosts, or literal secrets reach the public spec

Claude Code can inventory diffs, draft tests, and build the review table. Humans still own publication decisions, rate limits, contractual commitments, and data exposure.

Failure Cases To Watch

The first pitfall is hallucinated schemas: cancelled, refunded, adminNote, or internalToken appear because they sound reasonable. Reject anything not backed by implementation or product decision.

The second is inconsistent errors. If one endpoint returns { message } and another returns { error: { code } }, generated clients and frontend handling become messy. Define ErrorResponse once and reuse it.

The third is OpenAPI 3.0 habit inside a 3.1 file. In 3.1, prefer JSON Schema null types such as type: [string, "null"] over nullable: true.

The fourth is editing generated code. Generated TypeScript should be disposable. The contract is the source of truth.

The fifth is visual-only review. Swagger UI can look clean while duplicate operationId values, missing auth, weak examples, or incompatible enum changes remain.

Monetization Angle

OpenAPI cleanup is a strong training and consulting offer because the deliverables are concrete: a spec, generated client, server stub, contract test, validation command, and CI checklist. Individual builders can start with ClaudeCodeLab products for reusable prompts and review templates. Teams that want repository-specific rollout, CLAUDE.md, API testing, and security review can use Claude Code training and consultation.

Hands-On Verification

When Masa tested this workflow on the order API, the unrestricted prompt added unimplemented statuses and changed the error shape. The guarded prompt, plus openapi-generator-cli validate, the custom rule script, and node --test contract checks, reduced review to four human decisions: which fields are public, whether enum expansion should be future-proofed, what wording belongs in auth errors, and whether internal data leaked into the schema. The practical lesson was clear: OpenAPI works best as shared contract infrastructure, not as a document to outsource blindly.

Summary

Claude Code can make OpenAPI/Swagger work faster, but only when validation, contract tests, and security review are built into the workflow. Start with OpenAPI 3.1, verify against official docs, preview with Swagger tooling, generate TypeScript clients and server stubs with OpenAPI Generator, and always fix the spec before regenerating artifacts.

#Claude Code #OpenAPI #Swagger #API design #REST
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.