Agent skill

rest-api-design

Design REST API endpoints with Zod validation and OpenAPI documentation. Use when creating new API routes, validating request/response schemas, or updating API documentation. Activates for endpoint design, schema validation, error handling, and API docs.

Stars 81
Forks 12

Install this agent skill to your Project

npx add-skill https://github.com/curiositech/some_claude_skills/tree/main/.claude/skills/rest-api-design

Metadata

Additional technical details for this skill

tags
api code validation documentation
category
Code Quality & Testing
pairs with
[
    {
        "skill": "api-architect",
        "reason": "REST API design is a specific implementation of broader API architecture principles"
    },
    {
        "skill": "openapi-spec-writer",
        "reason": "REST endpoints designed here are formalized as OpenAPI specifications for documentation"
    },
    {
        "skill": "form-validation-architect",
        "reason": "Client-side form validation schemas should mirror API request validation for consistency"
    }
]

SKILL.md

REST API Design

This skill helps you design and implement REST API endpoints following project patterns with Zod validation and OpenAPI documentation.

When to Use

USE this skill for:

  • Creating new REST API endpoints with Next.js App Router
  • Designing request/response schemas with Zod
  • Implementing proper error handling and status codes
  • Adding rate limiting and authentication
  • Generating OpenAPI documentation

DO NOT use for:

  • GraphQL APIs → different paradigm entirely
  • Cloudflare Workers → use cloudflare-worker-dev skill
  • Supabase Edge Functions → use Supabase docs
  • WebSocket/real-time APIs → different patterns

API Route Structure

src/app/api/
├── auth/           # Authentication endpoints
├── check-in/       # Daily check-in CRUD
├── chat/           # AI coaching chat
├── journal/        # Journal entries
├── admin/          # Admin-only endpoints
└── health/         # Health check

Standard Route Template

typescript
// src/app/api/[feature]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { getSession } from '@/lib/auth';
import { createRateLimiter } from '@/lib/rate-limit';
import { logPHIAccess } from '@/lib/hipaa/audit';
import { db } from '@/db';

// 1. Define schemas
const RequestSchema = z.object({
  field: z.string().min(1).max(1000),
  optional: z.string().optional(),
  enumField: z.enum(['option1', 'option2']),
  number: z.number().int().positive(),
});

const ResponseSchema = z.object({
  id: z.string(),
  createdAt: z.string().datetime(),
});

// 2. Configure rate limiter
const rateLimiter = createRateLimiter({
  windowMs: 60000,    // 1 minute
  maxRequests: 30,    // 30 requests per window
  keyPrefix: 'api:feature',
});

// 3. Implement handlers
export async function GET(request: NextRequest) {
  // Auth check
  const session = await getSession();
  if (!session) {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }

  // Rate limit
  const rateLimitResult = await rateLimiter.check(session.userId);
  if (!rateLimitResult.allowed) {
    return NextResponse.json(
      { error: 'Rate limit exceeded' },
      { status: 429, headers: rateLimitResult.headers }
    );
  }

  // Query data
  const data = await db.query.features.findMany({
    where: eq(features.userId, session.userId),
  });

  // Audit log (if PHI)
  await logPHIAccess(session.userId, 'feature', null, 'LIST');

  return NextResponse.json(data);
}

export async function POST(request: NextRequest) {
  // Auth check
  const session = await getSession();
  if (!session) {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }

  // Rate limit
  const rateLimitResult = await rateLimiter.check(session.userId);
  if (!rateLimitResult.allowed) {
    return NextResponse.json(
      { error: 'Rate limit exceeded' },
      { status: 429, headers: rateLimitResult.headers }
    );
  }

  // Parse and validate body
  let body: unknown;
  try {
    body = await request.json();
  } catch {
    return NextResponse.json(
      { error: 'Invalid JSON' },
      { status: 400 }
    );
  }

  const parsed = RequestSchema.safeParse(body);
  if (!parsed.success) {
    return NextResponse.json(
      {
        error: 'Validation failed',
        details: parsed.error.issues.map(i => ({
          path: i.path.join('.'),
          message: i.message,
        })),
      },
      { status: 400 }
    );
  }

  // Create resource
  const [created] = await db.insert(features).values({
    id: generateId(),
    userId: session.userId,
    ...parsed.data,
    createdAt: new Date(),
  }).returning();

  // Audit log
  await logPHIAccess(session.userId, 'feature', created.id, 'CREATE');

  return NextResponse.json(created, { status: 201 });
}

