<< All versions
Skill v1.0.0
Automated scan100/100thamjiahe/claude-code-handbook/examples
──Details
PublishedApril 28, 2026 at 04:52 AM
Content Hashsha256:e1489fbfe053bd90...
Git SHA4dedd940c1a2
──Files
Files (1 file, 10.1 KB)
SKILL.md10.1 KBactive
SKILL.md · 423 lines · 10.1 KB
name: "API Development" description: "Build REST APIs with proper error handling, status codes, request validation, response formatting, and rate limiting. Apply when creating API routes, handling errors, validating input, or designing API responses." allowed-tools: Read, Write, Edit, Bash version: 1.1.0 compatibility: Claude Opus 4.5, Claude Code v2.x updated: 2026-01-24
API Development
Systematic REST API development with error handling, validation, and consistent response formats.
Overview
This Skill enforces:
- HTTP status codes (appropriate, not overused)
- RFC 7807 Problem Details for errors
- Input validation and sanitization
- Consistent response formatting
- Request correlation IDs
- Rate limiting
- Security-first error messages
- Centralized error handling
Apply when building API routes, handling errors, or designing responses.
HTTP Status Codes
Status Code Categories
| Range | Purpose | Common Examples | |
|---|---|---|---|
| 200-299 | Success | 200 OK, 201 Created, 204 No Content | |
| 300-399 | Redirection | 301 Moved Permanently, 302 Found | |
| 400-499 | Client Errors | 400 Bad Request, 401 Unauthorized, 404 Not Found | |
| 500-599 | Server Errors | 500 Internal Error, 503 Service Unavailable |
Correct Status Codes
ts
// ✅ GOOD: Specific status codes200 // GET: Resource retrieved201 // POST: Resource created204 // DELETE: Resource deleted (no content)400 // Bad Request: Validation failed401 // Unauthorized: Not authenticated403 // Forbidden: Authenticated but no permission404 // Not Found: Resource doesn't exist409 // Conflict: Duplicate email422 // Unprocessable Entity: Semantic error429 // Too Many Requests: Rate limited500 // Internal Server Error: Server bug// ❌ BAD: Vague status codes200 // Success response for everything500 // Error response for everything200 // Returned even when validation failed
Error Response Format (RFC 7807)
Problem Details Structure
ts
// RFC 7807 Problem Detailstype ProblemDetails = {type: string; // URL to error type documentationtitle: string; // Short error titlestatus: number; // HTTP status codedetail: string; // Specific error detailsinstance?: string; // Request ID for trackingerrors?: Record<string, string[]>; // Field-level errors};
Implementation
ts
// lib/errors.tsexport class ApiError extends Error {constructor(public status: number,public title: string,public detail: string,public type: string = 'about:blank',public errors?: Record<string, string[]>) {super(detail);this.name = 'ApiError';}toJSON() {return {type: this.type,title: this.title,status: this.status,detail: this.detail,instance: this.instance,...(this.errors && { errors: this.errors })};}}
Error Responses
ts
// ✅ GOOD: RFC 7807 format{"type": "https://api.example.com/errors/validation-failed","title": "Validation Failed","status": 400,"detail": "The request body contains invalid data","instance": "req-12345","errors": {"email": ["Invalid email format"],"age": ["Must be >= 18"]}}// ✅ GOOD: Unauthorized (no sensitive details){"type": "https://api.example.com/errors/unauthorized","title": "Unauthorized","status": 401,"detail": "Authentication required","instance": "req-12346"}// ❌ BAD: Leaks internal details{"error": "User not found in database","stack": "Error: query failed at line 42..."}// ❌ BAD: Not structured{"message": "Something went wrong"}
Centralized Error Handler
ts
// middleware/error-handler.tsimport { NextRequest, NextResponse } from 'next/server';import { ApiError } from '@/lib/errors';export function errorHandler(error: unknown) {const requestId = crypto.randomUUID();// Log error (internal, never exposed)console.error(`[${requestId}] Error:`, error);// ApiError (predictable)if (error instanceof ApiError) {return NextResponse.json({type: error.type,title: error.title,status: error.status,detail: error.detail,instance: requestId,...(error.errors && { errors: error.errors })},{ status: error.status });}// Validation errorif (error instanceof ZodError) {return NextResponse.json({type: 'https://api.example.com/errors/validation-failed',title: 'Validation Failed',status: 400,detail: 'The request body contains invalid data',instance: requestId,errors: error.flatten().fieldErrors},{ status: 400 });}// Unknown error (generic message)return NextResponse.json({type: 'https://api.example.com/errors/internal-server-error',title: 'Internal Server Error',status: 500,detail: 'An unexpected error occurred',instance: requestId},{ status: 500 });}
Using Error Handler
ts
// app/api/users/route.tsimport { errorHandler } from '@/middleware/error-handler';export async function POST(request: Request) {try {const body = await request.json();// Validateconst validated = CreateUserSchema.parse(body);// Check duplicateconst existing = await db.user.findUnique({where: { email: validated.email }});if (existing) {throw new ApiError(409,'Conflict','A user with this email already exists','https://api.example.com/errors/duplicate-email');}// Createconst user = await db.user.create({ data: validated });return new Response(JSON.stringify(user), {status: 201,headers: { 'Content-Type': 'application/json' }});} catch (error) {return errorHandler(error);}}
Input Validation
Schema Validation
ts
import { z } from 'zod';const CreateUserSchema = z.object({email: z.string().email('Invalid email format'),name: z.string().min(1, 'Name required').max(255),age: z.number().int().min(0).max(150),role: z.enum(['admin', 'user', 'guest']).default('user')});// Validate requestconst validated = CreateUserSchema.parse(body);
Sanitization
ts
import DOMPurify from 'isomorphic-dompurify';const sanitized = {...validated,name: DOMPurify.sanitize(validated.name)};
Rate Limiting
ts
import rateLimit from 'express-rate-limit';// General rate limiterconst limiter = rateLimit({windowMs: 15 * 60 * 1000, // 15 minutesmax: 100, // 100 requests per windowmessage: 'Too many requests, please try again later',standardHeaders: true, // Return rate limit info in headerslegacyHeaders: false});// Auth rate limiter (stricter)const authLimiter = rateLimit({windowMs: 15 * 60 * 1000,max: 5, // 5 attemptsskipSuccessfulRequests: true // Don't count successful logins});app.post('/login', authLimiter, loginHandler);app.use('/api/', limiter);
Response Formatting
Success Response
ts
// ✅ GOOD: Consistent responseexport async function GET(request: Request) {const users = await db.user.findMany();return NextResponse.json({status: 'success',data: users,meta: {count: users.length,timestamp: new Date().toISOString()}});}// ✅ GOOD: Paginated responseexport async function GET(request: Request) {const page = parseInt(request.nextUrl.searchParams.get('page') || '1');const limit = parseInt(request.nextUrl.searchParams.get('limit') || '20');const offset = (page - 1) * limit;const [users, total] = await Promise.all([db.user.findMany({ skip: offset, take: limit }),db.user.count()]);return NextResponse.json({status: 'success',data: users,meta: {pagination: {page,limit,total,pages: Math.ceil(total / limit)}}});}
Request Correlation
ts
// middleware/correlation-id.tsimport { NextResponse } from 'next/server';import type { NextRequest } from 'next/server';export function middleware(request: NextRequest) {const correlationId =request.headers.get('x-correlation-id') ||crypto.randomUUID();const response = NextResponse.next();response.headers.set('x-correlation-id', correlationId);return response;}// Include in logsconsole.log(`[${correlationId}] User created:`, user);// Client can track requestsfetch('/api/users', {headers: { 'x-correlation-id': myRequestId }});
Anti-Patterns
ts
// ❌ BAD: Leaking stack traces{"error": "Cannot read property 'id' of undefined at getUserData (line 42)","stack": "Error: ...\nat app.js:42..."}// ❌ BAD: Generic error message{"error": "Something went wrong"}// ❌ BAD: No rate limiting// Anyone can hammer API endpoint// ❌ BAD: Overusing 500// Always return 500 for any error// ❌ BAD: No validationconst user = await db.user.create(request.body);// Raw user input!
Verification Before Production
- [ ] HTTP status codes specific and appropriate
- [ ] Error responses RFC 7807 compliant
- [ ] No stack traces or sensitive data exposed
- [ ] Input validated on server side
- [ ] Input sanitized before storage
- [ ] Rate limiting configured
- [ ] Correlation IDs for request tracking
- [ ] Error messages user-friendly (not technical)
- [ ] Centralized error handler
- [ ] Response format consistent
Integration with Project Standards
Enforces security and usability:
- S-1: No sensitive data in errors
- C-10: Input validated
- AP-8: Validation on server side
Resources
- RFC 7807 Problem Details: https://tools.ietf.org/html/rfc7807
- HTTP Status Codes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
- Express Rate Limiting: https://github.com/nfriedly/express-rate-limit
Last Updated: January 24, 2026 Compatibility: Claude Opus 4.5, Claude Code v2.x Status: Production Ready
January 2026 Update: This skill is compatible with Claude Opus 4.5 and Claude Code v2.x. For complex tasks, use theeffort: highparameter for thorough analysis.