openapi-types

Generate TypeScript types and client SDKs from OpenAPI specs

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-types" with this command: npx skills add manastalukdar/claude-devstudio/manastalukdar-claude-devstudio-openapi-types

OpenAPI Type Generator

I'll generate TypeScript types, client SDKs, and Zod schemas from OpenAPI 3.0 specifications.

Arguments: $ARGUMENTS - path to OpenAPI spec file

Features:

  • TypeScript types from OpenAPI schemas
  • Type-safe fetch/axios client SDK
  • Zod schemas for runtime validation
  • React hooks for data fetching
  • Integration with /api-docs-generate

Token Optimization

This skill uses code generation-specific patterns to minimize token usage:

1. OpenAPI Spec Caching (800 token savings)

Pattern: Cache parsed OpenAPI specification structure

  • Store spec analysis in .openapi-types-cache (1 hour TTL)
  • Cache: schemas, endpoints, parameters, responses
  • Read cached spec on subsequent runs (50 tokens vs 850 tokens fresh)
  • Invalidate on spec file changes
  • Savings: 94% on repeat type generations

2. Bash-Based OpenAPI Validation (600 token savings)

Pattern: Use openapi-generator-cli or swagger-cli for validation

  • Run swagger-cli validate spec.yaml (200 tokens)
  • Parse validation errors with grep
  • No Task agents for spec validation
  • Savings: 75% vs LLM-based validation

3. Template-Based Type Generation (2,000 token savings)

Pattern: Use openapi-typescript or openapi-generator templates

  • Standard tool: openapi-typescript spec.yaml -o types.ts (300 tokens)
  • Pre-defined type generation templates
  • No creative type generation logic needed
  • Savings: 87% vs LLM-generated TypeScript types

4. Early Exit for Current Types (95% savings)

Pattern: Detect if types already generated and current

  • Check for existing types file matching spec name (50 tokens)
  • Compare spec mtime with types file mtime
  • If types current: return types location (100 tokens)
  • Distribution: ~50% of runs check existing types
  • Savings: 100 vs 2,500 tokens for type regeneration checks

5. Sample-Based Schema Analysis (700 token savings)

Pattern: Analyze first 20 schemas for patterns

  • Identify common patterns: pagination, errors, IDs (400 tokens)
  • Apply patterns to remaining schemas
  • Full analysis only if explicitly requested
  • Savings: 65% vs analyzing every schema definition

6. Incremental Type Updates (1,000 token savings)

Pattern: Generate only changed schemas

  • Compare new spec with cached version
  • Generate types only for modified schemas
  • Merge with existing types file
  • Savings: 75% vs full type regeneration

7. Cached Tool Detection (400 token savings)

Pattern: Cache openapi-typescript installation status

  • Check tool installation once, cache result
  • Don't re-check npm ls on each run
  • Standard installation instructions
  • Savings: 80% on tool detection

8. Bash-Based Code Generation (800 token savings)

Pattern: Use openapi-typescript tool directly

  • Generate all types with single command (300 tokens)
  • Post-process with sed/awk if needed (100 tokens)
  • No Task agents for code generation
  • Savings: 75% vs Task-based generation

Real-World Token Usage Distribution

Typical operation patterns:

  • Check existing types (current): 100 tokens
  • Generate types (first time): 2,500 tokens
  • Update types (spec changed): 1,500 tokens
  • Regenerate (tool changed): 2,000 tokens
  • Incremental update (few schemas): 800 tokens
  • Most common: Check existing types or tool-based generation

Expected per-generation: 2,000-3,000 tokens (50% reduction from 4,000-6,000 baseline) Real-world average: 900 tokens (due to cached specs, early exit, tool-based generation)

Phase 1: OpenAPI Spec Detection

#!/bin/bash
# Detect OpenAPI specifications

echo "=== Detecting OpenAPI Specifications ==="
echo ""

# Find OpenAPI spec files
find_openapi_specs() {
    find . -type f \( \
        -name "openapi*.yaml" -o \
        -name "openapi*.yml" -o \
        -name "swagger*.json" -o \
        -name "swagger*.yaml" -o \
        -name "api-spec*.yaml" \
    \) ! -path "*/node_modules/*" 2>/dev/null
}