Zod Schema Patterns

Basic Types

typescript
import { z } from 'zod';

const Schema = z.object({
  // Strings
  name: z.string().min(1).max(100),
  email: z.string().email(),
  url: z.string().url(),
  uuid: z.string().uuid(),

  // Numbers
  count: z.number().int().positive(),
  rating: z.number().min(1).max(5),
  price: z.number().nonnegative(),

  // Booleans
  isActive: z.boolean(),

  // Dates
  date: z.string().datetime(),
  dateOnly: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),

  // Enums
  status: z.enum(['pending', 'approved', 'denied']),

  // Arrays
  tags: z.array(z.string()).min(1).max(10),

  // Optional fields
  notes: z.string().optional(),
  metadata: z.record(z.string()).optional(),

  // Nullable
  deletedAt: z.string().datetime().nullable(),
});

Advanced Patterns

typescript
// Discriminated unions
const EventSchema = z.discriminatedUnion('type', [
  z.object({ type: z.literal('click'), x: z.number(), y: z.number() }),
  z.object({ type: z.literal('keypress'), key: z.string() }),
]);

// Refinements
const PasswordSchema = z.string()
  .min(12, 'Password must be at least 12 characters')
  .regex(/[A-Z]/, 'Must contain uppercase')
  .regex(/[a-z]/, 'Must contain lowercase')
  .regex(/[0-9]/, 'Must contain number')
  .regex(/[^A-Za-z0-9]/, 'Must contain special character');

// Transform
const DateSchema = z.string()
  .datetime()
  .transform(str => new Date(str));

// Preprocess (coerce types)
const NumberFromString = z.preprocess(
  val => typeof val === 'string' ? parseInt(val, 10) : val,
  z.number()
);

Query Parameter Validation

typescript
export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);

  const QuerySchema = z.object({
    page: z.coerce.number().int().positive().default(1),
    limit: z.coerce.number().int().min(1).max(100).default(20),
    sort: z.enum(['asc', 'desc']).default('desc'),
    status: z.enum(['all', 'active', 'archived']).optional(),
  });

  const query = QuerySchema.safeParse({
    page: searchParams.get('page'),
    limit: searchParams.get('limit'),
    sort: searchParams.get('sort'),
    status: searchParams.get('status'),
  });

  if (!query.success) {
    return NextResponse.json(
      { error: 'Invalid query parameters', details: query.error.issues },
      { status: 400 }
    );
  }

  const { page, limit, sort, status } = query.data;
  // Use validated params...
}

Error Response Format

typescript
// Standard error response
interface APIError {
  error: string;           // Human-readable message
  code?: string;           // Machine-readable code
  details?: ErrorDetail[]; // Validation details
}

interface ErrorDetail {
  path: string;
  message: string;
}

// Error responses
return NextResponse.json(
  { error: 'Not found', code: 'NOT_FOUND' },
  { status: 404 }
);

return NextResponse.json(
  {
    error: 'Validation failed',
    code: 'VALIDATION_ERROR',
    details: [
      { path: 'email', message: 'Invalid email format' },
    ],
  },
  { status: 400 }
);

HTTP Status Codes

Code Use Case
200 Successful GET, PUT, PATCH
201 Successful POST (created)
204 Successful DELETE (no content)
400 Invalid request/validation error
401 Not authenticated
403 Not authorized (authenticated but forbidden)
404 Resource not found
409 Conflict (duplicate, etc.)
429 Rate limit exceeded
500 Server error

OpenAPI Documentation

Update docs/openapi.yaml when adding endpoints:

yaml
paths:
  /api/feature:
    get:
      summary: List features
      tags: [Features]
      security:
        - cookieAuth: []
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Feature'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      summary: Create feature
      tags: [Features]
      security:
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateFeatureRequest'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Feature'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'

components:
  schemas:
    Feature:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        createdAt:
          type: string
          format: date-time
      required: [id, name, createdAt]

    CreateFeatureRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
      required: [name]

  responses:
    Unauthorized:
      description: Not authenticated
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
                example: Unauthorized

    ValidationError:
      description: Validation failed
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
              details:
                type: array
                items:
                  type: object
                  properties:
                    path:
                      type: string
                    message:
                      type: string

