Claude Code for OpenAPI Code Generation: Type-Safe Clients and Server Stubs — Claude Skills 360 Blog
Blog / Development / Claude Code for OpenAPI Code Generation: Type-Safe Clients and Server Stubs
Development

Claude Code for OpenAPI Code Generation: Type-Safe Clients and Server Stubs

Published: August 17, 2026
Read time: 8 min read
By: Claude Skills 360

OpenAPI code generation eliminates an entire class of bugs: the client-server type mismatch. When your TypeScript client types are generated from the same spec your Go server validates against, a breaking API change fails at compile time — not in production. Claude Code generates OpenAPI specs from existing code, generates client and server code from specs, and helps design APIs contract-first before any implementation.

This guide covers OpenAPI code generation with Claude Code: writing specs, generating TypeScript clients with openapi-typescript, Go servers with oapi-codegen, mock servers for development, and CI validation.

CLAUDE.md for OpenAPI Workflows

## OpenAPI Workflow
- OpenAPI 3.1 specs in api/openapi.yaml
- TypeScript client: generated with openapi-typescript (types only, no fetcher)
- Go server: generated with oapi-codegen (interface + types, impl in handlers/)
- Mock server: Prism for local development
- CI: spectral for linting, oasdiff for breaking change detection

## Invariants
- Never edit generated files — regenerate instead
- Add x-go-type annotations for custom Go types (time.Time, uuid.UUID)
- All endpoints document error responses: 400, 401, 403, 404, 500
- Required fields have no default — optional fields have explicit defaults

Contract-First API Design

Design the OpenAPI spec for an Orders API before writing any implementation.
Include pagination, filtering, proper error schemas, and examples.
# api/openapi.yaml
openapi: 3.1.0
info:
  title: Orders API
  version: 1.0.0
  description: Order management for the e-commerce platform

servers:
  - url: https://api.example.com/v1
    description: Production
  - url: http://localhost:8080/v1
    description: Local development

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  schemas:
    OrderStatus:
      type: string
      enum: [pending, confirmed, shipped, delivered, cancelled]

    Money:
      type: object
      required: [amount_cents, currency]
      properties:
        amount_cents:
          type: integer
          minimum: 0
          example: 4999
        currency:
          type: string
          pattern: '^[A-Z]{3}$'
          example: USD

    OrderItem:
      type: object
      required: [product_id, quantity, unit_price]
      properties:
        product_id:
          type: string
          format: uuid
        product_name:
          type: string
        quantity:
          type: integer
          minimum: 1
          maximum: 100
        unit_price:
          $ref: '#/components/schemas/Money'

    Order:
      type: object
      required: [id, user_id, status, items, total, created_at]
      properties:
        id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        status:
          $ref: '#/components/schemas/OrderStatus'
        items:
          type: array
          items:
            $ref: '#/components/schemas/OrderItem'
        total:
          $ref: '#/components/schemas/Money'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    CreateOrderRequest:
      type: object
      required: [items, shipping_address_id]
      properties:
        items:
          type: array
          minItems: 1
          maxItems: 50
          items:
            type: object
            required: [product_id, quantity]
            properties:
              product_id:
                type: string
                format: uuid
              quantity:
                type: integer
                minimum: 1
                maximum: 100
        shipping_address_id:
          type: string
          format: uuid

    PaginatedOrders:
      type: object
      required: [items, total, page, per_page, pages]
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Order'
        total:
          type: integer
        page:
          type: integer
        per_page:
          type: integer
        pages:
          type: integer

    Error:
      type: object
      required: [error, code]
      properties:
        error:
          type: string
        code:
          type: string
        details:
          type: object
          additionalProperties: true

security:
  - BearerAuth: []

paths:
  /orders:
    get:
      operationId: listOrders
      summary: List orders for the authenticated user
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
        - name: per_page
          in: query
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/OrderStatus'
      responses:
        '200':
          description: Paginated list of orders
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedOrders'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      operationId: createOrder
      summary: Create a new order
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOrderRequest'
            examples:
              singleItem:
                summary: Single item order
                value:
                  items:
                    - product_id: "550e8400-e29b-41d4-a716-446655440000"
                      quantity: 2
                  shipping_address_id: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
      responses:
        '201':
          description: Order created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /orders/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      operationId: getOrder
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '404':
          $ref: '#/components/responses/NotFound'

  components:
    responses:
      Unauthorized:
        description: Authentication required
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Error'
      NotFound:
        description: Resource not found
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Error'

