skills/rest-api-design/SKILL.md
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.
npx skillsauth add curiositech/windags-skills rest-api-designInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Design REST API endpoints that follow HTTP semantics and handle edge cases gracefully.
Does the request read data without side effects?
├─ YES → GET
│ └─ Never mutate state. Cache-safe.
└─ NO → Does it create a new resource?
├─ YES → Server assigns ID?
│ ├─ YES → POST to collection (/orders)
│ └─ NO → PUT to specific path (/orders/123)
└─ NO → Does it replace entire resource?
├─ YES → PUT to item (/orders/123)
│ └─ Send full representation
└─ NO → Does it update specific fields?
├─ YES → PATCH to item (/orders/123)
│ └─ Send only changed fields
└─ NO → DELETE the resource
└─ Must be idempotent
Operation succeeded?
├─ YES → Returning data?
│ ├─ YES → 200 OK
│ ├─ Created resource? → 201 Created + Location header
│ ├─ Async processing? → 202 Accepted + status URL
│ └─ No content? → 204 No Content
└─ NO → Client error or server error?
├─ CLIENT → Malformed request?
│ ├─ Bad JSON/headers → 400 Bad Request
│ ├─ Valid syntax, bad data → 422 Unprocessable Entity
│ ├─ No auth → 401 Unauthorized
│ ├─ Insufficient permissions → 403 Forbidden
│ ├─ Resource missing → 404 Not Found
│ ├─ Conflict/duplicate → 409 Conflict
│ └─ Rate limited → 429 Too Many Requests
└─ SERVER → Code threw?
├─ YES → 500 Internal Server Error
├─ Dependency down? → 502 Bad Gateway
└─ Overloaded? → 503 Service Unavailable
Data volume?
├─ < 100 items → No pagination needed
├─ 100-10k items → Offset-based
│ └─ ?page=1&limit=20
└─ > 10k or real-time → Cursor-based
└─ ?cursor=abc&limit=50
Resource relationship?
├─ Independent resource → Top-level (/orders)
├─ Owned by parent → One level nesting (/users/123/orders)
└─ Deeper hierarchy needed?
└─ STOP → Flatten with query params
└─ /comments?postId=123 not /posts/123/comments
/getUsers, /createOrder, /deleteItem/5GET /users, POST /orders, DELETE /items/5/users/1/posts/2/comments/3/likes/4/likes?commentId=3. If resource has global ID, make it top-level{"error": "Not found"} with HTTP 200, or {"success": false} with 200/users?page=1 but /posts?offset=0&count=20Idempotency-Key header. Cache response by (userId, key) for 24h{"data": {"result": {"items": [{"item": {"value": 123}}]}}}{"data": [...], "meta": {}}{"error": "Validation failed"} with no field-level infodetails array with path and message for each validation failureGET /users returns all 50,000 users in one responseRequirements: Create orders, list user orders, update shipping address, cancel orders.
Step 1: Method Selection
GET /ordersPOST /ordersPATCH /orders/{id}POST /orders/{id}/cancellationStep 2: Handle Edge Cases
// POST /orders - Creation
{
"items": [{"productId": "prod_1", "quantity": 2}],
"shippingAddress": {...},
"idempotencyKey": "uuid-12345" // Prevent double-submit
}
// Success: 201 Created + Location header
{
"id": "ord_123",
"status": "pending",
"total": 59.98,
"createdAt": "2024-01-15T10:30:00Z"
}
// Duplicate: 409 Conflict (idempotency key reused within 24h)
{
"error": "Order already exists with this idempotency key",
"code": "DUPLICATE_ORDER",
"existingOrderId": "ord_123"
}
// Invalid data: 422 Unprocessable Entity
{
"error": "Validation failed",
"code": "VALIDATION_ERROR",
"details": [
{"path": "items[0].quantity", "message": "Must be positive integer"},
{"path": "shippingAddress.zipCode", "message": "Invalid format"}
]
}
Step 3: Pagination & Filtering
// GET /orders?status=pending&page=2&limit=10&sort=createdAt:desc
{
"data": [
{"id": "ord_124", "status": "pending", "total": 29.99, ...},
{"id": "ord_123", "status": "pending", "total": 59.98, ...}
],
"pagination": {
"page": 2,
"limit": 10,
"total": 156,
"totalPages": 16
},
"filters": {
"status": "pending",
"sort": "createdAt:desc"
}
}
Step 4: State Transitions
// PATCH /orders/ord_123 - Update shipping
{"shippingAddress": {"street": "123 New St"}}
// Success if order is pending: 200 OK
{"id": "ord_123", "status": "pending", "shippingAddress": {...}}
// Error if already shipped: 409 Conflict
{
"error": "Cannot modify shipped order",
"code": "ORDER_ALREADY_SHIPPED",
"currentStatus": "shipped"
}
// POST /orders/ord_123/cancellation - Cancel order
{"reason": "Changed mind"}
// Success: 201 Created
{
"id": "canc_456",
"orderId": "ord_123",
"reason": "Changed mind",
"refundAmount": 59.98,
"createdAt": "2024-01-15T11:00:00Z"
}
What novice misses: Uses DELETE /orders/123 for cancellation (loses cancel reason/refund data). Returns 200 for all errors. No idempotency protection.
What expert catches: Models cancellation as resource creation. Uses proper status codes. Protects against double-submit. Validates state transitions.
/getUsers)error, code, and details fields in standard envelopeIdempotency-Key headerDon't use REST API design for:
graphql-schema-design skillrealtime-api-design skillgrpc-design or message queuesfile-api-design skill for multipart handlingDelegate to other skills:
database-design skillauth-implementation skillopenapi-documentation skillapi-performance skilldata-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.