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 skilltools
Building resilient distributed systems with circuit breakers, retries with full-jitter exponential backoff, retry budgets (per-request 3-attempt + per-client 10% ratio per Google SRE), deadline propagation, and the cascading-failure math (4 layers × 3 retries = 64x amplification). Grounded in Resilience4j, Microsoft Cloud Patterns, AWS Architecture Blog (Marc Brooker), and Google SRE Book.
testing
Designing HTTP cache headers that work correctly across browsers, CDNs, and shared proxies — `Cache-Control` directives per RFC 9111, `stale-while-revalidate` and `stale-if-error` per RFC 5861, the Vary header for varying responses, and surrogate keys for tag-based purging. Grounded in IETF RFCs and Cloudflare/Fastly docs.
development
Use when designing or fixing a Content Security Policy on a real site, choosing between nonce-based and hash-based CSP, adding strict-dynamic, debugging "Refused to execute inline script" errors, deploying CSP in report-only mode first, configuring report-to / report-uri, or auditing an existing policy for unsafe-inline / unsafe-eval / wildcards. Triggers: "CSP blocks legitimate inline script", strict-dynamic, nonce-{RANDOM}, sha256-{HASH}, object-src none, base-uri none, frame-ancestors, Trusted Types, X-Content-Security-Policy obsolete, report-only vs enforced. NOT for general HTTP security headers (HSTS, COOP/COEP), Trusted Types deep dive, CORS configuration, or building a WAF.
tools
Choosing and operating an HTTP API versioning strategy that doesn't break clients — Stripe's date-based pinned versions, the Deprecation/Sunset header pair (RFC 9745 + RFC 8594), URI vs header vs media-type approaches, and the version-transformer pattern. Grounded in Stripe's published architecture and IETF RFCs.