API Design Principles
API design is grounded in clarity, consistency, and usability. The following principles guide our decisions:
Table of Contents
- RESTful Design
- Resource-Oriented
- Statelessness
- Consistency
- Idempotency
- Versioning
- Error Handling
- Performance
- Security
- Documentation
- Evolution
- Best Practices
RESTful Design
1. Client-Server Architecture
- Separation of Concerns: Clear separation between client and server
- Stateless Communication: Each request contains all necessary information
- Cacheable: Responses define cacheability
- Uniform Interface: Standardized way of interacting with resources
- Layered System: Client can't tell if connected to end server or intermediary
2. Resource Identification
- Use nouns (not verbs) to identify resources
- Use plural nouns for collections (
/users
not/user
) - Use forward slashes (/) for hierarchy (
/users/123/orders
) - Use hyphens (-) for multi-word path segments (
/product-categories
) - Use lowercase letters in paths and headers
- Never use file extensions in URIs
Resource-Oriented
1. Resource Naming
- Pluralization: Use plural for collections (
/users
) - Consistency: Stick to either plural or singular (prefer plural)
- Concreteness: Use concrete rather than abstract names
- Avoid Verbs: Use HTTP methods to indicate actions
2. Resource Relationships
- Hierarchy: Parent/child relationships via path segments (
/users/123/orders
) - References: Use IDs to reference related resources
- Embedding: Include related resources when it improves efficiency
- Pagination: Always paginate collections
Statelessness
1. Stateless Servers
- No client context stored on server between requests
- Each request contains all necessary authentication/authorization
- Session state held entirely on the client
- Improves reliability and scalability
2. Authentication & Authorization
- Use stateless authentication (JWT, OAuth2)
- Include auth tokens in headers
- Implement proper token expiration and refresh
- Follow principle of least privilege
Consistency
1. Naming Conventions
- camelCase for JSON properties
- kebab-case for URLs and headers
- UPPER_SNAKE_CASE for constants and enums
- PascalCase for type names in documentation
2. Response Format
{
"data": {
"id": "123",
"type": "users",
"attributes": {
"name": "John Doe",
"email": "john@example.com"
},
"relationships": {
"organization": {
"data": { "id": "456", "type": "organizations" }
}
}
},
"included": [
{
"id": "456",
"type": "organizations",
"attributes": {
"name": "Acme Inc."
}
}
],
"meta": {
"total": 100,
"page": 1,
"limit": 10
},
"links": {
"self": "/users?page=1",
"next": "/users?page=2",
"prev": null,
"first": "/users?page=1",
"last": "/users?page=10"
}
}
Idempotency
1. Safe Methods
- GET, HEAD, OPTIONS, and TRACE are naturally idempotent
- Multiple identical requests have the same effect as a single request
2. Making Mutations Idempotent
- PUT: Replace entire resource (idempotent)
- PATCH: Partial updates (make idempotent with versioning)
- DELETE: Idempotent by nature
- POST: Not idempotent (use idempotency keys)
3. Idempotency Keys
POST /payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{
"amount": 1000,
"currency": "USD",
"description": "Subscription payment"
}
Versioning
1. Versioning Strategies
- URI Path:
/v1/users
- Header:
Accept: application/vnd.dexaminds.v1+json
- Query Param:
/users?version=1
(not recommended) - Media Type:
application/vnd.dexaminds+json;version=1
2. Versioning Best Practices
- Version early, version often
- Support multiple versions in parallel
- Provide clear deprecation policies
- Document version changes thoroughly
Error Handling
1. Error Response Format
{
"error": {
"code": "validation_error",
"message": "One or more validation errors occurred",
"target": "user_creation",
"details": [
{
"code": "required",
"target": "email",
"message": "Email is required"
},
{
"code": "min_length",
"target": "password",
"message": "Password must be at least 8 characters"
}
],
"innerError": {
"code": "validation_failed",
"requestId": "a1b2c3d4",
"timestamp": "2025-06-30T06:27:45Z"
}
}
}
2. HTTP Status Codes
- 2xx: Success
- 4xx: Client errors
- 5xx: Server errors
- Use specific status codes (e.g., 429 for rate limiting)
Performance
1. Caching
- Client Caching:
Cache-Control
,ETag
,Last-Modified
- Server Caching: Redis, Memcached
- CDN Caching: For static assets
2. Pagination
- Offset-based:
?page=2&limit=25
- Cursor-based:
?after=cursor&limit=25
- Keyset Pagination:
?since_id=123&limit=25
3. Filtering & Sorting
- Filtering:
?status=active&role=admin
- Sorting:
?sort=-created_at,name
- Field Selection:
?fields=id,name,email
Security
1. Authentication
- OAuth 2.0 with OpenID Connect
- JWT for stateless authentication
- API keys for service-to-service
- MFA for sensitive operations
2. Authorization
- Role-Based Access Control (RBAC)
- Attribute-Based Access Control (ABAC)
- Fine-grained permissions
- Policy-based authorization
3. Input Validation
- Validate all input data
- Use allow-listing
- Sanitize output
- Prevent injection attacks
Documentation
1. API Reference
- Endpoint descriptions
- Request/response examples
- Error codes
- Authentication requirements
2. Interactive Documentation
- Swagger/OpenAPI
- Postman collections
- API explorers
3. Guides & Tutorials
- Getting started
- Authentication
- Common use cases
- Migration guides
Evolution
1. Backward Compatibility
- Add new fields as optional
- Don't remove or rename fields
- Use feature flags
- Version your API
2. Deprecation Policy
- Announce deprecations in advance
- Provide migration guides
- Maintain deprecated versions for a period
- Monitor usage of deprecated endpoints
Best Practices
1. Design First
- Design before implementation
- Use API-first approach
- Get feedback early
- Iterate on design
2. Keep It Simple (KISS)
- Favor simplicity over complexity
- Follow the principle of least surprise
- Avoid over-engineering
- Make common tasks easy
3. Don't Repeat Yourself (DRY)
- Reuse schemas and components
- Centralize common functionality
- Avoid code duplication
- Use references in OpenAPI/Swagger
4. You Aren't Gonna Need It (YAGNI)
- Implement features when needed
- Avoid speculative generality
- Keep the API surface small
- Focus on current requirements
5. Principle of Least Privilege
- Grant minimum necessary permissions
- Use scopes for OAuth
- Implement proper access controls
- Audit permissions regularly
6. Fail Fast, Fail Loudly
- Validate early
- Provide clear error messages
- Don't hide errors
- Log failures appropriately
Anti-Patterns to Avoid
1. Over-fetching/Under-fetching
- Problem: Getting too much or too little data
- Solution: Use field selection and sparse fieldsets
2. Chatty APIs
- Problem: Too many round-trips
- Solution: Batch requests or use GraphQL
3. Ignoring Caching
- Problem: Unnecessary load on servers
- Solution: Implement proper caching headers
4. Ignoring Rate Limiting
- Problem: API abuse and DDoS attacks
- Solution: Implement rate limiting and quotas
5. Poor Error Handling
- Problem: Unhelpful error messages
- Solution: Standardized error responses with codes
API Maturity Model
Level 0: HTTP as a Tunnel
- Single POST endpoint
- All actions as parameters
- No REST principles
Level 1: Resources
- Multiple URIs
- Resources as nouns
- Still mostly RPC-style
Level 2: HTTP Verbs
- Proper use of HTTP methods
- Status codes
- Still some RPC endpoints
Level 3: Hypermedia Controls
- HATEOAS
- Discoverable APIs
- Self-documenting
Measuring API Quality
1. Performance Metrics
- Response time (p50, p95, p99)
- Throughput (requests/second)
- Error rate
- Uptime/availability
2. Developer Experience
- Time to first successful call
- Documentation quality
- SDK availability
- Community support
3. Business Metrics
- API adoption rate
- Active developers
- Revenue impact
- Cost per API call
Conclusion
Designing great APIs requires careful consideration of many factors. By following these principles, you can create APIs that are:
- Intuitive: Easy to understand and use
- Efficient: Fast and resource-friendly
- Reliable: Consistent and available
- Secure: Protected against threats
- Maintainable: Easy to evolve over time
Remember that API design is an iterative process. Continuously gather feedback from your consumers and be prepared to make improvements based on real-world usage patterns.