TypeScript Client Generation

Generate a type-safe TypeScript client from the OpenAPI spec.
I want strong types but I'll use my own fetch wrapper.
# Install openapi-typescript (types only — no runtime code)
npm install -D openapi-typescript

# Generate types
npx openapi-typescript api/openapi.yaml -o src/types/api.d.ts
// src/api/orders-client.ts
import type { paths, components } from '../types/api';

type Order = components['schemas']['Order'];
type CreateOrderRequest = components['schemas']['CreateOrderRequest'];
type PaginatedOrders = components['schemas']['PaginatedOrders'];

// Type-safe parameters extracted from OpenAPI spec
type ListOrdersParams = paths['/orders']['get']['parameters']['query'];

export class OrdersClient {
  constructor(
    private baseUrl: string,
    private getToken: () => string,
  ) {}

  async list(params?: ListOrdersParams): Promise<PaginatedOrders> {
    const url = new URL(`${this.baseUrl}/orders`);
    if (params?.page) url.searchParams.set('page', String(params.page));
    if (params?.per_page) url.searchParams.set('per_page', String(params.per_page));
    if (params?.status) url.searchParams.set('status', params.status);

    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${this.getToken()}` },
    });

    if (!response.ok) {
      const error = await response.json();
      throw new ApiError(response.status, error.error);
    }

    return response.json();
  }

  async create(data: CreateOrderRequest): Promise<Order> {
    const response = await fetch(`${this.baseUrl}/orders`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${this.getToken()}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    });

    if (!response.ok) {
      const error = await response.json();
      throw new ApiError(response.status, error.error);
    }

    return response.json();
  }
}

Go Server Stub Generation

# oapi-codegen config
cat > oapi-codegen.yaml << 'EOF'
package: handlers
generate:
  chi-server: true   # Generate Chi router interface
  models: true       # Generate types from schemas
  spec: false
output: internal/generated/api.gen.go
EOF

go generate ./...  # Run: oapi-codegen -config oapi-codegen.yaml api/openapi.yaml
// internal/handlers/orders.go — implements the generated interface
package handlers

import (
    "net/http"
    "orders-service/internal/generated"
    "orders-service/internal/service"
)

// Compile-time check: OrdersHandler implements the generated interface
var _ generated.StrictServerInterface = (*OrdersHandler)(nil)

type OrdersHandler struct {
    svc service.OrdersService
}

func (h *OrdersHandler) ListOrders(w http.ResponseWriter, r *http.Request, params generated.ListOrdersParams) {
    page := 1
    if params.Page != nil {
        page = *params.Page
    }
    // Implementation here — interface enforced by compiler
}

CI: Spec Linting and Breaking Change Detection

# .github/workflows/api-validation.yml
name: API validation
on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Lint OpenAPI spec with Spectral
        uses: stoplightio/spectral-action@latest
        with:
          file_glob: 'api/openapi.yaml'

      - name: Check for breaking changes with oasdiff
        run: |
          # Compare current spec against main branch
          git show origin/main:api/openapi.yaml > /tmp/old-spec.yaml
          npx oasdiff breaking /tmp/old-spec.yaml api/openapi.yaml \
            --fail-on ERR  # Only fail on breaking changes, not warnings

      - name: Validate generated code is up to date
        run: |
          go generate ./...
          git diff --exit-code internal/generated/
          # Fails if generated code doesn't match spec

For contract testing between services using Pact (consumer-driven contracts that complement OpenAPI), see the contract testing guide. For API gateway patterns that apply rate limiting and auth in front of OpenAPI services, see the API gateway guide. The Claude Skills 360 bundle includes API design skill sets covering OpenAPI, versioning strategies, and SDK generation. Start with the free tier to try OpenAPI code generation.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free