RESTful API Design Skill
RESTful API Design Skill
How to Use This Skill
- Review the patterns and examples below
- Apply the relevant patterns to your implementation
- Follow the best practices outlined in this skill
Production-ready RESTful API design skill implementing industry best practices for resource-oriented architecture, comprehensive documentation with OpenAPI 3.1, and consistent error handling patterns.
When to Use This Skill
Use restful-api-design when:
- Designing new REST APIs from scratch
- Creating OpenAPI/Swagger documentation
- Reviewing existing API designs for consistency
- Implementing pagination, filtering, or sorting
- Defining error response standards
- Planning API versioning strategies
Don't use restful-api-design when:
- Building GraphQL APIs (use
graphql-designskill) - Implementing gRPC or WebSocket protocols
- Only need API versioning guidance (use
api-versioningskill) - Working on API security (use
security-auditskill)
REST Design Principles
Resource-Oriented Architecture
| Principle | Description | Example |
|---|---|---|
| Resources | Use nouns, not verbs | /users not /getUsers |
| Collections | Plural naming | /users, /orders, /products |
| Hierarchy | Nested resources | /users/{id}/orders |
| Consistency | Uniform interface | Same patterns across all endpoints |
| Stateless | No server-side sessions | Use tokens/API keys |
HTTP Methods
| Method | Purpose | Idempotent | Safe | Request Body |
|---|---|---|---|---|
| GET | Retrieve resource(s) | Yes | Yes | No |
| POST | Create resource | No | No | Yes |
| PUT | Replace resource | Yes | No | Yes |
| PATCH | Partial update | No | No | Yes |
| DELETE | Remove resource | Yes | No | Optional |
| OPTIONS | Get allowed methods | Yes | Yes | No |
| HEAD | Get headers only | Yes | Yes | No |
HTTP Status Codes
2xx Success
├── 200 OK - Successful GET, PUT, PATCH, DELETE
├── 201 Created - Successful POST (include Location header)
├── 202 Accepted - Async operation accepted
└── 204 No Content - Successful DELETE (no body)
3xx Redirection
├── 301 Moved Permanently - Resource relocated
├── 304 Not Modified - Cache hit (ETag/If-Modified-Since)
└── 307 Temporary Redirect - Use for redirects
4xx Client Error
├── 400 Bad Request - Malformed request, validation error
├── 401 Unauthorized - Missing/invalid authentication
├── 403 Forbidden - Valid auth, insufficient permissions
├── 404 Not Found - Resource doesn't exist
├── 405 Method Not Allowed - Wrong HTTP method
├── 409 Conflict - State conflict (duplicate, version mismatch)
├── 422 Unprocessable Entity - Semantic validation error
└── 429 Too Many Requests - Rate limit exceeded
5xx Server Error
├── 500 Internal Server Error - Unexpected server failure
├── 502 Bad Gateway - Upstream service failure
├── 503 Service Unavailable - Maintenance/overload
└── 504 Gateway Timeout - Upstream timeout
Instructions
Phase 1: Resource Design
Objective: Define API resources and relationships.
-
Identify resources:
Domain: E-commerce
Primary Resources:
- Users (/users)
- Products (/products)
- Orders (/orders)
- Categories (/categories)
Nested Resources:
- User's orders (/users/{id}/orders)
- Product reviews (/products/{id}/reviews)
- Order items (/orders/{id}/items) -
Define resource relationships:
# Resource relationships
User:
has_many: [orders, addresses, reviews]
belongs_to: []
Order:
has_many: [order_items, payments]
belongs_to: [user]
Product:
has_many: [reviews, variants]
belongs_to: [category] -
Design URL structure:
# Collection endpoints
GET /api/v1/users # List users
POST /api/v1/users # Create user
# Instance endpoints
GET /api/v1/users/{id} # Get user
PUT /api/v1/users/{id} # Replace user
PATCH /api/v1/users/{id} # Update user
DELETE /api/v1/users/{id} # Delete user
# Nested resources
GET /api/v1/users/{id}/orders # User's orders
POST /api/v1/users/{id}/orders # Create order for user
GET /api/v1/users/{id}/orders/{orderId} # Specific order
Phase 2: OpenAPI Specification
Objective: Create comprehensive API documentation.
-
Define OpenAPI structure:
openapi: 3.1.0
info:
title: E-commerce API
version: 1.0.0
description: |
RESTful API for e-commerce platform.
## Authentication
All endpoints require Bearer token authentication.
## Rate Limiting
- Standard: 100 requests/minute
- Premium: 1000 requests/minute
contact:
name: API Support
email: api@example.com
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0
servers:
- url: https://api.example.com/v1
description: Production
- url: https://staging-api.example.com/v1
description: Staging
tags:
- name: Users
description: User management operations
- name: Products
description: Product catalog operations
- name: Orders
description: Order management operations -
Define schemas:
components:
schemas:
User:
type: object
required:
- email
- name
properties:
id:
type: string
format: uuid
readOnly: true
example: "550e8400-e29b-41d4-a716-446655440000"
email:
type: string
format: email
example: "user@example.com"
name:
type: string
minLength: 1
maxLength: 100
example: "John Doe"
created_at:
type: string
format: date-time
readOnly: true
updated_at:
type: string
format: date-time
readOnly: true
UserCreate:
type: object
required:
- email
- name
- password
properties:
email:
type: string
format: email
name:
type: string
password:
type: string
format: password
minLength: 8
Error:
type: object
required:
- code
- message
properties:
code:
type: string
example: "VALIDATION_ERROR"
message:
type: string
example: "Invalid input data"
details:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
ErrorDetail:
type: object
properties:
field:
type: string
message:
type: string -
Define paths:
paths:
/users:
get:
summary: List users
operationId: listUsers
tags: [Users]
parameters:
- $ref: '#/components/parameters/PageParam'
- $ref: '#/components/parameters/LimitParam'
- $ref: '#/components/parameters/SortParam'
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/UserListResponse'
'401':
$ref: '#/components/responses/Unauthorized'
post:
summary: Create user
operationId: createUser
tags: [Users]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserCreate'
responses:
'201':
description: User created
headers:
Location:
description: URL of created user
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
$ref: '#/components/responses/BadRequest'
'409':
$ref: '#/components/responses/Conflict'
/users/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
get:
summary: Get user by ID
operationId: getUser
tags: [Users]
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
$ref: '#/components/responses/NotFound'
Phase 3: Pagination & Filtering
Objective: Implement consistent pagination and filtering.
-
Cursor-based pagination (recommended):
# Request
GET /api/v1/users?limit=20&cursor=eyJpZCI6MTAwfQ==
# Response
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTIwfQ==",
"prev_cursor": "eyJpZCI6ODB9",
"has_more": true
}
} -
Offset-based pagination:
# Request
GET /api/v1/users?page=2&limit=20
# Response
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"total": 150,
"total_pages": 8
}
} -
Filtering patterns:
# Simple equality
GET /api/v1/products?category=electronics
# Multiple values
GET /api/v1/products?status=active,pending
# Range queries
GET /api/v1/products?price_min=10&price_max=100
GET /api/v1/orders?created_after=2024-01-01&created_before=2024-12-31
# Text search
GET /api/v1/products?q=laptop
# Nested field
GET /api/v1/orders?user.email=john@example.com -
Sorting:
# Single field
GET /api/v1/products?sort=price # Ascending
GET /api/v1/products?sort=-price # Descending
# Multiple fields
GET /api/v1/products?sort=-created_at,name
Phase 4: Error Handling
Objective: Implement consistent error responses.
-
Standard error format:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"request_id": "req_abc123xyz",
"timestamp": "2024-01-15T10:30:00Z",
"details": [
{
"field": "email",
"message": "must be a valid email address",
"code": "INVALID_FORMAT"
},
{
"field": "password",
"message": "must be at least 8 characters",
"code": "MIN_LENGTH"
}
]
}
} -
Error code enum:
ErrorCode:
- VALIDATION_ERROR # 400 - Input validation failed
- INVALID_FORMAT # 400 - Wrong data format
- MISSING_FIELD # 400 - Required field missing
- UNAUTHORIZED # 401 - Missing authentication
- INVALID_TOKEN # 401 - Invalid/expired token
- FORBIDDEN # 403 - Insufficient permissions
- NOT_FOUND # 404 - Resource not found
- CONFLICT # 409 - Resource conflict
- RATE_LIMITED # 429 - Too many requests
- INTERNAL_ERROR # 500 - Server error
- SERVICE_UNAVAILABLE # 503 - Service down
Examples
Example 1: User CRUD API
OpenAPI Specification:
openapi: 3.1.0
info:
title: User API
version: 1.0.0
paths:
/users:
get:
summary: List all users
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
pagination:
$ref: '#/components/schemas/Pagination'
post:
summary: Create new user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserCreate'
responses:
'201':
description: Created
headers:
Location:
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/User'
/users/{id}:
get:
summary: Get user by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Example 2: FastAPI Implementation
from fastapi import FastAPI, HTTPException, Query, Path
from pydantic import BaseModel, EmailStr
from typing import Optional
from uuid import UUID
from datetime import datetime
app = FastAPI(title="User API", version="1.0.0")
class UserCreate(BaseModel):
email: EmailStr
name: str
password: str
class User(BaseModel):
id: UUID
email: EmailStr
name: str
created_at: datetime
updated_at: datetime
class Pagination(BaseModel):
page: int
limit: int
total: int
total_pages: int
class UserListResponse(BaseModel):
data: list[User]
pagination: Pagination
@app.get("/users", response_model=UserListResponse)
async def list_users(
page: int = Query(1, ge=1),
limit: int = Query(20, ge=1, le=100),
sort: Optional[str] = Query(None, regex="^-?(name|email|created_at)$")
):
"""List all users with pagination."""
# Implementation here
pass
@app.post("/users", status_code=201)
async def create_user(user: UserCreate):
"""Create a new user."""
# Implementation here
pass
@app.get("/users/{user_id}", response_model=User)
async def get_user(user_id: UUID = Path(...)):
"""Get user by ID."""
# Implementation here
pass
Example 3: Express.js Implementation
import express, { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
const app = express();
// Schemas
const UserCreateSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
password: z.string().min(8),
});
const PaginationSchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
sort: z.string().regex(/^-?(name|email|created_at)$/).optional(),
});
// Middleware
const validateQuery = (schema: z.ZodSchema) => {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.query);
if (!result.success) {
return res.status(400).json({
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid query parameters',
details: result.error.errors,
},
});
}
req.query = result.data;
next();
};
};
// Routes
app.get('/users', validateQuery(PaginationSchema), async (req, res) => {
const { page, limit, sort } = req.query;
// Implementation here
res.json({ data: [], pagination: { page, limit, total: 0, total_pages: 0 } });
});
app.post('/users', async (req, res) => {
const result = UserCreateSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid input',
details: result.error.errors,
},
});
}
// Implementation here
res.status(201).json({ id: 'uuid', ...result.data });
});
Integration
Related Components
- Skill:
graphql-design- Alternative API design approach - Skill:
api-versioning- Versioning strategy guidance - Skill:
security-audit- API security review - Agent:
senior-architect- API architecture planning - Command:
/describe-pr- Document API changes
OpenAPI Tools
# Validate OpenAPI spec
npx @redocly/cli lint openapi.yaml
# Generate client SDKs
npx @openapitools/openapi-generator-cli generate \
-i openapi.yaml \
-g typescript-fetch \
-o ./client
# Generate documentation
npx @redocly/cli build-docs openapi.yaml --output docs.html
Troubleshooting
| Issue | Solution |
|---|---|
| Inconsistent status codes | Use error handling middleware |
| N+1 queries on nested resources | Implement eager loading, use ?include= |
| Large response sizes | Implement pagination, use ?fields= |
| Breaking changes | Use API versioning |
| Poor API discoverability | Add HATEOAS links |
Best Practices Checklist
- Use plural nouns for collections (
/usersnot/user) - Use UUID or slug for resource IDs
- Include
Content-Type: application/jsonheader - Return
201 CreatedwithLocationheader for POST - Use
204 No Contentfor successful DELETE - Implement consistent error response format
- Add pagination for all list endpoints
- Use HTTPS in production
- Include API versioning from day one
- Document all endpoints with OpenAPI
Success Output
When this skill completes successfully, output:
✅ SKILL COMPLETE: restful-api-design
Completed:
- [x] Resource-oriented architecture designed (nouns, collections, hierarchy)
- [x] OpenAPI 3.1 specification created with complete schemas
- [x] Pagination strategy implemented (cursor or offset-based)
- [x] Error handling standardized with consistent format
- [x] All endpoints documented with examples
Outputs:
- openapi.yaml - OpenAPI 3.1 specification
- api/routes/ - Endpoint implementations
- api/schemas/ - Request/response models
- docs/api/ - API documentation
Validation:
- [ ] OpenAPI spec validates: `npx @redocly/cli lint openapi.yaml`
- [ ] All status codes follow HTTP standards
- [ ] Pagination on all list endpoints
- [ ] Error responses use standard format
Completion Checklist
Before marking this skill as complete, verify:
- Resources use plural nouns (e.g.,
/usersnot/user) - HTTP methods correctly applied (GET safe, PUT idempotent, etc.)
- OpenAPI spec includes all endpoints with request/response schemas
- Status codes follow HTTP standards (201 + Location for POST, 204 for DELETE)
- Pagination implemented for all collection endpoints
- Filtering and sorting parameters documented
- Error responses follow standard format with codes
- Authentication/authorization documented
- OpenAPI spec validates successfully
- API documentation generated and reviewed
Failure Indicators
This skill has FAILED if:
- ❌ Resources use verbs instead of nouns (e.g.,
/getUser) - ❌ OpenAPI spec has validation errors or missing schemas
- ❌ Collection endpoints lack pagination (will fail at scale)
- ❌ Error responses inconsistent or lack proper codes
- ❌ HTTP methods misused (POST for retrieval, GET with side effects)
- ❌ Status codes incorrect (200 for errors, 404 for auth failures)
- ❌ No authentication/authorization documentation
- ❌ Nested resources exceed 2-3 levels deep (too complex)
When NOT to Use
Do NOT use this skill when:
- Building GraphQL APIs (use
graphql-designskill instead) - Implementing gRPC or WebSocket protocols (use protocol-specific skills)
- Only need API versioning guidance (use
api-versioningskill) - Working on API security audits (use
security-auditskill instead) - Building simple CRUD with existing framework (may be over-engineering)
- Internal microservice communication (consider simpler RPC)
Use instead:
graphql-designfor GraphQL APIsgrpc-designfor gRPC servicesapi-versioningfor versioning strategy onlysecurity-auditfor security reviews- Framework defaults for simple CRUD operations
Anti-Patterns (Avoid)
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Verb-based endpoints | /getUser, /createOrder not RESTful | Use /users/{id} with HTTP methods |
| Ignoring idempotency | PUT/DELETE not idempotent causes issues | Design for safe retries |
| No pagination | Large collections crash clients/servers | Always paginate list endpoints |
| Generic error codes | All errors return 400 or 500 | Use specific codes (401, 403, 404, 409, 422) |
| Deep nesting | /users/{id}/orders/{id}/items/{id}/reviews | Max 2-3 levels, use query params |
| Breaking changes | Adding required fields breaks clients | Use API versioning, optional fields |
| No OpenAPI docs | Manual docs get stale | Generate from OpenAPI spec |
| Status code confusion | 404 for auth, 200 for errors | Follow HTTP standards strictly |
Principles
This skill embodies CODITECT principles:
- #1 Recycle → Extend → Re-Use → Create - Uses industry-standard REST patterns and OpenAPI
- #2 First Principles - Understands WHY REST is resource-oriented, stateless, cacheable
- #3 Keep It Simple - Simple URL patterns, standard HTTP methods, consistent responses
- #4 Separation of Concerns - Resources separated by domain, endpoints by responsibility
- #5 Eliminate Ambiguity - OpenAPI spec eliminates API ambiguity completely
- #6 Clear, Understandable, Explainable - Self-documenting URLs, standard error codes
- #8 No Assumptions - Explicit documentation, validation, error handling
Full Standard: CODITECT-STANDARD-AUTOMATION.md
References
- OpenAPI 3.1 Specification
- JSON:API Specification
- REST API Design Guidelines (Microsoft)
- HTTP Status Codes (MDN)
Status: Production-ready OpenAPI Version: 3.1.0 Supported Frameworks: FastAPI, Express, Actix-web, Spring Boot Integration: API documentation, SDK generation, security audits Version: 1.0.0 | Updated: 2026-01-04 | Author: CODITECT Team