Awesome-omni-skill api-design
Design and review REST/GraphQL APIs with best practices. Use when creating endpoints, reviewing API architecture, designing data contracts, route handlers, Server Actions, webhooks, or when user mentions "API", "endpoint", "REST", "GraphQL", "route handler", "request/response", or "HTTP methods".
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/api-design-kensaurus" ~/.claude/skills/diegosouzapw-awesome-omni-skill-api-design-19afe8 && rm -rf "$T"
skills/development/api-design-kensaurus/SKILL.mdAPI Design Skill
Systematic methodology for designing, reviewing, and improving APIs following industry best practices.
When to Use
- Designing new API endpoints
- Reviewing existing API architecture
- Creating data contracts between services
- Standardizing API patterns across a codebase
- Migrating or versioning APIs
CRITICAL: Check Existing First
Before creating ANY new endpoint, verify:
- Search for existing endpoints:
rg "export.*GET|POST|PATCH|DELETE" app/api/ --type ts ls -la app/api/
- Check for existing Server Actions:
rg "'use server'" --type ts -l rg "export async function" src/features/*/server/
- Check existing RPC functions (Supabase):
SELECT proname FROM pg_proc WHERE pronamespace = 'public'::regnamespace;
- Review existing patterns:
- Check
folder structureapp/api/ - Look at existing error handling patterns
- Verify authentication patterns used
Why: Duplicate endpoints cause routing conflicts, inconsistent behavior, and maintenance nightmares. Always search before creating.
API Design Principles
1. RESTful Resource Design
URL Structure:
GET /resources → List resources GET /resources/:id → Get single resource POST /resources → Create resource PATCH /resources/:id → Partial update PUT /resources/:id → Full replace DELETE /resources/:id → Delete resource # Nested resources GET /users/:id/posts → User's posts POST /users/:id/posts → Create post for user # Actions (when CRUD doesn't fit) POST /orders/:id/cancel → Cancel order POST /users/:id/verify → Verify user
Naming Conventions:
- Use plural nouns for resources (
, not/users
)/user - Use kebab-case for multi-word (
)/user-profiles - Avoid verbs in URLs (let HTTP methods convey action)
- Keep URLs shallow (max 2-3 levels deep)
2. Request/Response Design
Request Structure:
// Query params for filtering/sorting/pagination GET /products?category=electronics&sort=-price&page=2&limit=20 // Body for mutations POST /products { "name": "Widget", "price": 29.99, "categoryId": "cat_123" } // Partial updates with PATCH PATCH /products/123 { "price": 24.99 // Only changed fields }
Response Structure (Consistent Envelope):
// Success response { "data": { /* resource or array */ }, "meta": { "pagination": { "page": 1, "limit": 20, "total": 100 } } } // Error response { "error": { "code": "VALIDATION_ERROR", "message": "Invalid input", "details": [ { "field": "email", "message": "Invalid email format" } ] } }
3. HTTP Status Codes
| Code | Meaning | When to Use |
|---|---|---|
| 200 | OK | Successful GET, PATCH, PUT |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid input, validation error |
| 401 | Unauthorized | Missing/invalid auth |
| 403 | Forbidden | Auth valid but no permission |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate, state conflict |
| 422 | Unprocessable | Semantic validation error |
| 429 | Too Many Requests | Rate limited |
| 500 | Internal Error | Server error (never expose details) |
4. Pagination Patterns
Offset-based (simple but slow at scale):
GET /posts?page=5&limit=20 { "data": [...], "meta": { "pagination": { "page": 5, "limit": 20, "total": 543, "totalPages": 28 } } }
Cursor-based (better for large datasets):
GET /posts?cursor=eyJpZCI6MTIzfQ&limit=20 { "data": [...], "meta": { "pagination": { "nextCursor": "eyJpZCI6MTQzfQ", "prevCursor": "eyJpZCI6MTAzfQ", "hasMore": true } } }
5. Filtering & Sorting
// Filtering GET /products?status=active&minPrice=10&maxPrice=100 GET /products?category[in]=electronics,books GET /products?createdAt[gte]=2024-01-01 // Sorting GET /products?sort=price // Ascending GET /products?sort=-price // Descending GET /products?sort=-createdAt,name // Multiple // Field selection (reduce payload) GET /users?fields=id,name,email
6. Versioning Strategies
URL versioning (recommended for breaking changes):
/api/v1/users /api/v2/users
Header versioning (cleaner URLs):
Accept: application/vnd.api+json; version=2
When to version:
- Breaking changes to response structure
- Removing fields or endpoints
- Changing field types
- NOT for additive changes (new optional fields)
7. Authentication Patterns
Bearer Token (JWT):
headers: { 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIs...' }
API Key (service-to-service):
headers: { 'X-API-Key': 'sk_live_abc123...' }
Security Checklist:
- Auth on every endpoint (except public)
- Rate limiting per user/IP
- Request validation before processing
- No sensitive data in URLs (use headers/body)
- HTTPS only
8. Error Handling
Consistent Error Schema:
interface ApiError { error: { code: string // Machine-readable (VALIDATION_ERROR) message: string // Human-readable details?: ErrorDetail[] // Field-level errors requestId?: string // For debugging } } interface ErrorDetail { field: string message: string code?: string }
Implementation:
// Zod validation errors → API errors function formatZodError(error: ZodError): ApiError { return { error: { code: 'VALIDATION_ERROR', message: 'Invalid request data', details: error.errors.map(e => ({ field: e.path.join('.'), message: e.message, })) } } }
9. Next.js Route Handler Patterns
// app/api/products/route.ts import { z } from 'zod' import { NextRequest, NextResponse } from 'next/server' const CreateProductSchema = z.object({ name: z.string().min(1).max(200), price: z.number().positive(), }) export async function POST(request: NextRequest) { try { const body = await request.json() const validated = CreateProductSchema.parse(body) const product = await db.product.create({ data: validated }) return NextResponse.json( { data: product }, { status: 201 } ) } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json( formatZodError(error), { status: 400 } ) } return NextResponse.json( { error: { code: 'INTERNAL_ERROR', message: 'Something went wrong' } }, { status: 500 } ) } }
10. Server Actions vs Route Handlers (Next.js 15+)
| Use Case | Recommendation |
|---|---|
| Internal mutations | Server Actions |
| Webhooks from external services | Route Handlers |
| Third-party API proxy | Route Handlers |
| File uploads | Route Handlers (or Server Actions with ) |
| Streaming responses | Route Handlers |
| Form submissions | Server Actions |
| Background tasks | Server Actions + from next/server |
Server Action Example (Next.js 15):
// app/actions.ts 'use server' import { revalidatePath } from 'next/cache' import { after } from 'next/server' export async function createProduct(formData: FormData) { const product = await db.product.create({ data: { name: formData.get('name') } }) // Run after response (Next.js 15) after(async () => { await sendNotification(product.id) }) revalidatePath('/products') return { success: true, data: product } }
API Review Checklist
- URLs follow RESTful conventions
- HTTP methods used correctly
- Status codes are appropriate
- Request/response schemas defined (Zod)
- Error responses consistent
- Pagination implemented for lists
- Authentication required where needed
- Rate limiting in place
- Input validation on all endpoints
- No sensitive data in URLs
- Versioning strategy defined
- OpenAPI/Swagger docs generated
API Documentation Template
# openapi.yaml openapi: 3.0.0 info: title: Product API version: 1.0.0 paths: /products: get: summary: List products parameters: - name: page in: query schema: type: integer default: 1 responses: 200: description: Success content: application/json: schema: $ref: '#/components/schemas/ProductList'