SPECS=$(find_openapi_specs)

if [ -z "$SPECS" ]; then
    echo "❌ No OpenAPI specifications found"
    echo ""
    echo "Looking for files like:"
    echo "  - openapi.yaml"
    echo "  - swagger.json"
    echo "  - docs/api-spec.yaml"
    echo ""
    echo "💡 Generate OpenAPI spec with: claude \"/api-docs-generate\""
    exit 1
fi

echo "✓ Found OpenAPI specifications:"
echo "$SPECS" | sed 's/^/  /'
echo ""

# Select spec file
if [ -n "$1" ]; then
    SPEC_FILE="$1"
else
    SPEC_FILE=$(echo "$SPECS" | head -1)
fi

if [ ! -f "$SPEC_FILE" ]; then
    echo "❌ Spec file not found: $SPEC_FILE"
    exit 1
fi

echo "Using spec: $SPEC_FILE"

# Validate OpenAPI spec
validate_spec() {
    echo ""
    echo "=== Validating OpenAPI Spec ==="
    echo ""

    if command -v swagger-cli &> /dev/null; then
        npx swagger-cli validate "$SPEC_FILE"
        echo "✓ Spec is valid"
    else
        echo "⚠️  swagger-cli not found, skipping validation"
        echo "   Install: npm install -g @apidevtools/swagger-cli"
    fi
}

validate_spec

Phase 2: Generate TypeScript Types

// Generated TypeScript types from OpenAPI spec
// Auto-generated - do not edit manually

/**
 * OpenAPI spec: ${SPEC_FILE}
 * Generated: ${DATE}
 */

export namespace API {
  /**
   * Common types
   */
  export type UUID = string;
  export type ISODate = string;
  export type Email = string;

  /**
   * User schema
   */
  export interface User {
    /** Unique user identifier */
    id: UUID;

    /** User's email address */
    email: Email;

    /** User's display name */
    name: string;

    /** User role for authorization */
    role: 'admin' | 'user' | 'guest';

    /** Account creation timestamp */
    createdAt: ISODate;

    /** Last update timestamp */
    updatedAt: ISODate;

    /** Optional user profile */
    profile?: UserProfile;
  }

  /**
   * User profile schema
   */
  export interface UserProfile {
    /** Profile bio */
    bio?: string;

    /** Avatar image URL */
    avatarUrl?: string;

    /** User location */
    location?: string;

    /** Social media links */
    socialLinks?: {
      twitter?: string;
      github?: string;
      linkedin?: string;
    };
  }

  /**
   * Request schemas
   */
  export interface CreateUserRequest {
    email: Email;
    name: string;
    password: string;
    role?: 'user' | 'guest';
  }

  export interface UpdateUserRequest {
    name?: string;
    profile?: Partial<UserProfile>;
  }

  export interface LoginRequest {
    email: Email;
    password: string;
  }

  /**
   * Response schemas
   */
  export interface UserResponse {
    data: User;
    message?: string;
  }

  export interface UsersListResponse {
    data: User[];
    pagination: PaginationMeta;
  }

  export interface LoginResponse {
    token: string;
    user: User;
    expiresIn: number;
  }

  export interface ErrorResponse {
    error: {
      code: string;
      message: string;
      details?: Record<string, unknown>;
    };
    timestamp: ISODate;
  }

  /**
   * Pagination metadata
   */
  export interface PaginationMeta {
    page: number;
    pageSize: number;
    total: number;
    totalPages: number;
    hasNext: boolean;
    hasPrevious: boolean;
  }

  /**
   * Query parameters
   */
  export interface ListUsersParams {
    page?: number;
    limit?: number;
    search?: string;
    sortBy?: 'name' | 'createdAt' | 'updatedAt';
    sortOrder?: 'asc' | 'desc';
  }

  /**
   * Path parameters
   */
  export interface UserIdParam {
    userId: UUID;
  }

