From api-design-bundle
Expert REST API design patterns, resource modeling, HTTP semantics, pagination, filtering, and RESTful best practices. Use when designing REST endpoints, reviewing API specifications, establishing REST conventions, or implementing HTTP-based web services.
How this skill is triggered — by the user, by Claude, or both
Slash command
/api-design-bundle:rest-api-designThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Master REST API design principles to build intuitive, scalable, and developer-friendly APIs that stand the test of time.
Master REST API design principles to build intuitive, scalable, and developer-friendly APIs that stand the test of time.
REST APIs should be resource-oriented, not action-oriented:
Good patterns:
GET /api/users # List users
POST /api/users # Create user
GET /api/users/{id} # Get specific user
PUT /api/users/{id} # Replace user
PATCH /api/users/{id} # Update user fields
DELETE /api/users/{id} # Delete user
# Nested resources (shallow)
GET /api/users/{id}/orders # Get user's orders
POST /api/users/{id}/orders # Create order for user
Bad patterns (avoid):
POST /api/createUser
POST /api/getUserById
POST /api/deleteUser
GET /api/user (inconsistent singular)
Each HTTP method has specific semantics that must be respected:
Idempotency: GET, PUT, DELETE must be idempotent (same result when called multiple times)
Resource Naming:
/api/users not /api/user/api/users not /api/Users/api/user-profiles not /api/userProfilesNested Resources (Shallow Preferred):
# Shallow nesting (preferred) - easy to understand
GET /api/users/{id}/orders
# Deep nesting (avoid) - hard to route and query
GET /api/users/{id}/orders/{orderId}/items/{itemId}/reviews
# Better approach for deep hierarchies:
GET /api/order-items/{id}/reviews
Use status codes correctly to communicate request outcomes:
200 OK - Successful GET, PATCH, PUT201 Created - Successful POST (include Location header)204 No Content - Successful DELETE or empty response400 Bad Request - Malformed request syntax401 Unauthorized - Authentication required403 Forbidden - Authenticated but not authorized404 Not Found - Resource doesn't exist409 Conflict - State conflict (duplicate email, etc.)422 Unprocessable Entity - Validation errors500 Internal Server Error - Server error503 Service Unavailable - Temporary downtime429 Too Many Requests - Rate limit exceeded# List collections
GET /api/users?page=1&limit=20
→ 200 OK
→ Returns: [user1, user2, ...]
# Get specific resource
GET /api/users/{id}
→ 200 OK or 404 Not Found
→ Returns: {id, name, email, ...}
# Create new resource
POST /api/users
Body: {"name": "John", "email": "[email protected]"}
→ 201 Created
→ Location: /api/users/123
→ Returns: {id: "123", name: "John", ...}
# Update entire resource
PUT /api/users/{id}
Body: {complete user object with ALL fields}
→ 200 OK or 404 Not Found
# Partial update
PATCH /api/users/{id}
Body: {"name": "Jane"} (only changed fields)
→ 200 OK or 404 Not Found
# Delete resource
DELETE /api/users/{id}
→ 204 No Content or 404 Not Found
Always paginate large collections. Three main approaches:
Best for: Small datasets, traditional pagination UI
GET /api/users?page=2&page_size=20
Response:
{
"items": [...],
"page": 2,
"page_size": 20,
"total": 150,
"pages": 8,
"has_next": true,
"has_prev": true
}
Best for: Large datasets, real-time data, consistent results
GET /api/users?limit=20&cursor=eyJpZCI6MTIzfQ
Response:
{
"items": [...],
"next_cursor": "eyJpZCI6MTQzfQ",
"prev_cursor": "eyJpZCI6MTA3fQ",
"has_more": true
}
Most RESTful approach using HTTP Link header:
GET /api/users?page=2
Response Headers:
Link: <https://api.example.com/users?page=3>; rel="next",
<https://api.example.com/users?page=1>; rel="prev",
<https://api.example.com/users?page=1>; rel="first",
<https://api.example.com/users?page=8>; rel="last"
Implementation pattern (FastAPI):
from fastapi import FastAPI, Query
from typing import Optional
@app.get("/api/users")
async def list_users(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100)
):
offset = (page - 1) * page_size
total = await count_users()
users = await fetch_users(limit=page_size, offset=offset)
return {
"items": users,
"total": total,
"page": page,
"page_size": page_size,
"pages": (total + page_size - 1) // page_size
}
Filtering:
GET /api/users?status=active
GET /api/users?role=admin&status=active
Sorting:
GET /api/users?sort=created_at
GET /api/users?sort=-created_at # descending
GET /api/users?sort=name,created_at # multiple fields
Searching:
GET /api/users?search=john
GET /api/users?q=john
Field Selection (Sparse Fieldsets):
GET /api/users?fields=id,name,email
Standardize error responses for consistency:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Invalid email format",
"value": "not-an-email"
}
],
"timestamp": "2025-10-16T12:00:00Z",
"path": "/api/users"
}
}
Plan for breaking changes from day one:
Clear and easy to route:
/api/v1/users
/api/v2/users
Clean URLs but less visible:
GET /api/users
Accept: application/vnd.api+json; version=2
Easy to test but easy to forget:
GET /api/users?version=2
Bearer Token (JWT):
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
401 Unauthorized - Missing/invalid token
403 Forbidden - Valid token, insufficient permissions
API Keys:
X-API-Key: your-api-key-here
Protect APIs from abuse:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 742
X-RateLimit-Reset: 1640000000
Response when limited:
429 Too Many Requests
Retry-After: 3600
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://example.com"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
allow_headers=["*"],
)
For non-idempotent operations (POST), use idempotency keys:
POST /api/orders
Idempotency-Key: unique-key-123
If duplicate request: → 200 OK (return cached response)
POST /api/users/batch
{
"items": [
{"name": "User1", "email": "[email protected]"},
{"name": "User2", "email": "[email protected]"}
]
}
Response:
{
"results": [
{"id": "1", "status": "created"},
{"id": null, "status": "failed", "error": "Email already exists"}
]
}
Include links for related resources:
{
"id": "123",
"name": "John",
"email": "[email protected]",
"_links": {
"self": {"href": "/api/users/123"},
"orders": {"href": "/api/users/123/orders"},
"update": {"href": "/api/users/123", "method": "PATCH"},
"delete": {"href": "/api/users/123", "method": "DELETE"}
}
}
Cache Headers:
# Client caching
Cache-Control: public, max-age=3600
# No caching
Cache-Control: no-cache, no-store, must-revalidate
# Conditional requests
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
→ 304 Not Modified
Generate interactive API documentation:
from fastapi import FastAPI, Path
app = FastAPI(
title="My API",
description="API for managing users",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc"
)
@app.get(
"/api/users/{user_id}",
summary="Get user by ID",
response_description="User details",
tags=["Users"]
)
async def get_user(
user_id: str = Path(..., description="The user ID")
):
"""
Retrieve user by ID.
Returns full user profile including:
- Basic information
- Contact details
- Account status
"""
pass
/api/createUser (wrong)/users and /userReference files for this skill are planned for a future release.
npx claudepluginhub karchtho/my-claude-marketplace --plugin api-design-bundleREST API design with semantic HTTP methods, status codes, and resource modeling. Use when designing new APIs or reviewing existing API designs.
Guides REST API design with best practices for HTTP methods, status codes, structured errors, pagination, versioning, and OpenAPI documentation.
Guides REST API design patterns for production-grade endpoints including resource naming, status codes, pagination, filtering, error responses, versioning, and rate limiting. Use when designing new APIs or reviewing contracts.