API Response Formats: Best Practices for Consistent APIs
Inconsistent API responses are a top complaint from frontend developers. When every endpoint returns data in a different shape, client code becomes littered with special cases. A consistent response format improves developer experience, reduces bugs, and makes your API self-documenting.
The Response Envelope
Wrap every response in a consistent structure:
Success (Single Resource)
{
"data": {
"id": "user_123",
"name": "Alice",
"email": "alice@example.com",
"createdAt": "2024-01-15T10:30:00Z"
},
"meta": {
"requestId": "req_abc123"
}
}
Success (Collection)
{
"data": [
{ "id": "user_123", "name": "Alice" },
{ "id": "user_456", "name": "Bob" }
],
"meta": {
"total": 142,
"page": 1,
"perPage": 20,
"requestId": "req_def456"
}
}
Error
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "Invalid email format" }
]
},
"meta": {
"requestId": "req_ghi789"
}
}
The key principle: clients always check for data or error at the top level. Never mix success data and error information in the same response.
Validate your response format with our JSON Validator.
HTTP Status Codes
Use status codes correctly β they are the first thing clients check:
Success Codes
| Code | When |
|---|---|
| 200 OK | GET success, PUT/PATCH success |
| 201 Created | POST that created a resource |
| 204 No Content | DELETE success (no body) |
Client Error Codes
| Code | When |
|---|---|
| 400 Bad Request | Malformed request syntax |
| 401 Unauthorized | Missing or invalid authentication |
| 403 Forbidden | Authenticated but not authorized |
| 404 Not Found | Resource does not exist |
| 409 Conflict | Resource state conflict (duplicate) |
| 422 Unprocessable | Valid syntax but semantic errors |
| 429 Too Many Requests | Rate limit exceeded |
Server Error Codes
| Code | When |
|---|---|
| 500 Internal Server Error | Unexpected server failure |
| 502 Bad Gateway | Upstream service failure |
| 503 Service Unavailable | Temporary overload or maintenance |
Error Response Design
Good error responses help developers debug quickly:
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "User with ID 'user_999' not found",
"details": [],
"documentationUrl": "https://api.example.com/docs/errors#RESOURCE_NOT_FOUND"
},
"meta": {
"requestId": "req_xyz789",
"timestamp": "2024-01-15T10:30:00Z"
}
}
Rules:
codeis machine-readable (constant string, not HTTP status)messageis human-readable (can change without breaking clients)detailsprovides field-level errors for validation failuresrequestIdenables support team to trace the request in logs
Pagination Patterns
Offset-Based
Simple and supports jumping to arbitrary pages:
{
"data": ["..."],
"meta": {
"page": 2,
"perPage": 20,
"total": 142,
"totalPages": 8
},
"links": {
"first": "/api/users?page=1&per_page=20",
"prev": "/api/users?page=1&per_page=20",
"next": "/api/users?page=3&per_page=20",
"last": "/api/users?page=8&per_page=20"
}
}
Cursor-Based
Better for real-time data and large datasets:
{
"data": ["..."],
"meta": {
"hasNext": true,
"hasPrev": true
},
"links": {
"next": "/api/users?cursor=eyJpZCI6MTQzfQ&limit=20",
"prev": "/api/users?cursor=eyJpZCI6MTIzfQ&limit=20&direction=prev"
}
}
Date and Time
Always use ISO 8601 with timezone information:
{
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T14:22:33+00:00",
"expiresAt": "2024-12-31T23:59:59Z"
}
Never use:
- Unix timestamps (ambiguous: seconds or milliseconds?)
- Local dates without timezone
- Custom formats like MM/DD/YYYY
For more on timestamp handling, see our Unix Timestamps guide.
Null vs Absent Fields
Two common approaches:
Include with null (explicit):
{ "name": "Alice", "avatar": null, "bio": null }
Omit absent fields (sparse):
{ "name": "Alice" }
Recommendation: Be consistent within your API. Explicit nulls are better for typed languages (clients know the field exists). Sparse is better for bandwidth-constrained environments.
Versioning Responses
When response format changes, version your API:
GET /api/v2/users/123
Breaking changes that require versioning:
- Removing a field
- Changing a field type
- Renaming a field
- Changing the response envelope structure
Non-breaking changes (safe without versioning):
- Adding new fields
- Adding new endpoints
- Adding new enum values
FAQ
Should I wrap successful responses in a data key or return the resource directly?
Using a data wrapper provides a consistent structure for all responses and leaves room for metadata, pagination, and links alongside the data. Returning the resource directly is simpler for single-resource endpoints. Most modern APIs use the wrapper approach for consistency.
How should I handle partial failures in batch operations?
Return 200 with a response that includes both successes and failures in the data object. Using 207 Multi-Status (WebDAV) is another option but less commonly used in REST APIs.
Related Resources
- JSON Formatter β Format API responses
- JSON API Design Patterns β Comprehensive API design guide
- JSON Schema Validation β Validate response structures