  /**
   * API endpoints
   */
  export namespace Endpoints {
    export const USERS_LIST = '/api/users';
    export const USER_GET = '/api/users/:userId';
    export const USER_CREATE = '/api/users';
    export const USER_UPDATE = '/api/users/:userId';
    export const USER_DELETE = '/api/users/:userId';
    export const AUTH_LOGIN = '/api/auth/login';
    export const AUTH_LOGOUT = '/api/auth/logout';
  }
}

Phase 3: Generate Zod Schemas

// Zod schemas for runtime validation
import { z } from 'zod';

/**
 * User schema
 */
export const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  name: z.string().min(1).max(255),
  role: z.enum(['admin', 'user', 'guest']),
  createdAt: z.string().datetime(),
  updatedAt: z.string().datetime(),
  profile: z
    .object({
      bio: z.string().optional(),
      avatarUrl: z.string().url().optional(),
      location: z.string().optional(),
      socialLinks: z
        .object({
          twitter: z.string().url().optional(),
          github: z.string().url().optional(),
          linkedin: z.string().url().optional(),
        })
        .optional(),
    })
    .optional(),
});

export type User = z.infer<typeof UserSchema>;

/**
 * Request schemas
 */
export const CreateUserRequestSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(255),
  password: z.string().min(8).max(255),
  role: z.enum(['user', 'guest']).optional(),
});

export type CreateUserRequest = z.infer<typeof CreateUserRequestSchema>;

export const UpdateUserRequestSchema = z.object({
  name: z.string().min(1).max(255).optional(),
  profile: z
    .object({
      bio: z.string().optional(),
      avatarUrl: z.string().url().optional(),
      location: z.string().optional(),
    })
    .partial()
    .optional(),
});

export type UpdateUserRequest = z.infer<typeof UpdateUserRequestSchema>;

export const LoginRequestSchema = z.object({
  email: z.string().email(),
  password: z.string().min(1),
});

export type LoginRequest = z.infer<typeof LoginRequestSchema>;

/**
 * Response schemas
 */
export const UserResponseSchema = z.object({
  data: UserSchema,
  message: z.string().optional(),
});

export type UserResponse = z.infer<typeof UserResponseSchema>;

export const UsersListResponseSchema = z.object({
  data: z.array(UserSchema),
  pagination: z.object({
    page: z.number().int().positive(),
    pageSize: z.number().int().positive(),
    total: z.number().int().nonnegative(),
    totalPages: z.number().int().nonnegative(),
    hasNext: z.boolean(),
    hasPrevious: z.boolean(),
  }),
});

export type UsersListResponse = z.infer<typeof UsersListResponseSchema>;

export const ErrorResponseSchema = z.object({
  error: z.object({
    code: z.string(),
    message: z.string(),
    details: z.record(z.unknown()).optional(),
  }),
  timestamp: z.string().datetime(),
});

export type ErrorResponse = z.infer<typeof ErrorResponseSchema>;

/**
 * Validation utilities
 */
export function validateCreateUserRequest(
  data: unknown
): CreateUserRequest {
  return CreateUserRequestSchema.parse(data);
}

export function validateUserResponse(data: unknown): UserResponse {
  return UserResponseSchema.parse(data);
}

export function isValidUser(data: unknown): data is User {
  return UserSchema.safeParse(data).success;
}

Phase 4: Generate Fetch Client SDK

// Type-safe fetch client SDK
import type { API } from './types';

/**
 * API client configuration
 */
export interface ClientConfig {
  baseURL: string;
  headers?: Record<string, string>;
  timeout?: number;
}

/**
 * API client error
 */
export class APIError extends Error {
  constructor(
    public status: number,
    public code: string,
    message: string,
    public details?: unknown
  ) {
    super(message);
    this.name = 'APIError';
  }
}

/**
 * Type-safe API client
 */
export class APIClient {
  private baseURL: string;
  private headers: Record<string, string>;
  private timeout: number;

  constructor(config: ClientConfig) {
    this.baseURL = config.baseURL.replace(/\/$/, '');
    this.headers = {
      'Content-Type': 'application/json',
      ...config.headers,
    };
    this.timeout = config.timeout || 30000;
  }

  /**
   * Set authorization token
   */
  setAuthToken(token: string): void {
    this.headers['Authorization'] = `Bearer ${token}`;
  }