Route Handler Patterns

Dynamic Routes

typescript
// src/app/api/feature/[id]/route.ts
export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;

  // Validate ID format
  if (!isValidUUID(id)) {
    return NextResponse.json(
      { error: 'Invalid ID format' },
      { status: 400 }
    );
  }

  const item = await db.query.features.findFirst({
    where: eq(features.id, id),
  });

  if (!item) {
    return NextResponse.json(
      { error: 'Not found' },
      { status: 404 }
    );
  }

  return NextResponse.json(item);
}

Pagination

typescript
interface PaginatedResponse<T> {
  data: T[];
  pagination: {
    page: number;
    limit: number;
    total: number;
    totalPages: number;
  };
}

async function getPaginated(page: number, limit: number) {
  const offset = (page - 1) * limit;

  const [data, [{ count }]] = await Promise.all([
    db.query.features.findMany({
      limit,
      offset,
      orderBy: desc(features.createdAt),
    }),
    db.select({ count: count() }).from(features),
  ]);

  return {
    data,
    pagination: {
      page,
      limit,
      total: count,
      totalPages: Math.ceil(count / limit),
    },
  };
}

References

Expand your agent's capabilities with these related and highly-rated skills.

curiositech/some_claude_skills

3d-cv-labeling-2026

Expert in 3D computer vision labeling tools, workflows, and AI-assisted annotation for LiDAR, point clouds, and sensor fusion. Covers SAM4D/Point-SAM, human-in-the-loop architectures, and vertical-specific training strategies. Activate on '3D labeling', 'point cloud annotation', 'LiDAR labeling', 'SAM 3D', 'SAM4D', 'sensor fusion annotation', '3D bounding box', 'semantic segmentation point cloud'. NOT for 2D image labeling (use clip-aware-embeddings), general ML training (use ml-engineer), video annotation without 3D (use computer-vision-pipeline), or VLM prompt engineering (use prompt-engineer).

81 12
Explore
curiositech/some_claude_skills

project-management-guru-adhd

Expert project manager for ADHD engineers managing multiple concurrent projects. Specializes in hyperfocus management, context-switching minimization, and parakeet-style gentle reminders. Activate on 'ADHD project management', 'context switching', 'hyperfocus', 'task prioritization', 'multiple projects', 'productivity for ADHD', 'task chunking', 'deadline management'. NOT for neurotypical project management, rigid waterfall processes, or general productivity advice without ADHD context.

81 12
Explore
curiositech/some_claude_skills

large-scale-map-visualization

Master of high-performance web map implementations handling 5,000-100,000+ geographic data points. Specializes in Leaflet.js optimization, Supercluster algorithms, viewport-based loading, canvas rendering, and progressive disclosure UX patterns.

81 12
Explore
curiositech/some_claude_skills

adhd-design-expert

Designs digital experiences for ADHD brains using neuroscience research and UX principles. Expert in reducing cognitive load, time blindness solutions, dopamine-driven engagement, and compassionate design patterns. Activate on 'ADHD design', 'cognitive load', 'accessibility', 'neurodivergent UX', 'time blindness', 'dopamine-driven', 'executive function'. NOT for general accessibility (WCAG only), neurotypical UX design, or simple UI styling without ADHD context.

81 12
Explore
curiositech/some_claude_skills

liaison

Translate multi-agent ecosystem activity into human-readable status briefings, decision requests, and progress summaries. Use for 'status update', 'brief me', 'what happened', 'summarize progress'. NOT for project planning (use project-management-guru-adhd), code review, or technical documentation.

81 12
Explore
curiositech/some_claude_skills

windows-95-web-designer

Modern web applications with authentic Windows 95 aesthetic. Gradient title bars, Start menu paradigm, taskbar patterns, 3D beveled chrome. Extrapolates Win95 to AI chatbots, mobile UIs, responsive layouts. Activate on 'windows 95', 'win95', 'start menu', 'taskbar', 'retro desktop', '95 aesthetic', 'clippy'. NOT for Windows 3.1 (use windows-3-1-web-designer), vaporwave/synthwave, macOS, flat design.

81 12
Explore

Didn't find tool you were looking for?

Be as detailed as possible for better results