Claude Code로 OpenAPI 3.1 만들기: Swagger 검증과 TypeScript 생성
Claude Code로 OpenAPI 3.1 명세를 작성하고 Swagger 검증과 TypeScript 클라이언트 생성을 연결합니다.
Claude Code에게 OpenAPI 파일을 만들라고 하면 결과는 빠르게 나옵니다. 하지만 검증 없이 계약으로 삼으면 위험합니다. UI 문구만 보고 필드를 추정하거나, 데이터베이스에 없는 enum 값을 추가하거나, 엔드포인트마다 오류 응답 형태가 달라질 수 있습니다.
이 글에서는 Claude Code를 스키마의 최종 작성자가 아니라 API 계약을 유지하는 보조자로 사용합니다. OpenAPI 3.1 명세를 만들고, 검증하고, TypeScript 클라이언트와 서버 stub을 생성하며, AI가 만들어낸 그럴듯한 schema를 잡는 간단한 규칙도 추가합니다. OpenAPI는 REST 엔드포인트, 요청, 응답, 인증, 오류를 도구가 읽을 수 있게 표현하는 규격입니다. Swagger는 이런 문서를 편집하고 미리 보고 검토하는 도구 생태계로 자주 쓰입니다.
Masa는 작은 주문 API에서 이 흐름을 시험했습니다. 처음에는 “저장소를 읽고 OpenAPI를 만들어 줘”라고만 했고, Claude Code는 실제 도메인에 없는 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 usage, typescript-fetch generator, typescript-nestjs-server generator를 기준으로 삼으세요. OpenAPI 3.1을 볼 때는 현재 Swagger 문서에서 Swagger Editor Next 지원 범위도 확인하는 편이 안전합니다.
함께 보면 좋은 글은 API 개발 가이드, API 테스트 가이드, 보안 모범 사례, 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
실제 활용 사례
| 상황 | Claude Code가 할 일 | 사람이 결정할 일 |
|---|---|---|
| 기존 API 문서화 | 라우트, 모델, 상태 코드, 인증 경계 정리 | 공개 필드, 호환성, 이름 규칙 |
| 계약 우선 개발 | OpenAPI 3.1 초안, 예시, 생성 명령 | 비즈니스 규칙, 오류 정책, 릴리스 범위 |
| 프런트엔드 연동 | TypeScript client, 호출 예시, 타입 차이 | UX, 재시도, 사용자 메시지 |
| 계약 테스트 | operationId, 4xx 응답, enum, 인증 선언 점검 | breaking change 정책과 예외 승인 |
| 보안 리뷰 | 공개 schema, 인증 방식, PII 후보, 내부 ID 점검 | 법무 리스크, 감사 증거, 파트너 약속 |
| 파트너 API 공개 | Swagger 설명, 샘플, 인증 안내 | 계약, rate limit, 지원 범위 |
코드, DB, 테스트, 제품 결정으로 확인되지 않은 내용은 공개 schema에 넣지 않는 것이 원칙입니다.
복사해서 실행하는 설정
Node.js 20 이상을 권장합니다. OpenAPI Generator는 코드 생성에 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.json:
{
"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.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"
responses:
"201":
description: Order created
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
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"]
items:
type: array
minItems: 1
items:
$ref: "#/components/schemas/OrderItemInput"
OrderItemInput:
type: object
additionalProperties: false
required: [sku, quantity]
properties:
sku:
type: string
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]
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
로컬 규칙 검사:
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.");
CI에서 실행할 계약 테스트도 추가합니다. 이 테스트는 실제 서버를 전부 때리는 대신 공개 계약 자체를 점검합니다. operationId 안정성, 변경 작업의 인증 선언, 4xx 공통 응답, 현재 구현이 지원하는 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"]);
});
실행:
npm run validate:openapi
npm run lint:contract
npm run test:contract
npm run generate:client
npm run generate:server
Claude Code 프롬프트
주문 API의 OpenAPI 3.1 계약을 작성해 주세요.
먼저 src/routes/orders.ts, src/domain/order.ts, prisma/schema.prisma, tests/orders.test.ts를 읽으세요.
코드, schema, 테스트, 제품 메모로 확인되지 않은 필드, enum, 오류 코드는 만들지 마세요.
불확실한 내용은 x-claude-review에 적고 공개 schemas에는 넣지 마세요.
nullable: true는 쓰지 마세요. operationId는 안정적인 camelCase로 작성하세요.
보호된 작업에는 security를 선언하세요. 내부 필드, PII, secret 후보는 공개 schema 전에 x-claude-review로 분리하세요.
npm run validate:openapi, npm run lint:contract, npm run test:contract를 실행하고 실패를 수정하세요.
diff, TypeScript client 호출 예시, 보안 리뷰 메모, 사람이 확인할 질문을 반환하세요.
계약 테스트와 보안 리뷰
SaaS나 파트너 API에서는 OpenAPI 리뷰를 문서 교정으로 끝내면 부족합니다. spec 리뷰, 생성 client 리뷰, 계약 테스트, 보안 리뷰를 분리하세요. spec 리뷰는 path, method, schema, status, example을 봅니다. 생성 client 리뷰는 typescript-fetch 타입과 optional/null 처리를 확인합니다. 계약 테스트는 인증 누락, 오류 응답 drift, enum 변경을 막습니다. 보안 리뷰는 공개 필드, PII, 내부 ID, server URL, secret 후보를 확인합니다.
Claude Code는 diff 정리, 테스트 초안, 리뷰 표 작성을 맡길 수 있습니다. 그러나 어떤 필드가 공개 가능한지, rate limit와 SLA를 어떻게 약속할지, 감사 증거를 얼마나 보관할지는 사람이 결정해야 합니다. API integration lead라면 PR 완료 조건에 “생성 client로 한 번 호출”, “계약 테스트 통과”, “security와 공개 schema 확인”을 넣는 것이 좋습니다.
흔한 실패
가장 흔한 문제는 구현되지 않은 cancelled, refunded, adminNote 같은 schema를 추가하는 것입니다. 그다음은 오류 응답 형태가 일관되지 않은 경우입니다. OpenAPI 3.1 문서에 nullable: true를 쓰는 것도 피해야 합니다. 생성된 client나 server stub을 손으로 고치는 것도 위험합니다. Swagger 화면만 보고 검토를 끝내면 인증 누락, operationId 중복, enum 호환성 문제가 남습니다.
교육 및 컨설팅 CTA
OpenAPI 정리는 교육과 컨설팅에 잘 맞습니다. 결과물이 명확하기 때문입니다: 명세 파일, TypeScript client, 서버 stub, 계약 테스트, 검증 명령, CI 체크리스트. 개인은 ClaudeCodeLab 제품 템플릿에서 prompt와 리뷰표를 시작할 수 있습니다. 팀이 실제 repository 기준으로 CLAUDE.md, API 테스트, 보안 리뷰까지 정리하려면 Claude Code 교육 및 상담을 사용할 수 있습니다.
직접 검증한 결과
Masa의 테스트에서 자유 프롬프트는 미구현 상태를 추가하고 오류 구조를 바꿨습니다. 필수 파일, 추측 금지, openapi-generator-cli validate, 로컬 규칙 검사, node --test 계약 테스트를 넣자 사람의 리뷰는 공개 필드, enum 확장 가능성, 인증 오류 문구, 내부 정보 노출 여부에 집중되었습니다. OpenAPI는 AI에게 맡겨 버리는 문서가 아니라 팀과 AI가 함께 보는 계약으로 사용할 때 가장 안정적이었습니다.
무료 PDF: Claude Code 치트시트
이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.
개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.
작성자 소개
Masa
Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.
관련 글
Obsidian 메모를 CLAUDE.md로 바꾸는 Claude Code 워크플로
Obsidian 작업 메모를 CLAUDE.md 운영 노트로 정리해 Claude Code 세션의 문맥 반복을 줄입니다.
Claude Code Revenue CTA Routing: 글에서 PDF, Gumroad, 상담으로 보내기
독자 의도에 따라 무료 PDF, Gumroad 상품, 상담으로 나누는 Claude Code CTA 설계입니다.
Claude Code 팀 인계 규칙: 리뷰 증거, 권한, 롤백, 수익 경로까지 넘기는 법
Claude Code 작업을 팀에 넘길 때 필요한 증거, 권한 규칙, 롤백, 무료 PDF, Gumroad, 상담 경로 체크리스트.