  /**
   * Clear authorization token
   */
  clearAuthToken(): void {
    delete this.headers['Authorization'];
  }

  /**
   * Make HTTP request
   */
  private async request<T>(
    method: string,
    path: string,
    options?: {
      params?: Record<string, any>;
      body?: unknown;
      headers?: Record<string, string>;
    }
  ): Promise<T> {
    // Build URL with query parameters
    const url = new URL(`${this.baseURL}${path}`);
    if (options?.params) {
      Object.entries(options.params).forEach(([key, value]) => {
        if (value !== undefined && value !== null) {
          url.searchParams.append(key, String(value));
        }
      });
    }

    // Make request with timeout
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), this.timeout);

    try {
      const response = await fetch(url.toString(), {
        method,
        headers: { ...this.headers, ...options?.headers },
        body: options?.body ? JSON.stringify(options.body) : undefined,
        signal: controller.signal,
      });

      clearTimeout(timeoutId);

      // Handle error responses
      if (!response.ok) {
        const error = await response.json().catch(() => ({}));
        throw new APIError(
          response.status,
          error.error?.code || 'UNKNOWN_ERROR',
          error.error?.message || response.statusText,
          error.error?.details
        );
      }

      // Handle 204 No Content
      if (response.status === 204) {
        return undefined as T;
      }

      return response.json();
    } catch (error) {
      clearTimeout(timeoutId);

      if (error instanceof APIError) {
        throw error;
      }

      if (error instanceof Error && error.name === 'AbortError') {
        throw new APIError(408, 'TIMEOUT', 'Request timeout');
      }

      throw new APIError(
        0,
        'NETWORK_ERROR',
        error instanceof Error ? error.message : 'Network error'
      );
    }
  }

  /**
   * Users API
   */
  readonly users = {
    /**
     * List users
     */
    list: (params?: API.ListUsersParams) =>
      this.request<API.UsersListResponse>('GET', '/api/users', { params }),

    /**
     * Get user by ID
     */
    get: (userId: string) =>
      this.request<API.UserResponse>('GET', `/api/users/${userId}`),

    /**
     * Create new user
     */
    create: (data: API.CreateUserRequest) =>
      this.request<API.UserResponse>('POST', '/api/users', { body: data }),

    /**
     * Update user
     */
    update: (userId: string, data: API.UpdateUserRequest) =>
      this.request<API.UserResponse>('PUT', `/api/users/${userId}`, {
        body: data,
      }),

    /**
     * Delete user
     */
    delete: (userId: string) =>
      this.request<void>('DELETE', `/api/users/${userId}`),
  };

  /**
   * Authentication API
   */
  readonly auth = {
    /**
     * Login
     */
    login: (data: API.LoginRequest) =>
      this.request<API.LoginResponse>('POST', '/api/auth/login', {
        body: data,
      }),

    /**
     * Logout
     */
    logout: () => this.request<void>('POST', '/api/auth/logout'),
  };
}

/**
 * Create API client instance
 */
export function createAPIClient(config: ClientConfig): APIClient {
  return new APIClient(config);
}

/**
 * Default client instance
 */
export const apiClient = createAPIClient({
  baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000',
});

Phase 5: Generate React Hooks

// React hooks for data fetching
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import type { API } from './types';
import { apiClient } from './client';

/**
 * Hook to list users
 */
export function useUsers(params?: API.ListUsersParams) {
  return useQuery({
    queryKey: ['users', params],
    queryFn: () => apiClient.users.list(params),
  });
}

/**
 * Hook to get user by ID
 */
export function useUser(userId: string) {
  return useQuery({
    queryKey: ['users', userId],
    queryFn: () => apiClient.users.get(userId),
    enabled: !!userId,
  });
}

/**
 * Hook to create user
 */
export function useCreateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: API.CreateUserRequest) =>
      apiClient.users.create(data),
    onSuccess: () => {
      // Invalidate users list
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
}

/**
 * Hook to update user
 */
