Help us improve
Share bugs, ideas, or general feedback.
From api-security-testing
Analyze an entire API codebase and generate an accurate OpenAPI Specification (OAS 3.0) file from the source code. Use this skill whenever the user wants to generate, create, or derive an OpenAPI spec from code, reverse-engineer an API definition, or document an existing API. Triggers on phrases like "generate OAS from code", "create OpenAPI spec", "document my API", "reverse-engineer spec", "write openapi.json from my codebase", or any request to produce an OAS file by reading source files rather than an existing spec.
npx claudepluginhub 42crunch-ai/claude-plugins --plugin api-security-testingHow this skill is triggered — by the user, by Claude, or both
Slash command
/api-security-testing:code-to-oasThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Analyzes an API codebase — regardless of language or framework — and produces
Drafts personalized cold emails, warm intros, follow-ups, updates, and communications for investor outreach during fundraising to angels, VCs, accelerators.
Share bugs, ideas, or general feedback.
Analyzes an API codebase — regardless of language or framework — and produces
a complete, valid OpenAPI 3.0.x specification file (openapi.json) from
the source code. No existing OAS file is required.
Identify the root directory. Use the directory the user specifies, or default to the current working directory. If a specific service subdirectory is open in the editor, use that.
Detect the language and framework. Scan for indicators without opening every file yet:
package.json → Node.js. Check dependencies for express, fastify,
koa, hapi, nestjs, @nestjs/core.requirements.txt / pyproject.toml / setup.py → Python. Check for
fastapi, flask, django, starlette, tornado.pom.xml / build.gradle → Java/Kotlin. Check for spring-boot,
quarkus, micronaut.go.mod → Go. Check for gin, echo, chi, gorilla/mux, fiber.Gemfile → Ruby. Check for rails, sinatra, grape.*.csproj / *.sln → C#/.NET. Check for AspNetCore, WebApi.openapi.yaml, swagger.json) —
read it and use it as a starting scaffold, then extend/correct it.Announce the plan.
"I'll analyze the codebase as a
<framework>API and generateopenapi.json. I'll read route files, middleware, and model definitions. This may take a moment."
Execute the analysis (Steps 1–8 below), then write the OAS file.
Locate the files that define HTTP routes or controllers. Use glob and grep patterns matched to the detected framework:
| Framework | Look for |
|---|---|
| Express | Files importing express.Router(), app.get/post/put/delete/patch |
| FastAPI | Files with @app.get, @router.get, APIRouter() |
| Flask | Files with @app.route, @blueprint.route |
| Django | urls.py files, path() / re_path() / url() calls |
| NestJS | Files with @Controller, @Get, @Post, @Put, @Delete, @Patch |
| Spring | Files with @RestController, @RequestMapping, @GetMapping, etc. |
| Gin/Echo/Chi | Files calling r.GET, r.POST, e.GET, r.Route, chi.NewRouter() |
| Rails | config/routes.rb |
| Sinatra/Grape | Files with get '/', post '/', resource :name |
Read every discovered route file in full. For each route, record:
GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS):param → {param}, <param> → {param}, {param:int} → {param})operationId)For each route handler identified in Step 1, read the handler implementation. Extract:
Any segment in the path like {id} is a path parameter. It must appear in
parameters with in: path and required: true.
Look for:
req.query.foo, destructuring const { foo } = req.queryBody() annotation and not in the pathrequest.args.get('foo')request.GET.get('foo')@RequestParamc.Query("foo"), r.URL.Query().Get("foo")Look for:
req.body, req.body.fooBody() annotated params, Pydantic model paramsrequest.json, request.get_json()request.data, request.POST@RequestBodyc.ShouldBindJSON(), json.NewDecoder(r.Body)Look for:
res.json({...}), res.status(200).json({...}) (Express)return {...} with type annotations (FastAPI)jsonify({...}) (Flask)Response(data, ...) (Django REST)return ResponseEntity<> (Spring)c.JSON(200, ...) (Gin)Note every distinct status code sent and the shape of each response body.
Look for authentication headers read from the request:
req.headers['authorization'], req.headers.authorizationAuthorization: Bearer checksx-api-key, x-user-idRead all middleware files. Look for:
securityScheme type http, scheme bearer,
bearerFormat: JWTsecurityScheme type apiKey,
in: headersecurityScheme type apiKey,
in: querysecurityScheme type http, scheme basicsecurityScheme type oauth2 or openIdConnectsecurityScheme type apiKey, in: cookieFor each route, determine whether authentication middleware is applied:
/login, /register are public)Map each route to: authenticated (list the security scheme) or public
(no security requirement, or security: [{}]).
Locate model, schema, or DTO definitions:
| Framework | Source |
|---|---|
| Express + Mongoose | mongoose.Schema({...}) definitions |
| Express + Sequelize | sequelize.define(...) or class models |
| Express + TypeORM | @Entity class definitions |
| FastAPI | Pydantic BaseModel subclasses |
| Flask + SQLAlchemy | db.Model subclasses |
| Django | models.Model subclasses, serializers.Serializer subclasses |
| Spring | @Entity, @Document, DTO/POJO classes |
| Go | type Foo struct { ... } with JSON tags |
| Rails | ActiveRecord model files, serializer files |
For each model/schema, extract:
minLength, maxLength, minimum, maximum,
pattern, enum, etc.Map framework types to OAS types:
| Framework type | OAS type + format |
|---|---|
String / str / string | type: string |
Number / float / Float | type: number, format: float |
Int / int / Integer / Long | type: integer, format: int64 |
Boolean / bool | type: boolean |
Date / DateTime / datetime | type: string, format: date-time |
Buffer / bytes / BinaryField | type: string, format: binary |
Array / List / []Type | type: array, items: <schema> |
Object / Dict / Map | type: object |
ObjectId / UUID / uuid | type: string, format: uuid |
Email / EmailStr | type: string, format: email |
| Enum | type: string, enum: [...] |
Find the server's base URL and port:
app.listen(PORT) — check PORT env var and its default valueuvicorn.run(app, host=..., port=...) or Dockerfile/docker-compose.ymlALLOWED_HOSTS, runserver portserver.port in application.properties/application.ymlhttp.ListenAndServe(":PORT", ...)docker-compose.yml, .env, Dockerfile, Makefile for exposed portsCheck for a URL prefix applied to all routes:
app.use('/api/v1', router)app.include_router(router, prefix='/api/v1')path('api/v1/', include(urlpatterns))@RequestMapping('/api/v1')Record: base URL (e.g. http://localhost:3000) and any API prefix
(e.g. /api/v1).
Read any existing documentation, README, or config that gives API context:
README.md — API description, versioning, authentication instructionsopenapi.yaml / swagger.json — scaffold to extendCHANGELOG.md — API version history.env.example — reveals environment variable names and defaultspackage.json version field or pyproject.toml version — API versionUse this to populate:
info.titleinfo.descriptioninfo.versioninfo.contact (if present in README)info.license (if present)Build the complete OAS 3.0 document in memory before writing:
info"info": {
"title": "<API name from README or package.json name field>",
"description": "<API description from README; CommonMark supported>",
"version": "<version from package.json / pyproject.toml / git tag>",
"contact": {
"name": "...",
"email": "..."
},
"license": {
"name": "...",
"url": "..."
}
}
Omit contact and license if not found in the codebase.
servers"servers": [
{
"url": "http://localhost:<PORT>",
"description": "Local development server"
}
]
Add staging/production servers if found in environment config or README.
components.securitySchemesDefine one entry per distinct authentication mechanism found in Step 3.
Use descriptive names: BearerAuth, ApiKeyAuth, BasicAuth, OAuth2.
components.schemasOne schema per model found in Step 4. Name schemas in PascalCase. Use $ref
throughout the document to avoid duplication.
For request schemas (write payloads), exclude readOnly fields like id,
createdAt, updatedAt. Mark them "readOnly": true on the base schema
instead. Do not create separate CreateFoo and Foo schemas unless the
shapes genuinely differ significantly.
pathsFor each route:
"/path/{param}": {
"<method>": {
"operationId": "<camelCase unique id derived from handler name>",
"summary": "<short description inferred from handler logic>",
"description": "<longer description if handler is complex>",
"tags": ["<resource name, e.g. Users, Vehicles, Auth>"],
"parameters": [
{
"name": "param",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/FooRequest" }
}
}
},
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Foo" }
}
}
},
"400": { "description": "Bad request — invalid input" },
"401": { "description": "Unauthorized — missing or invalid credentials" },
"404": { "description": "Not found" },
"500": { "description": "Internal server error" }
},
"security": [{ "BearerAuth": [] }]
}
}
Omit requestBody for GET/DELETE/HEAD operations.
Omit security on public routes, or set it to [] to override a global default.
operationId rules:
getVehicleById, createUser<method><Resource> pattern: getUserById, postVehicleTag rules:
/vehicles/* → tag Vehicles/auth/* → tag Authtags field with short descriptionsResponse rules:
401 to authenticated routes404 to routes with path parameters that fetch a resource400 to routes that accept a request body403 if authorization checks (role/ownership) are present in the handler500If the majority of routes are authenticated with the same scheme, apply it
globally and override public routes with "security": []:
"security": [{ "BearerAuth": [] }]
If auth is mixed or inconsistent, apply security per-operation only.
Output location: place openapi.json at the project root, or in an
openapi-spec/ subdirectory if one already exists. Never overwrite an existing
file without first reading it and confirming the intent with the user (unless
the existing file is clearly a scaffold or stub).
Format: JSON. Use 2-space indentation. Order root keys as:
openapi, info, servers, tags, paths, components.
After writing, perform a self-review:
$ref resolves to a defined component{param} in a path has a matching parameter entry with in: path and required: trueoperationId is uniquedescriptionin: path parameter has required: true/After writing the file, output a summary:
OpenAPI Specification Generated
File: <relative path to openapi.json>
Version: OAS 3.0.x
Framework: <detected framework>
Paths: <N> endpoints across <M> route files
Tags: <list of tags>
Schemas: <N> component schemas
Security: <scheme names, or "None detected">
Coverage notes:
- <any routes that were ambiguous or skipped, and why>
- <any response bodies that could not be inferred>
- <any assumptions made that the user should verify>
Router and are mounted in a central app.js
or server.js — always read the entry point to find all mounts and their
prefixes.authenticate or verifyToken applied with .use() before
route groups means all routes in that group are protected.req.params, req.query, req.body map to path/query/requestBody.response_model=Foo on the decorator tells you the response schema.status_code=201 on the decorator overrides the default 200.BaseModel subclasses become components.schemas directly.url_prefix combines with @blueprint.route path.Resource classes map methods to HTTP verbs.ViewSet routers generate CRUD routes automatically — infer them from
router.register() calls.serializers.py files define the schema shapes.permission_classes on a view define authentication requirements.class-validator decorators
become request schemas.@ApiProperty() decorators (if Swagger module is used) carry schema metadata — prioritize these.@UseGuards(JwtAuthGuard)) mark routes as authenticated.@RequestBody, @PathVariable, @RequestParam map directly to OAS concepts.ResponseEntity<Foo>) defines
the response schema.@Valid / @Validated on request body parameters implies validation
constraints are on the DTO class fields.json:"field_name" binding:"required") define field names and
required constraints.c.ShouldBindJSON(&dto) or c.BindJSON(&dto) identifies the request body type.r.Use(AuthMiddleware) before route groups marks
those routes as authenticated.type: object with additionalProperties: true and note the ambiguity.$ref over inline schemas for any object used more than once.openapi: "3.0.3"), not 2.0 (Swagger) or 3.1,
unless the user explicitly requests otherwise.operationId — it is required for downstream tooling
such as 42Crunch audit and scan.nullable: true (OAS 3.0 style) for fields that can be null, not
type: ["string", "null"] (OAS 3.1 style).operationId values in camelCase,
and tag names in Title Case.