Introduction to JSON API Design
Designing great JSON APIs is both an art and a science. A well-designed API is intuitive, consistent, predictable, and a joy for developers to use. Poorly designed APIs lead to frustration, buggy integrations, abandoned projects, and lost business opportunities. In this comprehensive guide, we'll cover 50+ proven best practices for designing world-class JSON APIs that developers will love.
✅ Good to Know
📊 Why API Design Matters in 2025
Why API Design Matters in 2025
APIs are the backbone of modern software development. In 2025, the average application uses over 50 different APIs. Poor API design has real costs:
Financial Cost
Poor APIs cost companies an average of $1.5M annually in developer time, support, and lost opportunities.
Time Cost
Developers spend 30% of their time dealing with poorly designed APIs instead of building features.
Reputation Cost
60% of developers will avoid using an API again if they had a bad experience.
1. Naming Conventions & Consistency
Consistency in naming makes APIs predictable and easier to use. Pick a convention and stick to it across your entire API.
✅ Recommended Conventions
- camelCase for JSON properties:
firstName,createdAt - Plural nouns for collection endpoints:
/users,/products - Lowercase for URLs:
/user-profilesnot/UserProfiles - Hyphens for multi-word resources:
/order-items - Query parameters use snake_case:
filter_by=name - Forward slashes to indicate hierarchy:
/users/123/orders
❌ Avoid These
- Mixed case in URLs:
/Users/JohnDoevs/users/johndoe - Verb-based endpoints (use HTTP methods):
/getUsershould beGET /users/1 - Inconsistent pluralization:
/userand/productstogether - Abbreviations:
/usrinstead of/users - File extensions:
/users.json(use content negotiation instead) - Underscores in URLs:
/user_profiles(hyphens are better for SEO)
Resource Naming Examples
| Resource Type | Good Example | Bad Example | Why It's Bad |
|---|---|---|---|
| Collection | /users | /userList | Verb instead of noun |
| Single Resource | /users/123 | /user?id=123 | Query param instead of path |
| Sub-resource | /users/123/orders | /orders?user=123 | Not expressing relationship |
| Action | /users/123/activate | /activateUser/123 | Verb before resource |
| Nested Resource | /users/123/orders/456 | /order/456?userId=123 | Missing relationship context |
2. API Versioning Strategies
APIs evolve. Versioning allows you to make breaking changes without affecting existing clients.
Four Main Versioning Approaches
1. URL Path Versioning
https://api.example.com/v1/users
Most common approach. Easy to see, cache-friendly, simple to implement. Pros: Clear, visible, works everywhere. Cons: Pollutes URL space.
✅ Recommended for: Public APIs, most use cases
2. Custom Header Versioning
Accept: application/vnd.example.v1+json
Keeps URLs clean. Uses content negotiation. Pros: Clean URLs, RESTful. Cons: Less visible, requires header inspection.
✅ Recommended for: API purists, internal APIs
3. Query Parameter Versioning
https://api.example.com/users?version=1
Simple but controversial. Pros: Very simple. Cons: Can be cached poorly, clutters query params.
⚠️ Not recommended for production APIs
4. Content Negotiation (Accept Header)
Accept: application/vnd.example.user.v1+json
Most RESTful approach. Uses HTTP content negotiation. Pros: Pure REST, clean URLs. Cons: Complex, not well understood.
✅ Recommended for: REST purists, hypermedia APIs
💡 Versioning Best Practices
- Use URL path versioning for public APIs (most intuitive for developers)
- Include version in response headers:
API-Version: v1 - Support at least 2 versions simultaneously during migration periods
- Deprecate versions with a clear timeline (6-12 months notice)
- Use semantic versioning for your API (v1.0.0, v1.1.0, v2.0.0)
- Never remove a version that still has active users
- Document your versioning strategy clearly
- Consider backward-compatible changes before creating new versions
3. HTTP Methods & Their Proper Use
Use HTTP methods correctly to make your API RESTful and predictable. Each method has specific semantics that developers expect.
| Method | Purpose | Idempotent | Safe | Body | Example |
|---|---|---|---|---|---|
| GET | Retrieve resource | ✅ Yes | ✅ Yes | ❌ No | GET /users/123 |
| POST | Create new resource | ❌ No | ❌ No | ✅ Yes | POST /users |
| PUT | Full update (replace) | ✅ Yes | ❌ No | ✅ Yes | PUT /users/123 |
| PATCH | Partial update | ❌ No | ❌ No | ✅ Yes | PATCH /users/123 |
| DELETE | Remove resource | ✅ Yes | ❌ No | ⚠️ Optional | DELETE /users/123 |
| HEAD | GET without body | ✅ Yes | ✅ Yes | ❌ No | HEAD /users/123 |
| OPTIONS | Get allowed methods | ✅ Yes | ✅ Yes | ❌ No | OPTIONS /users |
📘 Info
⚠️ Important: Idempotency Explained
An idempotent operation means making the same request multiple times has the same effect as making it once. GET, PUT, DELETE are idempotent. POST is NOT idempotent - sending the same POST twice will create two resources.
Example: DELETE /users/123 - first request deletes user, subsequent requests return 404 (same end state - user doesn't exist).
4. HTTP Status Codes Complete Guide
Use appropriate HTTP status codes to communicate the result of API requests. Never return 200 for errors!
2xx - Success Codes
| Code | Name | When to Use |
|---|---|---|
| 200 | OK | GET, PUT, PATCH, DELETE successful |
| 201 | Created | POST successful (new resource created) |
| 202 | Accepted | Request accepted for async processing |
| 204 | No Content | DELETE successful, no body returned |
3xx - Redirection Codes
| Code | Name | When to Use |
|---|---|---|
| 301 | Moved Permanently | Resource URL changed permanently |
| 304 | Not Modified | Caching - content hasn't changed |
4xx - Client Error Codes
| Code | Name | When to Use |
|---|---|---|
| 400 | Bad Request | Malformed JSON, missing required fields, validation errors |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Authenticated but not authorized to access resource |
| 404 | Not Found | Resource doesn't exist |
| 405 | Method Not Allowed | HTTP method not supported for this endpoint |
| 409 | Conflict | Resource conflict (duplicate, version mismatch) |
| 422 | Unprocessable Entity | Semantic errors (valid JSON but invalid data) |
| 429 | Too Many Requests | Rate limit exceeded |
5xx - Server Error Codes
| Code | Name | When to Use |
|---|---|---|
| 500 | Internal Server Error | Generic server error (avoid leaking details) |
| 502 | Bad Gateway | Upstream server error |
| 503 | Service Unavailable | Server overloaded or under maintenance |
| 504 | Gateway Timeout | Upstream server timeout |
5. Error Handling & Response Format
Consistent, informative error responses are crucial for developer experience.
Standard Error Response Format
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request contains invalid fields",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Email address must be valid"
},
{
"field": "age",
"code": "MINIMUM",
"message": "Age must be at least 18"
}
],
"requestId": "req_abc123xyz",
"timestamp": "2024-01-15T10:30:00Z",
"docs": "https://api.example.com/docs/errors#VALIDATION_ERROR"
}
}
Error Response Examples by Status Code
400 Bad Request - Validation Error
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed for request body",
"details": [
{ "field": "email", "message": "Required field missing" },
{ "field": "age", "message": "Must be a positive integer" }
]
}
}
401 Unauthorized - Invalid API Key
{
"error": {
"code": "INVALID_API_KEY",
"message": "The provided API key is invalid or expired",
"docs": "https://api.example.com/docs/authentication"
}
}
429 Too Many Requests - Rate Limited
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Try again in 30 seconds.",
"rateLimit": {
"limit": 100,
"remaining": 0,
"reset": 1705315200
}
}
}
6. Pagination for Large Responses
Never return all records at once. Always implement pagination for collection endpoints.
Common Pagination Strategies
1. Offset-Limit
GET /users?offset=0&limit=20
✅ Easy to implement
❌ Inefficient for large offsets
❌ Data can change between requests
2. Cursor-Based (Keyset)
GET /users?cursor=abc123&limit=20
✅ Efficient, stable
✅ No missing/duplicate records
❌ Harder to implement
3. Page-Based
GET /users?page=2&size=20
✅ Simple, user-friendly
❌ Same issues as offset-limit
Standard Pagination Response Format
{
"data": [
{ "id": 1, "name": "John" },
{ "id": 2, "name": "Jane" }
],
"pagination": {
"total": 1000,
"limit": 20,
"offset": 0,
"next": "/users?offset=20&limit=20",
"prev": null,
"first": "/users?offset=0&limit=20",
"last": "/users?offset=980&limit=20"
},
"links": {
"self": "/users?offset=0&limit=20",
"next": "/users?offset=20&limit=20",
"last": "/users?offset=980&limit=20"
}
}
7. Filtering, Sorting & Field Selection
Give clients control over what data they receive to improve performance and reduce bandwidth.
Filtering Patterns
# Basic filtering
GET /users?status=active
GET /users?age=30
# Range filtering
GET /users?age_min=18&age_max=65
GET /users?created_at_after=2024-01-01
# Multiple values (OR logic)
GET /users?status=active,pending
# Search
GET /users?search=john
# Advanced filtering (JSON format)
GET /users?filter={"status":"active","age":{"$gte":18}}
GET /users?filter[status]=active&filter[age][$gte]=18
Sorting Patterns
# Simple sort
GET /users?sort=name
# Sort with direction
GET /users?sort=-created_at # descending
GET /users?sort=name,age # multiple fields
# Separate sort and order
GET /users?sort_by=name&order=desc
# Nested field sorting
GET /users?sort=address.city
Field Selection (Sparse Fieldsets)
# Request only needed fields
GET /users?fields=id,name,email
# For multiple resource types
GET /users/123/orders?fields[users]=name&fields[orders]=id,total
# Response includes only requested fields
{
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}
8. API Security Best Practices
🚨 Critical Security Checklist
- Always use HTTPS - no exceptions
- Validate and sanitize all input
- Implement proper authentication (OAuth2, JWT, API keys)
- Use rate limiting to prevent abuse
- Never expose internal error details
- Implement CORS properly
- Use security headers (CSP, HSTS, X-Frame-Options)
- Log security events and monitor for attacks
- Regular security audits and penetration testing
- Keep dependencies updated
✅ Recommended Security Headers
Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'none'
Cache-Control: no-store
X-XSS-Protection: 1; mode=block
9. Authentication & Authorization
Authentication Methods Comparison
| Method | Best For | Complexity | Security | Example |
|---|---|---|---|---|
| API Keys | Simple APIs, server-to-server | Low | Medium | X-API-Key: abc123 |
| JWT (Bearer) | Stateless auth, mobile apps | Medium | High | Authorization: Bearer <token> |
| OAuth2 | Third-party access, user delegation | High | Very High | Authorization code flow |
| Basic Auth | Internal tools, legacy | Low | Low (use with HTTPS) | Authorization: Basic <base64> |
JWT Best Practices
// JWT payload example (keep it small!)
{
"sub": "user_123",
"email": "john@example.com",
"roles": ["admin", "user"],
"permissions": ["read:users", "write:users"],
"iat": 1705315200, // Issued at
"exp": 1705318800, // Expires (1 hour)
"iss": "api.example.com",
"aud": "example-api"
}
// Security best practices:
// 1. Use short expiration times (15-60 minutes)
// 2. Implement refresh tokens
// 3. Store JWTs securely (HttpOnly cookies, not localStorage)
// 4. Use strong signing algorithms (HS256, RS256)
// 5. Validate signature on every request
// 6. Maintain a blacklist for revoked tokens
10. Rate Limiting & Throttling
Protect your API from abuse and ensure fair usage among all consumers.
Rate Limit Headers (RFC 6585)
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000 # Requests per time window
X-RateLimit-Remaining: 987 # Remaining requests
X-RateLimit-Reset: 1705315200 # Reset time (Unix timestamp)
Retry-After: 3600 # Seconds to wait (when rate limited)
Rate Limiting Strategies
🌐 IP-Based Limiting
Limit per IP address. Simple but not ideal for shared networks (offices, schools).
limit: 100 requests per minute per IP
👤 User-Based Limiting
Limit per API key or user account. More fair for legitimate users.
limit: 1000 requests per hour per API key
⚡ Endpoint-Based Limiting
Different limits for different endpoints (e.g., search vs write operations).
GET /search: 10 req/min
POST /orders: 100 req/hour
📊 Sliding Window
More accurate than fixed windows. Prevents bursts at window boundaries.
Redis sorted sets or sliding window counters
11. API Documentation That Developers Love
Great documentation is as important as the API itself. Poor documentation = poor adoption.
Documentation Must-Haves
- Quick Start Guide - Get developers making their first call in 5 minutes
- Authentication Guide - Clear instructions for API keys, OAuth, or JWT
- Interactive API Console - Try endpoints directly in the browser (Swagger UI, Redoc)
- Code Examples - At least 5 languages (cURL, JavaScript, Python, Ruby, Java)
- Error Codes Reference - Every possible error with troubleshooting tips
- Rate Limit Documentation - Limits, headers, and what to do when exceeded
- Changelog - Clear version history with deprecation notices
- FAQ - Common questions and solutions
- SDK Documentation - If you provide official SDKs, document them well
- Postman Collection - Downloadable collection for testing
Documentation Tools
Swagger/OpenAPI
Industry standard for API specification
Redoc
Beautiful documentation from OpenAPI
Postman
Collaborative API development platform
Example: Great API Documentation Structure
├── Getting Started
│ ├── Introduction
│ ├── Authentication
│ └── Your First Request
├── API Reference
│ ├── Users
│ │ ├── GET /users
│ │ ├── POST /users
│ │ ├── GET /users/{id}
│ │ └── PUT /users/{id}
│ ├── Orders
│ └── Products
├── Guides
│ ├── Pagination
│ ├── Filtering & Sorting
│ ├── Error Handling
│ └── Webhooks
├── Resources
│ ├── Changelog
│ ├── FAQ
│ ├── Rate Limits
│ └── Status Page
└── SDKs
├── JavaScript
├── Python
└── Java
12. Performance Optimization Techniques
⚡ 15 Performance Tips
- Implement caching (Redis, CDN, database query caching)
- Use compression (gzip, Brotli) - reduces size by 70-90%
- Return only needed fields (sparse fieldsets)
- Use pagination for all list endpoints
- Implement HTTP caching (ETag, Last-Modified)
- Use connection pooling for database calls
- Optimize database queries (indexes, query optimization)
- Use asynchronous processing for heavy operations
- Batch multiple requests (GraphQL, batch endpoints)
- Use CDN for static assets and responses
- Minify JSON responses (remove whitespace)
- Use HTTP/2 or HTTP/3 for multiplexing
- Implement database read replicas for GET requests
- Use message queues for background jobs
- Monitor performance with APM tools
📈 Response Time Targets
P95 Latency Goals
Simple queries: <100ms
Complex queries: <500ms
Heavy operations: <2000ms (2 sec)
13. Consistent Response Format
Always return a consistent response structure to make client integration predictable.
Recommended Response Envelope
// Success Response
{
"success": true,
"data": {
// Response data here
},
"meta": {
"requestId": "req_abc123",
"timestamp": "2024-01-15T10:30:00Z",
"version": "v1"
}
}
// Collection Response (with pagination)
{
"success": true,
"data": [...],
"meta": {
"requestId": "req_abc123",
"timestamp": "2024-01-15T10:30:00Z",
"version": "v1",
"pagination": {
"total": 1000,
"limit": 20,
"offset": 0,
"next": "/users?offset=20&limit=20"
}
}
}
// Error Response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input provided",
"details": [...]
},
"meta": {
"requestId": "req_abc123",
"timestamp": "2024-01-15T10:30:00Z",
"version": "v1"
}
}
Complete API Design Checklist
📋 Design & Structure
- ✓ Use plural nouns for collection endpoints
- ✓ Use consistent naming conventions (camelCase, snake_case)
- ✓ Use HTTP methods correctly
- ✓ Use appropriate HTTP status codes
- ✓ Implement versioning strategy
- ✓ Use nested resources for relationships
- ✓ Avoid deep nesting (>3 levels)
🔒 Security
- ✓ Always use HTTPS
- ✓ Implement authentication (OAuth2, JWT, API keys)
- ✓ Validate and sanitize all input
- ✓ Implement rate limiting
- ✓ Set security headers (CSP, HSTS, X-Frame-Options)
- ✓ Never expose internal errors
- ✓ Use CORS properly
📄 Documentation
- ✓ Quick start guide
- ✓ Authentication guide
- ✓ Interactive API console
- ✓ Code examples (multiple languages)
- ✓ Error code reference
- ✓ Rate limit documentation
- ✓ Changelog
⚡ Performance
- ✓ Implement pagination
- ✓ Use compression (gzip/Brotli)
- ✓ Implement caching (HTTP, Redis)
- ✓ Use sparse fieldsets
- ✓ Optimize database queries
- ✓ Use CDN for static content
- ✓ Monitor performance metrics
Frequently Asked Questions
Q: Should I use PUT or PATCH for updates?
Use PUT when the client sends the complete resource (all fields). Use PATCH when the client sends only the fields that changed. PUT is idempotent, PATCH is not.
Q: How should I handle API versioning?
Use URL path versioning (e.g., /v1/users) for public APIs. It's the most visible and easiest for developers to understand. Include version in response headers and maintain backward compatibility for 6-12 months.
Q: What's the best pagination method?
Cursor-based pagination is best for large datasets and real-time data. Offset-limit is simpler but inefficient for large offsets. Use cursor for performance-critical endpoints, offset for simple admin panels.
Q: Should I return 404 or empty array for empty collections?
Return 200 OK with an empty array ([]). 404 implies the resource doesn't exist, but an empty collection is still a valid resource.
Q: How do I handle partial success for batch operations?
Return 207 Multi-Status with per-item status codes. Include details about which operations succeeded and which failed.
Ready to Build Better APIs?
Use our free JSON formatter to validate and beautify your API responses before deploying.
🚀 Format JSON Now →