API design decisions made early become difficult to change once clients depend on them. Poorly designed APIs create maintenance burden, confuse developers, and cause production incidents. Good API design anticipates growth: clear resource naming, thorough validation, proper authentication, pagination for scale, versioning for evolution, idempotency for reliability, useful error responses, and backwards compatibility.
Resource naming and URL structure
Clear, consistent resource naming makes APIs intuitive. Poor naming creates confusion and forces clients to memorize arbitrary conventions.
Naming conventions
- Use nouns for resources, not verbs:
/usersnot/getUsers - Use plural nouns for collections:
/ordersnot/order - Use hierarchical paths for relationships:
/users/{id}/orders - Use lowercase and hyphens for readability:
/order-itemsnot/orderItems - Keep URLs short and predictable
Use HTTP methods correctly
GETretrieves resources (safe, idempotent, cacheable)POSTcreates resources (not idempotent)PUTreplaces resources (idempotent)PATCHupdates resources partially (may be idempotent)DELETEremoves resources (idempotent)
Input validation
Validate all inputs at the API boundary. Invalid data should be rejected before processing.
What to validate
- Required fields are present
- Data types are correct (string, number, boolean)
- Values are within acceptable ranges
- Formats are valid (email, URL, date)
- Enums match allowed values
- String lengths are within limits
Validation error responses
Return clear error messages indicating what is wrong and how to fix it:
{
"error": "validation_failed",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Invalid email format"
},
{
"field": "age",
"message": "Must be between 0 and 120"
}
]
}
Authentication and authorization
Security must be enforced at the API layer, not just in the client. For detailed security patterns, see Security-First API Design.
Authentication patterns
- API keys: simple but less secure, suitable for server-to-server
- OAuth 2.0: standard for delegated authorization
- JWT tokens: stateless, contain claims, can be validated without database lookups
Authorization checks
- Verify identity (authentication) before checking permissions (authorization)
- Enforce object-level authorization (users can only access their own resources)
- Use role-based or attribute-based access control
- Never trust client-provided IDs without verification
Pagination
APIs that return large collections must paginate results. Without pagination, responses grow unbounded as data scales.
Pagination strategies
- Offset-based:
?limit=20&offset=40(simple but can miss items if data changes) - Cursor-based:
?limit=20&cursor=abc123(consistent across changes, better for real-time data) - Page-based:
?page=3&per_page=20(familiar but has offset issues)
Include pagination metadata
{
"data": [...],
"pagination": {
"total": 1547,
"limit": 20,
"offset": 40,
"next": "/users?limit=20&offset=60",
"previous": "/users?limit=20&offset=20"
}
}
Versioning
APIs evolve. Versioning allows changes without breaking existing clients.
Versioning strategies
- URL versioning:
/v1/users(simple, explicit) - Header versioning:
Accept: application/vnd.api+json;version=1(clean URLs) - Query parameter:
/users?version=1(less common)
Versioning guidelines
- Version major breaking changes (removing fields, changing behaviour)
- Make backwards-compatible changes within a version (adding optional fields)
- Support previous versions long enough for clients to migrate
- Communicate deprecation timelines clearly
Idempotency
Idempotent operations can be safely retried without duplicating effects. This is critical for reliable distributed systems.
Idempotent HTTP methods
GET,PUT,DELETEare naturally idempotentPOSTis not idempotent (repeated requests create multiple resources)
Making POST idempotent
Use idempotency keys to prevent duplicate processing:
POST /orders
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
{
"product_id": 123,
"quantity": 2
}
If the same key is sent again, return the original result without reprocessing.
Error responses
Clear error messages help developers debug problems quickly.
Use HTTP status codes correctly
200 OK- successful GET, PUT, PATCH201 Created- successful POST that created a resource204 No Content- successful DELETE or update with no response body400 Bad Request- invalid input401 Unauthorized- missing or invalid authentication403 Forbidden- authenticated but not authorized404 Not Found- resource does not exist429 Too Many Requests- rate limit exceeded500 Internal Server Error- server error
Error response format
{
"error": "not_found",
"message": "User not found",
"request_id": "req_abc123",
"timestamp": "2026-10-22T10:30:00Z"
}
Rate limiting
Rate limits prevent abuse and ensure fair resource allocation. Communicate limits clearly in responses.
Rate limit response headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 742
X-RateLimit-Reset: 1634910000
Rate limiting strategies
- Per-user limits (authenticated requests)
- Per-IP limits (unauthenticated requests)
- Different limits for different endpoints
- Burst allowances for short spikes
Observability
APIs must be observable for debugging and monitoring.
Request IDs
Include a unique request ID in responses for tracing:
X-Request-Id: req_abc123
Log this ID with all operations so requests can be traced through the system.
What to log
- Request method, path, and parameters
- Response status code and latency
- Authentication and authorization failures
- Validation errors
- Rate limit hits
Backwards compatibility
Maintaining backwards compatibility avoids breaking existing clients.
Safe (backwards-compatible) changes
- Adding new optional fields
- Adding new endpoints
- Adding new enum values
- Relaxing validation (accepting more inputs)
Breaking changes
- Removing fields
- Renaming fields
- Changing field types
- Changing URL structure
- Removing endpoints
- Tightening validation (rejecting previously accepted inputs)
Breaking changes require a new API version.
Documentation
Good documentation reduces support burden and improves adoption.
What to document
- All endpoints with request and response examples
- Authentication methods
- Error codes and meanings
- Rate limits
- Pagination behaviour
- Idempotency requirements
- Versioning policy
Use OpenAPI specification
OpenAPI (formerly Swagger) provides machine-readable API documentation that can generate client SDKs, test cases, and interactive documentation.
Design for clients, not convenience
APIs are contracts. Once clients depend on them, changes become expensive. Design with clear resource naming, thorough validation, proper authentication, pagination, versioning, idempotency, useful errors, and backwards compatibility. Good API design anticipates growth and prevents problems before they occur.
Published by the DSSS Engineering Team. For corrections or topic requests, use the contact page.