export function useUpdateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: ({
      userId,
      data,
    }: {
      userId: string;
      data: API.UpdateUserRequest;
    }) => apiClient.users.update(userId, data),
    onSuccess: (_, variables) => {
      // Invalidate specific user and users list
      queryClient.invalidateQueries({ queryKey: ['users', variables.userId] });
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
}

/**
 * Hook to delete user
 */
export function useDeleteUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (userId: string) => apiClient.users.delete(userId),
    onSuccess: () => {
      // Invalidate users list
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
}

/**
 * Hook to login
 */
export function useLogin() {
  return useMutation({
    mutationFn: (data: API.LoginRequest) => apiClient.auth.login(data),
    onSuccess: (response) => {
      // Store auth token
      apiClient.setAuthToken(response.token);
      localStorage.setItem('auth_token', response.token);
    },
  });
}

/**
 * Hook to logout
 */
export function useLogout() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: () => apiClient.auth.logout(),
    onSuccess: () => {
      // Clear auth token
      apiClient.clearAuthToken();
      localStorage.removeItem('auth_token');

      // Clear all queries
      queryClient.clear();
    },
  });
}

Phase 6: Usage Examples

// Example: Using the generated client in a React component
import { useUsers, useCreateUser } from './api/hooks';

export function UsersPage() {
  const { data, isLoading, error } = useUsers({ page: 1, limit: 10 });
  const createUser = useCreateUser();

  const handleCreateUser = async () => {
    try {
      await createUser.mutateAsync({
        email: 'user@example.com',
        name: 'John Doe',
        password: 'secure-password',
      });
      alert('User created!');
    } catch (error) {
      console.error('Failed to create user:', error);
    }
  };

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>Users</h1>
      <button onClick={handleCreateUser}>Create User</button>
      <ul>
        {data?.data.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}
// Example: Using the client directly (non-React)
import { apiClient } from './api/client';

async function fetchUsers() {
  try {
    const response = await apiClient.users.list({ page: 1, limit: 10 });
    console.log('Users:', response.data);
    console.log('Pagination:', response.pagination);
  } catch (error) {
    if (error instanceof APIError) {
      console.error(`API Error (${error.status}):`, error.message);
    } else {
      console.error('Unknown error:', error);
    }
  }
}

Summary

echo ""
echo "=== ✓ OpenAPI Type Generation Complete ==="
echo ""
echo "📁 Generated files:"
echo "  - src/api/types.ts           # TypeScript types"
echo "  - src/api/schemas.ts         # Zod validation schemas"
echo "  - src/api/client.ts          # Fetch client SDK"
echo "  - src/api/hooks.ts           # React hooks"
echo ""
echo "📦 Install dependencies:"
echo "  npm install zod @tanstack/react-query"
echo ""
echo "🚀 Usage:"
echo ""
echo "# In React components:"
echo "import { useUsers, useCreateUser } from './api/hooks';"
echo ""
echo "# Direct client usage:"
echo "import { apiClient } from './api/client';"
echo "const users = await apiClient.users.list();"
echo ""
echo "💡 Integration points:"
echo "  - /api-docs-generate - Generate OpenAPI spec"
echo "  - /api-test-generate - Generate API tests"
echo "  - /types-generate - Additional type generation"

Best Practices

Type Safety:

  • Generate types from single source of truth
  • Use Zod for runtime validation
  • Keep types in sync with API

Client SDK:

  • Type-safe methods
  • Proper error handling
  • Request/response validation

Integration Points:

  • /api-docs-generate - OpenAPI spec generation
  • /api-test-generate - API tests
  • /mock-generate - Mock data

What I'll Actually Do

  1. Find OpenAPI spec - Detect spec files
  2. Generate types - TypeScript interfaces
  3. Create Zod schemas - Runtime validation
  4. Build client SDK - Type-safe fetch client
  5. Generate hooks - React Query hooks
  6. Add examples - Usage documentation

Important: I will NEVER add AI attribution.

Credits: Based on openapi-typescript, Zod, React Query, and API client patterns from tRPC, GraphQL clients, and REST API best practices.

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.

Coding

cache-strategy

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

sessions-init

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

postman-convert

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

db-diagram

No summary provided by upstream source.

Repository SourceNeeds Review
openapi-types | V50.AI