openapi-spec-generation

OpenAPI Spec Generation

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "openapi-spec-generation" with this command: npx skills add ericgrill/agents-skills-plugins/ericgrill-agents-skills-plugins-openapi-spec-generation

OpenAPI Spec Generation

Comprehensive patterns for creating, maintaining, and validating OpenAPI 3.1 specifications for RESTful APIs.

When to Use This Skill

  • Creating API documentation from scratch

  • Generating OpenAPI specs from existing code

  • Designing API contracts (design-first approach)

  • Validating API implementations against specs

  • Generating client SDKs from specs

  • Setting up API documentation portals

Core Concepts

  1. OpenAPI 3.1 Structure

openapi: 3.1.0 info: title: API Title version: 1.0.0 servers:

  1. Design Approaches

Approach Description Best For

Design-First Write spec before code New APIs, contracts

Code-First Generate spec from code Existing APIs

Hybrid Annotate code, generate spec Evolving APIs

Templates

Template 1: Complete API Specification

openapi: 3.1.0 info: title: User Management API description: | API for managing users and their profiles.

## Authentication
All endpoints require Bearer token authentication.

## Rate Limiting
- 1000 requests per minute for standard tier
- 10000 requests per minute for enterprise tier

version: 2.0.0 contact: name: API Support email: api-support@example.com url: https://docs.example.com license: name: MIT url: https://opensource.org/licenses/MIT

servers:

tags:

  • name: Users description: User management operations
  • name: Profiles description: User profile operations
  • name: Admin description: Administrative operations

paths: /users: get: operationId: listUsers summary: List all users description: Returns a paginated list of users with optional filtering. tags: - Users parameters: - $ref: "#/components/parameters/PageParam" - $ref: "#/components/parameters/LimitParam" - name: status in: query description: Filter by user status schema: $ref: "#/components/schemas/UserStatus" - name: search in: query description: Search by name or email schema: type: string minLength: 2 maxLength: 100 responses: "200": description: Successful response content: application/json: schema: $ref: "#/components/schemas/UserListResponse" examples: default: $ref: "#/components/examples/UserListExample" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "429": $ref: "#/components/responses/RateLimited" security: - bearerAuth: []

post:
  operationId: createUser
  summary: Create a new user
  description: Creates a new user account and sends welcome email.
  tags:
    - Users
  requestBody:
    required: true
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/CreateUserRequest"
        examples:
          standard:
            summary: Standard user
            value:
              email: user@example.com
              name: John Doe
              role: user
          admin:
            summary: Admin user
            value:
              email: admin@example.com
              name: Admin User
              role: admin
  responses:
    "201":
      description: User created successfully
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/User"
      headers:
        Location:
          description: URL of created user
          schema:
            type: string
            format: uri
    "400":
      $ref: "#/components/responses/BadRequest"
    "409":
      description: Email already exists
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
  security:
    - bearerAuth: []

/users/{userId}: parameters: - $ref: "#/components/parameters/UserIdParam"

get:
  operationId: getUser
  summary: Get user by ID
  tags:
    - Users
  responses:
    "200":
      description: Successful response
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/User"
    "404":
      $ref: "#/components/responses/NotFound"
  security:
    - bearerAuth: []

patch:
  operationId: updateUser
  summary: Update user
  tags:
    - Users
  requestBody:
    required: true
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/UpdateUserRequest"
  responses:
    "200":
      description: User updated
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/User"
    "400":
      $ref: "#/components/responses/BadRequest"
    "404":
      $ref: "#/components/responses/NotFound"
  security:
    - bearerAuth: []

delete:
  operationId: deleteUser
  summary: Delete user
  tags:
    - Users
    - Admin
  responses:
    "204":
      description: User deleted
    "404":
      $ref: "#/components/responses/NotFound"
  security:
    - bearerAuth: []
    - apiKey: []

components: schemas: User: type: object required: - id - email - name - status - createdAt properties: id: type: string format: uuid readOnly: true description: Unique user identifier email: type: string format: email description: User email address name: type: string minLength: 1 maxLength: 100 description: User display name status: $ref: "#/components/schemas/UserStatus" role: type: string enum: [user, moderator, admin] default: user avatar: type: string format: uri nullable: true metadata: type: object additionalProperties: true description: Custom metadata createdAt: type: string format: date-time readOnly: true updatedAt: type: string format: date-time readOnly: true

UserStatus:
  type: string
  enum: [active, inactive, suspended, pending]
  description: User account status

CreateUserRequest:
  type: object
  required:
    - email
    - name
  properties:
    email:
      type: string
      format: email
    name:
      type: string
      minLength: 1
      maxLength: 100
    role:
      type: string
      enum: [user, moderator, admin]
      default: user
    metadata:
      type: object
      additionalProperties: true

