Unified API design + ops toolkit — REST/GraphQL design with OpenAPI specs, error handling (RFC 7807, codes, retry-after), versioning (URL/header/query, deprecation, migration), caching (HTTP headers, ETag, SWR, CDN), rate design, throttling, API gateway routing, request composition/aggregation. Single entry point for API contract + operational concerns.
Absorbs
api-designer
api-error-handling
api-versioning
api-caching
api-rate-design
api-throttling
api-gateway
api-composition
From api-designer
REST and GraphQL API design with OpenAPI specifications, versioning strategies, pagination, and error contracts
API Designer
Purpose
This skill designs APIs that are consistent, predictable, well-documented, and a pleasure to consume. It covers REST API design with OpenAPI specifications, GraphQL schema design, error handling contracts, pagination strategies, versioning, and authentication patterns.
Key Concepts
REST Design Principles
1. RESOURCES, NOT ACTIONS:
Good: GET /users/123 (fetch user)
Bad: GET /getUser?id=123 (RPC-style)
2. HTTP METHODS HAVE MEANING:
GET -> Read (idempotent, safe)
POST -> Create (not idempotent)
PUT -> Full replace (idempotent)
PATCH -> Partial update (idempotent)
DELETE -> Remove (idempotent)
3. STATUS CODES HAVE MEANING:
2xx -> Success
3xx -> Redirect
4xx -> Client error (your fault)
5xx -> Server error (our fault)
4. URLS ARE NOUNS, NOT VERBS:
Good: POST /orders (create order)
Bad: POST /createOrder
5. COLLECTIONS ARE PLURAL:
Good: /users, /orders, /products
Bad: /user, /order, /product
6. NESTING FOR RELATIONSHIPS:
/users/123/orders (orders belonging to user 123)
/orders/456/items (items in order 456)
Limit nesting to 2 levels max
HTTP Status Code Guide
SUCCESS:
200 OK -> GET, PUT, PATCH (with response body)
201 Created -> POST (resource created, include Location header)
204 No Content -> DELETE, PUT/PATCH (no response body needed)
CLIENT ERRORS:
400 Bad Request -> Malformed request, validation failure
401 Unauthorized -> Missing or invalid authentication
403 Forbidden -> Authenticated but not authorized
404 Not Found -> Resource does not exist
405 Not Allowed -> HTTP method not supported for this resource
409 Conflict -> Resource state conflict (duplicate, version mismatch)
422 Unprocessable -> Request is well-formed but semantically invalid
429 Too Many Req -> Rate limit exceeded
SERVER ERRORS:
500 Internal Error -> Unexpected server failure
502 Bad Gateway -> Upstream service failure
503 Unavailable -> Service temporarily down (maintenance, overload)
504 Gateway Timeout -> Upstream service timeout
GraphQL Schema Design
# Types map to domain resourcestype User {id: ID!email: String!name: String!role: Role!
posts(first: Int =10, after: String): PostConnection!createdAt: DateTime!}# Connections for pagination (Relay spec)type PostConnection {edges:[PostEdge!]!pageInfo: PageInfo!totalCount: Int!}type PostEdge {node: Post!cursor: String!}type PageInfo {hasNextPage: Boolean!hasPreviousPage: Boolean!startCursor: String
endCursor: String
}# Input types for mutationsinput CreateUserInput {email: String!name: String!role: Role = MEMBER
}# Mutations return payloads, not raw typestype CreateUserPayload {user: User
userErrors:[UserError!]!}type UserError {field:[String!]message: String!code: UserErrorCode!}typeMutation{
createUser(input: CreateUserInput!): CreateUserPayload!}
API Design Workflow
Phase 1: Resource Identification
DOMAIN: E-commerce platform
RESOURCES:
users -> Customer accounts
products -> Items for sale
orders -> Purchase transactions
order-items -> Line items within an order
reviews -> Product reviews by users
categories -> Product categorization
RELATIONSHIPS:
user -> has many -> orders
order -> has many -> order-items
order-item -> belongs to -> product
product -> has many -> reviews
product -> belongs to many -> categories
Phase 2: Endpoint Design
RESOURCE: orders
LIST: GET /api/v1/orders -> 200 OrderList
CREATE: POST /api/v1/orders -> 201 Order
READ: GET /api/v1/orders/:id -> 200 Order
UPDATE: PATCH /api/v1/orders/:id -> 200 Order
DELETE: DELETE /api/v1/orders/:id -> 204 (no body)
SUB-RESOURCES:
GET /api/v1/orders/:id/items -> 200 OrderItemList
POST /api/v1/orders/:id/items -> 201 OrderItem
ACTIONS (non-CRUD operations):
POST /api/v1/orders/:id/cancel -> 200 Order
POST /api/v1/orders/:id/refund -> 200 Refund
FILTERING:
GET /api/v1/orders?status=pending&sort=-created_at&limit=20
Phase 3: Request/Response Schema
Order:type:objectrequired: [id, userId, status, items, total, createdAt]
properties:id:type:stringformat:uuiduserId:type:stringformat:uuidstatus:type:stringenum: [draft, pending, confirmed, shipped, delivered, cancelled]
items:type:arrayitems:$ref:"#/components/schemas/OrderItem"total:type:objectproperties:amount:type:integerdescription:"Amount in smallest currency unit (cents)"currency:type:stringcreatedAt:type:stringformat:date-time
Error Response Contract
Standard Error Format
{"error":{"code":"VALIDATION_ERROR","message":"The request body contains invalid fields","details":[{"field":"items[0].quantity","message":"Must be a positive integer","value":-1}],"requestId":"req_abc123","timestamp":"2026-03-02T10:30:00Z","docs":"https://api.example.com/docs/errors#VALIDATION_ERROR"}}
Error Code Catalog
AUTHENTICATION:
AUTH_REQUIRED 401 "Authentication is required"
AUTH_INVALID_TOKEN 401 "The provided token is invalid or expired"
AUTH_INSUFFICIENT_SCOPE 403 "Token lacks required scope: {scope}"
VALIDATION:
VALIDATION_ERROR 400 "Request validation failed" (with details[])
INVALID_JSON 400 "Request body is not valid JSON"
MISSING_FIELD 400 "Required field '{field}' is missing"
RESOURCES:
NOT_FOUND 404 "Resource '{type}' with id '{id}' not found"
ALREADY_EXISTS 409 "Resource with {field}='{value}' already exists"
STATE:
INVALID_STATE 409 "Cannot {action} when status is {status}"
RATE_LIMITED 429 "Rate limit exceeded. Retry after {seconds} seconds"
SERVER:
INTERNAL_ERROR 500 "An unexpected error occurred"
SERVICE_UNAVAILABLE 503 "Service temporarily unavailable"
Advantages: Consistent under concurrent writes, performant (no OFFSET), works with real-time data.
Disadvantages: Cannot jump to arbitrary page, cursor is opaque.
Verbs in URLs: /createUser, /deleteOrder -- use HTTP methods instead
Inconsistent naming: Mixing camelCase and snake_case in the same API
Exposing internal IDs: Auto-increment database IDs leak information -- use UUIDs
No pagination: Returning unbounded lists will crash clients or servers
Inconsistent error format: Different error shapes from different endpoints
Breaking changes without versioning: Removing or renaming fields breaks consumers
Over-nesting: /users/123/orders/456/items/789/reviews is too deep -- flatten or use query parameters
Best Practices
Use data-modeling to align API resource schemas with database schemas
Use error-handling to implement the error response contract in application code
Use mermaid to generate sequence diagrams documenting key API flows
Generate OpenAPI specs and validate them with tools like spectral or openapi-generator
Keep request/response schemas tight: require what you need, reject what you do not
Use additionalProperties: false in JSON Schema to prevent extra fields sneaking through
From api-error-handling
API error handling patterns — error codes, RFC 7807 Problem Details, error boundaries, retry logic
API Error Handling
Purpose
Design consistent, machine-readable API error responses using RFC 7807 Problem Details, typed error classes, structured error codes, and resilient client-side error handling with retry logic. Covers both server-side error production and client-side error consumption.
Key Patterns
RFC 7807 Problem Details
Standard error response format:
// types/error.tsinterfaceProblemDetails {
type: string; // URI reference identifying the error typetitle: string; // Short, human-readable summarystatus: number; // HTTP status codedetail?: string; // Human-readable explanation specific to this occurrenceinstance?: string; // URI reference for this specific occurrence
[key: string]: unknown; // Extension fields
}
// Example response:// {// "type": "https://api.example.com/errors/insufficient-funds",// "title": "Insufficient Funds",// "status": 422,// "detail": "Account balance is $10.00 but transaction requires $25.00",// "instance": "/transactions/txn_abc123",// "balance": 1000,// "required": 2500,// "currency": "USD"// }
// hooks/use-api-error.tsimport { useQueryClient } from'@tanstack/react-query';
import { toast } from'sonner';
exportfunctionuseApiErrorHandler() {
const queryClient = useQueryClient();
return(error: unknown) => {
if (error instanceofApiClientError) {
const { problem } = error;
switch (problem.status) {
case401:
// Redirect to login
queryClient.clear();
window.location.href = '/login';
break;
case403:
toast.error('You do not have permission to perform this action');
break;
case404:
toast.error(problem.detail ?? 'Resource not found');
break;
case422:
// Validation errors — handled by formbreak;
case429:
toast.error('Too many requests. Please try again later.');
break;
default:
toast.error(problem.detail ?? 'Something went wrong');
}
} else {
toast.error('Network error. Please check your connection.');
}
};
}
Best Practices
Always return application/problem+json — Use RFC 7807 for all error responses. Clients can parse errors consistently.
Never expose stack traces — Log full errors server-side; return only safe, user-facing messages to clients.
Use specific error codes — auth-token-expired is actionable; error is not. Clients need codes to handle errors programmatically.
Include Retry-After for 429s — Tell clients exactly when to retry instead of making them guess.
Validate early, fail fast — Use Zod at the API boundary to catch bad input before business logic runs.
Use exponential backoff with jitter — Prevents retry storms. Always cap with a max delay.
Separate client errors from server errors — 4xx = client's fault (do not retry), 5xx = server's fault (may retry).
Log correlation IDs — Include a request ID in error responses and logs for debugging: instance: "/api/orders/req_abc123".
Common Pitfalls
Pitfall
Problem
Fix
Generic error messages
Client cannot determine what went wrong or how to fix it
Use specific error codes and detailed messages
Exposing internal errors
Stack traces and DB errors leak implementation details
Catch all errors; return generic 500 for unknowns
No retry logic for transient failures
Temporary network/server issues cause permanent failures
Implement retry with exponential backoff for 5xx and 429
Retrying non-idempotent requests
POST retries create duplicate resources
Only auto-retry GET/PUT/DELETE; use idempotency keys for POST
Inconsistent error format
Different endpoints return errors in different shapes
Use middleware to normalize all errors to Problem Details
Swallowing errors silently
Bugs hidden, users confused by blank failures
Always surface errors to users; always log server-side
Missing validation error field paths
User does not know which field failed
Include field path in validation error details
Caching error responses
CDN serves 500 to all users until TTL expires
Set Cache-Control: no-store on all error responses
From api-versioning
API versioning strategies — URL path, header, query parameter approaches. Version lifecycle management, deprecation policies, backward compatibility, and migration planning
API Versioning Specialist
Purpose
API versioning is the discipline of evolving an API without breaking existing consumers. A wrong versioning strategy causes client outages, support burden, and migration nightmares. This skill covers the three major strategies, when to use each, how to implement version routing, deprecation lifecycle, and backward-compatible evolution patterns that often avoid versioning entirely.
Key Concepts
The Three Strategies
Strategy
Example
Pros
Cons
URL Path
/api/v2/users
Explicit, easy to route, cacheable
URL pollution, hard to sunset
Header
Accept: application/vnd.api.v2+json
Clean URLs, content negotiation
Hidden, harder to test in browser
Query Param
/api/users?version=2
Easy to test, explicit
Pollutes query string, cache key issues
When to Version vs Evolve
Can you make the change WITHOUT breaking existing clients?
|-- YES -> Do NOT version. Use backward-compatible evolution.
| |-- Add new fields (old clients ignore them)
| |-- Add new endpoints
| |-- Add optional parameters
| +-- Use feature flags
+-- NO -> Version the API.
|-- Removing or renaming fields
|-- Changing field types
|-- Changing response structure
|-- Changing authentication flow
+-- Changing error format
Design and implement effective API caching at every layer — HTTP cache headers, conditional requests with ETags, stale-while-revalidate patterns, CDN edge caching, and Next.js ISR. Reduces latency, lowers origin load, and improves user experience.
Key Patterns
HTTP Cache Headers
Cache-Control directives:
// Next.js API route with proper cache headersimport { NextRequest, NextResponse } from'next/server';
// Public, cacheable by CDN and browserexportasyncfunctionGET(request: NextRequest) {
const data = awaitfetchProducts();
returnNextResponse.json(data, {
headers: {
// CDN + browser cache for 60s, serve stale up to 1 hour while revalidating'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=3600',
},
});
}
// Private, user-specific data — no CDN cachingexportasyncfunctionGET(request: NextRequest) {
const user = awaitgetAuthenticatedUser(request);
const profile = awaitfetchProfile(user.id);
returnNextResponse.json(profile, {
headers: {
// Browser-only cache, 5 min, must revalidate after'Cache-Control': 'private, max-age=300, must-revalidate',
},
});
}
// No caching — real-time dataexportasyncfunctionGET() {
const liveData = awaitfetchLiveMetrics();
returnNextResponse.json(liveData, {
headers: {
'Cache-Control': 'no-store',
},
});
}
Cache-Control cheat sheet:
Directive
Meaning
public
CDN and browser can cache
private
Browser only, no CDN
s-maxage=N
CDN cache duration (overrides max-age for CDN)
max-age=N
Browser cache duration in seconds
stale-while-revalidate=N
Serve stale for N seconds while fetching fresh
stale-if-error=N
Serve stale for N seconds if origin errors
no-cache
Must revalidate before using cached version
no-store
Never cache
must-revalidate
Do not serve stale after max-age expires
immutable
Never changes — skip revalidation (use with hashed URLs)
// Weak ETag — content is semantically equivalent but may differ in encodingconst weakEtag = `W/"${version}-${lastModified.getTime()}"`;
// Use when minor formatting changes should not invalidate cache
// Different cached versions for different Accept-Language valuesreturnNextResponse.json(data, {
headers: {
'Cache-Control': 'public, s-maxage=3600',
'Vary': 'Accept-Language, Accept-Encoding',
},
});
Best Practices
Cache close to the user — Browser > CDN edge > application cache > database cache. Each layer reduces latency.
Use s-maxage for CDN, max-age for browser — Keep CDN cache long, browser cache short so users see updates after CDN revalidation.
Always set Vary for personalized responses — Without it, CDNs serve the wrong cached version to different users.
Use stale-while-revalidate — Users get instant responses while fresh data loads in background.
Hash-based URLs for static assets — Use immutable directive with content-hashed filenames (style.a1b2c3.css).
Invalidate explicitly, not by TTL alone — Event-driven invalidation (on write) is more reliable than hoping TTL expires at the right time.
Monitor cache hit rates — Track x-cache: HIT vs MISS in your CDN. Aim for >90% hit rate on static content.
Never cache errors — Ensure 4xx/5xx responses have Cache-Control: no-store to avoid caching failures.
Common Pitfalls
Pitfall
Problem
Fix
Missing Vary header
CDN serves cached response for wrong user/language
Add Vary: Cookie or Vary: Authorization for personalized content
Caching authenticated responses on CDN
User A sees User B's data
Use Cache-Control: private for auth-dependent responses
No cache invalidation strategy
Stale data persists until TTL expires
Implement webhook-based or event-driven invalidation
Over-caching POST/PUT responses
Mutations return stale data
Only cache GET requests; bust related GET caches on mutation
CDN caches error responses
500 error served to all users for TTL duration
Set Cache-Control: no-store on error responses
Cache stampede on expiry
All caches expire simultaneously, hammering origin
Use jitter: add random seconds to TTL, or use SWR pattern
Forgetting no-store on sensitive data
Browser disk-caches private information
Use Cache-Control: no-store for PII, tokens, financial data
ISR with slow revalidation
First visitor after TTL gets slow response
Use stale-while-revalidate so first visitor still gets stale fast
From api-gateway
API gateway patterns — routing, rate limiting, authentication, request transformation, and service mesh.
API Gateway Patterns
Purpose
Provide expert guidance on API gateway architecture, routing strategies, rate limiting, authentication delegation, request/response transformation, circuit breaking, and service mesh integration. Covers both dedicated gateway solutions (Kong, AWS API Gateway) and custom gateway implementations.
Gateway Responsibilities
An API gateway is the single entry point for all client requests. Core responsibilities:
Routing — Direct requests to the correct backend service
Authentication — Validate tokens, API keys before forwarding