API Design
You are designing or reviewing: $ARGUMENTS
API design is a contract with every consumer — external developers, mobile apps, browser clients, and future yourself. Changing an API is expensive. Designing it right the first time saves months of migration work.
Principles
- Design for the consumer, not the implementation. Resources should match how clients think about the problem, not how the database is structured.
- Be consistent. The tenth endpoint you add must follow the same patterns as the first.
- Be boring. Clever API design is bad API design. Every deviation from convention requires documentation.
- Fail loudly and clearly. Ambiguous error responses are support tickets.
- Treat URLs as public commitments. Never break a URL that is already in use.
1. URL Design
Resource naming
- Use nouns, not verbs:
/usersnot/getUsers - Use plural nouns:
/orders,/products,/invoices - Use kebab-case for multi-word:
/api/line-items,/api/password-resets - Nest resources sparingly — max 2 levels deep
GET /api/users → list users
POST /api/users → create user
GET /api/users/:id → get one user
PUT /api/users/:id → replace user (full update)
PATCH /api/users/:id → partial update
DELETE /api/users/:id → delete user
# Nested (1 level ok, 2 levels acceptable, 3+ is a smell)
GET /api/users/:id/orders → orders belonging to user
Actions that don't map to CRUD
Use a _actions sub-resource with a POST:
POST /api/users/:id/actions/verify-email
POST /api/orders/:id/actions/cancel
POST /api/invoices/:id/actions/send
Never: POST /api/cancelOrder, GET /api/activateUser?id=123
2. HTTP Method Semantics
| Method | Meaning | Idempotent | Safe |
|---|---|---|---|
| GET | Retrieve resource | ✅ | ✅ |
| POST | Create resource / trigger action | ❌ | ❌ |
| PUT | Replace resource entirely | ✅ | ❌ |
| PATCH | Partial update | Depends | ❌ |
| DELETE | Remove resource | ✅ | ❌ |
Rules:
- GET never changes state (no
GET /api/users/activate?token=X) - POST for creation returns
201 Createdwith the created resource in body - PUT requires the full resource in the body (any omitted field is set to null/default)
- PATCH accepts only the fields being changed
- DELETE on an already-deleted resource returns
404or204(pick one, be consistent)
3. HTTP Status Codes
Use the right code. Not just 200 and 500.
| Code | Use for |
|---|---|
| 200 OK | Successful GET, PATCH, PUT, DELETE (when returning body) |
| 201 Created | Successful POST that created a resource |
| 202 Accepted | Request queued for async processing |
| 204 No Content | Successful DELETE or action with no response body |
| 400 Bad Request | Malformed JSON, invalid field type |
| 401 Unauthorized | Not authenticated (no valid token) |
| 403 Forbidden | Authenticated but not authorised for this resource |
| 404 Not Found | Resource does not exist |
| 409 Conflict | Uniqueness conflict, optimistic lock conflict |
| 422 Unprocessable Entity | Validation failed (correct JSON, invalid values) |
| 429 Too Many Requests | Rate limit exceeded |
| 500 Internal Server Error | Unexpected server error (never send detail to clients) |
| 503 Service Unavailable | Temporary maintenance or overload |
4. Request and Response Shapes
Response envelope (consistent format)
// Success — single resource
HTTP 200
{
"data": {
"id": "uuid",
"email": "user@example.com",
"created_at": "2026-01-01T12:00:00Z"
}
}
// Success — collection
HTTP 200
{
"data": [...],
"meta": {
"total": 1234,
"page": 1,
"per_page": 20,
"last_page": 62
}
}
// Error
HTTP 422
{
"message": "The given data was invalid.",
"errors": {
"email": ["The email has already been taken."],
"password": ["The password must be at least 8 characters."]
}
}
Field naming
- Use
snake_casefor JSON fields (most universal) - Use ISO 8601 for all dates:
"2026-03-28T14:30:00Z"(UTC, explicit timezone) - Use strings for IDs if they may become non-numeric:
"id": "abc123"not"id": 1 - Never use
dataas a field name insidedata(nested envelope anti-pattern)
5. Pagination
Use cursor-based pagination for large or frequently-updating datasets, offset-based for simple admin pages:
Offset pagination
GET /api/orders?page=3&per_page=20
Response:
{
"data": [...],
"meta": { "total": 500, "page": 3, "per_page": 20, "last_page": 25 }
}
Cursor pagination (better for infinite scroll / large sets)
GET /api/feed?cursor=eyJpZCI6MTIzfQ&limit=20
Response:
{
"data": [...],
"meta": {
"next_cursor": "eyJpZCI6MTQzfQ",
"has_more": true
}
}
6. Filtering, Sorting, Search
GET /api/orders?status=shipped&created_after=2026-01-01
GET /api/products?sort=price&direction=asc
GET /api/users?q=john&status=active
GET /api/orders?include=customer,items (sparse fieldsets / eager loading)
Document every supported filter, sort field, and include option. Fail with 400 for unsupported parameters.
7. Versioning
Never break existing clients. Use one of:
URL versioning (simplest, most explicit)
/api/v1/users
/api/v2/users
Header versioning (cleaner URLs)
Accept: application/vnd.api+json;version=2
Rules:
- A new version is only needed when a breaking change is unavoidable (removing a field, changing a type, changing semantics)
- Adding new optional fields or new endpoints is NOT a breaking change
- Maintain old versions for at least 6 months after announcing deprecation
- Communicate deprecation via a response header on the old endpoints:
Deprecation: true,Sunset: Sat, 01 Jan 2027 00:00:00 GMT
8. Authentication
- Use
Authorization: Bearer <token>header — not query string (gets logged) - Use short-lived access tokens + long-lived refresh tokens
- Return
401when the token is missing or expired,403when it is valid but unauthorized - Never include tokens in URLs or redirect targets
9. API Design Review Checklist
- [ ] Resources are nouns, URLs are consistent (
/users/:id, not/user/:idand/users/:userId) - [ ] Correct HTTP method for each operation
- [ ] Correct status codes for every response case
- [ ] Consistent envelope format for success and error responses
- [ ] All timestamps in ISO 8601 UTC
- [ ] Pagination on every collection endpoint
- [ ] Validation errors return
422with field-level detail - [ ] No secrets or sensitive data in URLs (use request body or headers)
- [ ] All endpoints document authentication requirements
- [ ] Breaking changes are versioned