UpdateUserRequest:
  type: object
  minProperties: 1
  properties:
    name:
      type: string
      minLength: 1
      maxLength: 100
    status:
      $ref: "#/components/schemas/UserStatus"
    role:
      type: string
      enum: [user, moderator, admin]
    metadata:
      type: object
      additionalProperties: true

UserListResponse:
  type: object
  required:
    - data
    - pagination
  properties:
    data:
      type: array
      items:
        $ref: "#/components/schemas/User"
    pagination:
      $ref: "#/components/schemas/Pagination"

Pagination:
  type: object
  required:
    - page
    - limit
    - total
    - totalPages
  properties:
    page:
      type: integer
      minimum: 1
    limit:
      type: integer
      minimum: 1
      maximum: 100
    total:
      type: integer
      minimum: 0
    totalPages:
      type: integer
      minimum: 0
    hasNext:
      type: boolean
    hasPrev:
      type: boolean

Error:
  type: object
  required:
    - code
    - message
  properties:
    code:
      type: string
      description: Error code for programmatic handling
    message:
      type: string
      description: Human-readable error message
    details:
      type: array
      items:
        type: object
        properties:
          field:
            type: string
          message:
            type: string
    requestId:
      type: string
      description: Request ID for support

parameters: UserIdParam: name: userId in: path required: true description: User ID schema: type: string format: uuid

PageParam:
  name: page
  in: query
  description: Page number (1-based)
  schema:
    type: integer
    minimum: 1
    default: 1

LimitParam:
  name: limit
  in: query
  description: Items per page
  schema:
    type: integer
    minimum: 1
    maximum: 100
    default: 20

responses: BadRequest: description: Invalid request content: application/json: schema: $ref: "#/components/schemas/Error" example: code: VALIDATION_ERROR message: Invalid request parameters details: - field: email message: Must be a valid email address

Unauthorized:
  description: Authentication required
  content:
    application/json:
      schema:
        $ref: "#/components/schemas/Error"
      example:
        code: UNAUTHORIZED
        message: Authentication required

NotFound:
  description: Resource not found
  content:
    application/json:
      schema:
        $ref: "#/components/schemas/Error"
      example:
        code: NOT_FOUND
        message: User not found

RateLimited:
  description: Too many requests
  content:
    application/json:
      schema:
        $ref: "#/components/schemas/Error"
  headers:
    Retry-After:
      description: Seconds until rate limit resets
      schema:
        type: integer
    X-RateLimit-Limit:
      description: Request limit per window
      schema:
        type: integer
    X-RateLimit-Remaining:
      description: Remaining requests in window
      schema:
        type: integer

examples: UserListExample: value: data: - id: "550e8400-e29b-41d4-a716-446655440000" email: "john@example.com" name: "John Doe" status: "active" role: "user" createdAt: "2024-01-15T10:30:00Z" pagination: page: 1 limit: 20 total: 1 totalPages: 1 hasNext: false hasPrev: false

securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT description: JWT token from /auth/login

apiKey:
  type: apiKey
  in: header
  name: X-API-Key
  description: API key for service-to-service calls

security:

  • bearerAuth: []

Template 2: Code-First Generation (Python/FastAPI)

FastAPI with automatic OpenAPI generation

from fastapi import FastAPI, HTTPException, Query, Path, Depends from pydantic import BaseModel, Field, EmailStr from typing import Optional, List from datetime import datetime from uuid import UUID from enum import Enum

app = FastAPI( title="User Management API", description="API for managing users and profiles", version="2.0.0", openapi_tags=[ {"name": "Users", "description": "User operations"}, {"name": "Profiles", "description": "Profile operations"}, ], servers=[ {"url": "https://api.example.com/v2", "description": "Production"}, {"url": "http://localhost:8000", "description": "Development"}, ], )

Enums

class UserStatus(str, Enum): active = "active" inactive = "inactive" suspended = "suspended" pending = "pending"

class UserRole(str, Enum): user = "user" moderator = "moderator" admin = "admin"

Models

class UserBase(BaseModel): email: EmailStr = Field(..., description="User email address") name: str = Field(..., min_length=1, max_length=100, description="Display name")

class UserCreate(UserBase): role: UserRole = Field(default=UserRole.user) metadata: Optional[dict] = Field(default=None, description="Custom metadata")

model_config = {
    "json_schema_extra": {
        "examples": [
            {
                "email": "user@example.com",
                "name": "John Doe",
                "role": "user"
            }
        ]
    }
}

class UserUpdate(BaseModel): name: Optional[str] = Field(None, min_length=1, max_length=100) status: Optional[UserStatus] = None role: Optional[UserRole] = None metadata: Optional[dict] = None

