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.
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-devskill - 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
// 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
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
// 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
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
// 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:
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
// 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
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
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated 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).
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.
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.
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.
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.
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.
Didn't find tool you were looking for?