Awesome-omni-skill API Development
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.
install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/backend/api-development" ~/.claude/skills/diegosouzapw-awesome-omni-skill-api-development && rm -rf "$T"
manifest:
skills/backend/api-development/SKILL.mdsource content
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
// ✅ GOOD: Specific status codes 200 // GET: Resource retrieved 201 // POST: Resource created 204 // DELETE: Resource deleted (no content) 400 // Bad Request: Validation failed 401 // Unauthorized: Not authenticated 403 // Forbidden: Authenticated but no permission 404 // Not Found: Resource doesn't exist 409 // Conflict: Duplicate email 422 // Unprocessable Entity: Semantic error 429 // Too Many Requests: Rate limited 500 // Internal Server Error: Server bug // ❌ BAD: Vague status codes 200 // Success response for everything 500 // Error response for everything 200 // Returned even when validation failed
Error Response Format (RFC 7807)
Problem Details Structure
// RFC 7807 Problem Details type ProblemDetails = { type: string; // URL to error type documentation title: string; // Short error title status: number; // HTTP status code detail: string; // Specific error details instance?: string; // Request ID for tracking errors?: Record<string, string[]>; // Field-level errors };
Implementation
// lib/errors.ts export 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
// ✅ 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
// middleware/error-handler.ts import { 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 error if (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
// app/api/users/route.ts import { errorHandler } from '@/middleware/error-handler'; export async function POST(request: Request) { try { const body = await request.json(); // Validate const validated = CreateUserSchema.parse(body); // Check duplicate const 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' ); } // Create const 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
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 request const validated = CreateUserSchema.parse(body);
Sanitization
import DOMPurify from 'isomorphic-dompurify'; const sanitized = { ...validated, name: DOMPurify.sanitize(validated.name) };
Rate Limiting
import rateLimit from 'express-rate-limit'; // General rate limiter const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // 100 requests per window message: 'Too many requests, please try again later', standardHeaders: true, // Return rate limit info in headers legacyHeaders: false }); // Auth rate limiter (stricter) const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, // 5 attempts skipSuccessfulRequests: true // Don't count successful logins }); app.post('/login', authLimiter, loginHandler); app.use('/api/', limiter);
Response Formatting
Success Response
// ✅ GOOD: Consistent response export 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 response export 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
// middleware/correlation-id.ts import { 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 logs console.log(`[${correlationId}] User created:`, user); // Client can track requests fetch('/api/users', { headers: { 'x-correlation-id': myRequestId } });
Anti-Patterns
// ❌ 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 validation const 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 the
parameter for thorough analysis.effort: high