Universal API design skill covering RESTful design, GraphQL, versioning, documentation, error standards, pagination, and authentication patterns. Apply before building any new API or modifying existing contracts.
The API designer defines the contract between clients and services. Responsible for intuitive, consistent, secure, and evolvable APIs that minimize breaking changes and are well-documented.
Read before writing any new endpoint: Good API design is a contract. Changing it after clients depend on it is painful. Design carefully upfront.
# Good — nouns, lowercase, hyphen-separated
GET /purchase-orders
GET /purchase-orders/:id
POST /purchase-orders
PATCH /purchase-orders/:id
DELETE /purchase-orders/:id
# Actions as sub-resources for non-CRUD operations
POST /purchase-orders/:id/approve
POST /purchase-orders/:id/cancel
POST /users/:id/reset-password
# Bad — verbs in path
GET /getPurchaseOrders
POST /createOrder
| Method | Semantics | Idempotent | Body |
|---|---|---|---|
| GET | Read | Yes | No |
| POST | Create / Action | No | Yes |
| PUT | Full Replace | Yes | Yes |
| PATCH | Partial Update | No (usually) | Yes |
| DELETE | Remove | Yes | Optional |
# Good — 2 levels max
GET /suppliers/:id/invoices
GET /suppliers/:id/invoices/:invoiceId
# Avoid deep nesting — complexity explodes
GET /companies/:id/suppliers/:sid/invoices/:iid/line-items/:lid # too deep
# Instead:
GET /invoices/:iid/line-items/:lid # flatten with query params for filtering
Success (list)
{
"data": [...],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 142,
"hasNextPage": true
}
}
Success (single item)
{
"data": {
"id": "ord_abc123",
"status": "confirmed",
"createdAt": "2024-01-15T10:30:00Z"
}
}
Error
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "quantity", "message": "Must be a positive integer" },
{ "field": "price", "message": "Required" }
],
"requestId": "req_xyz789"
}
}
Define a finite list of machine-readable error codes:
VALIDATION_ERROR — Client sent invalid data
NOT_FOUND — Resource doesn't exist
UNAUTHORIZED — Not authenticated
FORBIDDEN — Authenticated but not authorized
CONFLICT — State conflict (duplicate, concurrent edit)
RATE_LIMITED — Too many requests
INTERNAL_ERROR — Server-side unexpected error
GET /transactions?cursor=eyJpZCI6MTAwfQ&limit=20
Response:
{
"data": [...],
"pagination": {
"nextCursor": "eyJpZCI6MTIwfQ",
"hasNextPage": true
}
}
GET /products?page=2&pageSize=20
Response:
{
"data": [...],
"pagination": { "page": 2, "pageSize": 20, "total": 200 }
}
Never return unbounded lists. Always paginate.
# Filtering — use query parameters
GET /transactions?status=completed&startDate=2024-01-01&endDate=2024-01-31
# Sorting — field and direction
GET /products?sort=price&order=asc
GET /orders?sort=createdAt&order=desc
# Field selection (reduces payload size)
GET /users?fields=id,name,email
/api/v1/orders
/api/v2/orders
GET /api/orders
Accept-Version: v2
Deprecation headerAuthorization: Bearer eyJhbGciOiJIUzI1NiJ9...
X-API-Key: ak_live_abc123xyz
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1705312800
Retry-After: 60 (only on 429 response)
| Type | Limit |
|---|---|
| Read endpoints | 1000 req/min |
| Write endpoints | 100 req/min |
| Auth endpoints | 10 req/min |
| File upload | 10 req/min |
Every API must have an OpenAPI 3.x spec. Generate from code annotations or maintain as source of truth: