| name | api-design |
| description | Use when designing REST or GraphQL APIs - covers OpenAPI spec, resource design, versioning, and documentation |
API Design
Overview
Design APIs that are consistent, predictable, and well-documented. This skill covers REST and GraphQL API design patterns.
Core principle: APIs are contracts. Once published, breaking changes are expensive.
When to Use
Use this skill:
- Designing new API endpoints
- Refactoring existing APIs
- Adding versioning strategy
- Creating OpenAPI/GraphQL specifications
- Standardizing error responses
Not needed for:
- Internal function signatures
- Database schema design (use data-engineer agent)
REST API Design
Resource Naming
✅ Good ❌ Bad
-----------------------------------------
GET /users GET /getUsers
GET /users/{id} GET /user?id=123
POST /users POST /createUser
PUT /users/{id} POST /updateUser
DELETE /users/{id} GET /deleteUser/{id}
GET /users/{id}/orders GET /getUserOrders
Rules:
- Use nouns, not verbs (HTTP method is the verb)
- Plural for collections:
/users not /user
- Hierarchical for relationships:
/users/{id}/orders
- Lowercase, hyphen-separated:
/user-profiles not /userProfiles
HTTP Methods
| Method | Purpose | Idempotent | Safe |
|---|
| GET | Retrieve resource | Yes | Yes |
| POST | Create resource | No | No |
| PUT | Replace resource | Yes | No |
| PATCH | Partial update | Yes* | No |
| DELETE | Remove resource | Yes | No |
*PATCH is idempotent if using JSON Patch/Merge Patch format.
Status Codes
2xx Success
-----------------------------------------
200 OK - GET/PUT/PATCH success, body contains data
201 Created - POST success, Location header has URL
204 No Content - DELETE success, no body
4xx Client Error
-----------------------------------------
400 Bad Request - Invalid input (validation failed)
401 Unauthorized - Authentication required
403 Forbidden - Authenticated but not allowed
404 Not Found - Resource doesn't exist
409 Conflict - State conflict (duplicate, version mismatch)
422 Unprocessable - Valid syntax but semantic error
5xx Server Error
-----------------------------------------
500 Internal Error - Unexpected server error
502 Bad Gateway - Upstream service failed
503 Unavailable - Temporary overload
Standard Response Format
Success Response:
{
"data": {
"id": "123",
"name": "Example"
},
"meta": {
"requestId": "abc-123",
"timestamp": "2024-01-15T10:30:00Z"
}
}
Collection Response:
{
"data": [
{ "id": "1", "name": "First" },
{ "id": "2", "name": "Second" }
],
"meta": {
"total": 100,
"page": 1,
"pageSize": 20,
"hasNext": true
}
}
Error Response:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{ "field": "email", "message": "Invalid email format" }
]
},
"meta": {
"requestId": "abc-123",
"timestamp": "2024-01-15T10:30:00Z"
}
}
Versioning Strategies
| Strategy | Example | Pros | Cons |
|---|
| URI Path | /v1/users | Clear, cacheable | URL changes |
| Header | Accept: application/vnd.api+json;version=1 | Clean URLs | Hidden |
| Query Param | /users?version=1 | Easy to test | Pollutes params |
Recommended: URI Path versioning for simplicity and clarity.
/v1/users # Version 1
/v2/users # Version 2 (breaking changes)
When to increment version:
- Removing fields
- Changing field types
- Renaming fields
- Changing behavior
Non-breaking (no version bump):
- Adding new optional fields
- Adding new endpoints
- Adding new optional parameters
OpenAPI Specification
openapi: 3.1.0
info:
title: User API
version: 1.0.0
description: User management endpoints
paths:
/users:
get:
summary: List users
operationId: listUsers
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: pageSize
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/UserListResponse'
post:
summary: Create user
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: Created
headers:
Location:
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/UserResponse'
'400':
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
components:
schemas:
User:
type: object
required:
- id
- email
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
createdAt:
type: string
format: date-time
ErrorResponse:
type: object
required:
- error
properties:
error:
type: object
required:
- code
- message
properties:
code:
type: string
message:
type: string
details:
type: array
items:
type: object
properties:
field:
type: string
message:
type: string
GraphQL API Design
Schema Design
type Query {
user(id: ID!): User
users(filter: UserFilter, pagination: Pagination): UserConnection!
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
deleteUser(id: ID!): DeleteUserPayload!
}
type User {
id: ID!
email: String!
name: String
orders(first: Int, after: String): OrderConnection!
createdAt: DateTime!
}
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge {
cursor: String!
node: User!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
input CreateUserInput {
email: String!
name: String
}
type CreateUserPayload {
user: User
errors: [UserError!]
}
type UserError {
field: String
message: String!
code: String!
}
N+1 Query Prevention
const resolvers = {
User: {
orders: async (user) => {
return db.orders.findByUserId(user.id);
}
}
};
import DataLoader from 'dataloader';
const orderLoader = new DataLoader(async (userIds) => {
const orders = await db.orders.findByUserIds(userIds);
return userIds.map(id => orders.filter(o => o.userId === id));
});
const resolvers = {
User: {
orders: async (user) => orderLoader.load(user.id)
}
};
Query Complexity Limiting
const complexityConfig = {
maximumComplexity: 1000,
scalarCost: 1,
objectCost: 10,
listFactor: 10,
};
Rate Limiting
Headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1704067200
# When exceeded
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Strategies
| Strategy | Description | Use Case |
|---|
| Fixed Window | X requests per minute | Simple APIs |
| Sliding Window | Rolling window | More accurate |
| Token Bucket | Refill tokens over time | Burst-friendly |
| Leaky Bucket | Constant output rate | Smooth traffic |
API Gateway Patterns
Client → API Gateway → Microservices
↓
- Authentication
- Rate Limiting
- Request Routing
- Response Caching
- Request/Response Transform
- Circuit Breaker
Documentation Checklist
Before shipping an API:
Common Mistakes
| Mistake | Fix |
|---|
| Verbs in URLs | Use nouns: /users not /getUsers |
| Inconsistent naming | Pick one: camelCase or snake_case |
| Missing pagination | Always paginate lists |
| No error codes | Use machine-readable codes |
| Breaking changes | Version the API |
| Missing rate limits | Protect against abuse |
| N+1 in GraphQL | Use DataLoader |
| No request IDs | Add for debugging |
Quick Reference
REST
────────────────────────────────────
GET /resources List all
GET /resources/{id} Get one
POST /resources Create
PUT /resources/{id} Replace
PATCH /resources/{id} Update
DELETE /resources/{id} Remove
GraphQL
────────────────────────────────────
Query Read operations
Mutation Write operations
Subscription Real-time updates
Connections (Relay)
────────────────────────────────────
edges List of edge objects
node The actual object
cursor Pagination cursor
pageInfo hasNextPage, etc.
Additional resources
- OpenAPI 3.1 template — Copy-and-adapt template with CRUD endpoints, pagination, error responses, and security schemes
- Error response examples — Standard error format for all HTTP status codes with machine-readable error code registry