class User(UserBase): id: UUID = Field(..., description="Unique identifier") status: UserStatus role: UserRole avatar: Optional[str] = Field(None, description="Avatar URL") metadata: Optional[dict] = None created_at: datetime = Field(..., alias="createdAt") updated_at: Optional[datetime] = Field(None, alias="updatedAt")

model_config = {"populate_by_name": True}

class Pagination(BaseModel): page: int = Field(..., ge=1) limit: int = Field(..., ge=1, le=100) total: int = Field(..., ge=0) total_pages: int = Field(..., ge=0, alias="totalPages") has_next: bool = Field(..., alias="hasNext") has_prev: bool = Field(..., alias="hasPrev")

class UserListResponse(BaseModel): data: List[User] pagination: Pagination

class ErrorDetail(BaseModel): field: str message: str

class ErrorResponse(BaseModel): code: str = Field(..., description="Error code") message: str = Field(..., description="Error message") details: Optional[List[ErrorDetail]] = None request_id: Optional[str] = Field(None, alias="requestId")

Endpoints

@app.get( "/users", response_model=UserListResponse, tags=["Users"], summary="List all users", description="Returns a paginated list of users with optional filtering.", responses={ 400: {"model": ErrorResponse, "description": "Invalid request"}, 401: {"model": ErrorResponse, "description": "Unauthorized"}, }, ) async def list_users( page: int = Query(1, ge=1, description="Page number"), limit: int = Query(20, ge=1, le=100, description="Items per page"), status: Optional[UserStatus] = Query(None, description="Filter by status"), search: Optional[str] = Query(None, min_length=2, max_length=100), ): """ List users with pagination and filtering.

- **page**: Page number (1-based)
- **limit**: Number of items per page (max 100)
- **status**: Filter by user status
- **search**: Search by name or email
"""
# Implementation
pass

@app.post( "/users", response_model=User, status_code=201, tags=["Users"], summary="Create a new user", responses={ 400: {"model": ErrorResponse}, 409: {"model": ErrorResponse, "description": "Email already exists"}, }, ) async def create_user(user: UserCreate): """Create a new user and send welcome email.""" pass

@app.get( "/users/{user_id}", response_model=User, tags=["Users"], summary="Get user by ID", responses={404: {"model": ErrorResponse}}, ) async def get_user( user_id: UUID = Path(..., description="User ID"), ): """Retrieve a specific user by their ID.""" pass

@app.patch( "/users/{user_id}", response_model=User, tags=["Users"], summary="Update user", responses={ 400: {"model": ErrorResponse}, 404: {"model": ErrorResponse}, }, ) async def update_user( user_id: UUID = Path(..., description="User ID"), user: UserUpdate = ..., ): """Update user attributes.""" pass

@app.delete( "/users/{user_id}", status_code=204, tags=["Users", "Admin"], summary="Delete user", responses={404: {"model": ErrorResponse}}, ) async def delete_user( user_id: UUID = Path(..., description="User ID"), ): """Permanently delete a user.""" pass

Export OpenAPI spec

if name == "main": import json print(json.dumps(app.openapi(), indent=2))

Template 3: Code-First (TypeScript/Express with tsoa)

// tsoa generates OpenAPI from TypeScript decorators

import { Controller, Get, Post, Patch, Delete, Route, Path, Query, Body, Response, SuccessResponse, Tags, Security, Example, } from "tsoa";

// Models interface User { /** Unique identifier / id: string; /* User email address / email: string; /* Display name / name: string; status: UserStatus; role: UserRole; /* Avatar URL / avatar?: string; /* Custom metadata */ metadata?: Record<string, unknown>; createdAt: Date; updatedAt?: Date; }

enum UserStatus { Active = "active", Inactive = "inactive", Suspended = "suspended", Pending = "pending", }

enum UserRole { User = "user", Moderator = "moderator", Admin = "admin", }

interface CreateUserRequest { email: string; name: string; role?: UserRole; metadata?: Record<string, unknown>; }

interface UpdateUserRequest { name?: string; status?: UserStatus; role?: UserRole; metadata?: Record<string, unknown>; }

interface Pagination { page: number; limit: number; total: number; totalPages: number; hasNext: boolean; hasPrev: boolean; }

interface UserListResponse { data: User[]; pagination: Pagination; }

interface ErrorResponse { code: string; message: string; details?: { field: string; message: string }[]; requestId?: string; }

@Route("users") @Tags("Users") export class UsersController extends Controller { /**

  • List all users with pagination and filtering
  • @param page Page number (1-based)
  • @param limit Items per page (max 100)
  • @param status Filter by user status
  • @param search Search by name or email */ @Get() @Security("bearerAuth") @Response<ErrorResponse>(400, "Invalid request") @Response<ErrorResponse>(401, "Unauthorized") @Example<UserListResponse>({ data: [ { id: "550e8400-e29b-41d4-a716-446655440000", email: "john@example.com", name: "John Doe", status: UserStatus.Active, role: UserRole.User, createdAt: new Date("2024-01-15T10:30:00Z"), }, ], pagination: { page: 1, limit: 20, total: 1, totalPages: 1, hasNext: false, hasPrev: false, }, }) public async listUsers( @Query() page: number = 1, @Query() limit: number = 20, @Query() status?: UserStatus, @Query() search?: string, ): Promise<UserListResponse> { // Implementation throw new Error("Not implemented"); }

/**

  • Create a new user */ @Post() @Security("bearerAuth") @SuccessResponse(201, "Created") @Response<ErrorResponse>(400, "Invalid request") @Response<ErrorResponse>(409, "Email already exists") public async createUser(@Body() body: CreateUserRequest): Promise<User> { this.setStatus(201); throw new Error("Not implemented"); }

/**

  • Get user by ID
  • @param userId User ID */ @Get("{userId}") @Security("bearerAuth") @Response<ErrorResponse>(404, "User not found") public async getUser(@Path() userId: string): Promise<User> { throw new Error("Not implemented"); }

/**

  • Update user attributes
  • @param userId User ID */ @Patch("{userId}") @Security("bearerAuth") @Response<ErrorResponse>(400, "Invalid request") @Response<ErrorResponse>(404, "User not found") public async updateUser( @Path() userId: string, @Body() body: UpdateUserRequest, ): Promise<User> { throw new Error("Not implemented"); }

/**

  • Delete user
  • @param userId User ID */ @Delete("{userId}") @Tags("Users", "Admin") @Security("bearerAuth") @SuccessResponse(204, "Deleted") @Response<ErrorResponse>(404, "User not found") public async deleteUser(@Path() userId: string): Promise<void> { this.setStatus(204); } }

Template 4: Validation & Linting

Install validation tools

npm install -g @stoplight/spectral-cli npm install -g @redocly/cli

Spectral ruleset (.spectral.yaml)

cat > .spectral.yaml << 'EOF' extends: ["spectral:oas", "spectral:asyncapi"]

rules:

Enforce operation IDs

operation-operationId: error

Require descriptions

operation-description: warn info-description: error

Naming conventions

operation-operationId-valid-in-url: true

Security

operation-security-defined: error

Response codes

operation-success-response: error

Custom rules

path-params-snake-case: description: Path parameters should be snake_case severity: warn given: "$.paths[].parameters[?(@.in == 'path')].name" then: function: pattern functionOptions: match: "^[a-z][a-z0-9_]$"

schema-properties-camelCase: description: Schema properties should be camelCase severity: warn given: "$.components.schemas[].properties[]~" then: function: casing functionOptions: type: camel EOF

Run Spectral

spectral lint openapi.yaml

Redocly config (redocly.yaml)

cat > redocly.yaml << 'EOF' extends:

  • recommended

rules: no-invalid-media-type-examples: error no-invalid-schema-examples: error operation-4xx-response: warn request-mime-type: severity: error allowedValues: - application/json response-mime-type: severity: error allowedValues: - application/json - application/problem+json

theme: openapi: generateCodeSamples: languages: - lang: curl - lang: python - lang: javascript EOF

Run Redocly

redocly lint openapi.yaml redocly bundle openapi.yaml -o bundled.yaml redocly preview-docs openapi.yaml

SDK Generation

OpenAPI Generator

npm install -g @openapitools/openapi-generator-cli

Generate TypeScript client

openapi-generator-cli generate
-i openapi.yaml
-g typescript-fetch
-o ./generated/typescript-client
--additional-properties=supportsES6=true,npmName=@myorg/api-client

Generate Python client

openapi-generator-cli generate
-i openapi.yaml
-g python
-o ./generated/python-client
--additional-properties=packageName=api_client

Generate Go client

openapi-generator-cli generate
-i openapi.yaml
-g go
-o ./generated/go-client

Best Practices

Do's

  • Use $ref - Reuse schemas, parameters, responses

  • Add examples - Real-world values help consumers

  • Document errors - All possible error codes

  • Version your API - In URL or header

  • Use semantic versioning - For spec changes

Don'ts

  • Don't use generic descriptions - Be specific

  • Don't skip security - Define all schemes

  • Don't forget nullable - Be explicit about null

  • Don't mix styles - Consistent naming throughout

  • Don't hardcode URLs - Use server variables

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

debugging-strategies

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

nodejs-backend-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

systematic-debugging

No summary provided by upstream source.

Repository SourceNeeds Review