Designing JSON APIs that are intuitive, maintainable, and scalable requires following established patterns and best practices. This guide covers the most important JSON API design patterns used by successful APIs from companies like GitHub, Stripe, and Twitter.
Whether you're building a public API, internal microservices, or a mobile backend, these patterns will help you create APIs that are easy to understand, use, and maintain.
1. Response Structure Patterns
Envelope Pattern
Wrap all responses in a consistent envelope structure. This provides metadata and makes error handling easier:
{
"success": true,
"data": {
"id": 123,
"name": "John Doe"
},
"meta": {
"timestamp": "2025-01-31T10:00:00Z",
"version": "v1"
}
}Data-Only Pattern
Some APIs return data directly without an envelope. This is simpler but less flexible:
{
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}2. Error Handling Patterns
Standardized Error Format
Use a consistent error structure across all endpoints. This makes error handling predictable:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid email format",
"field": "email",
"details": {
"expected": "email format",
"received": "invalid-email"
},
"timestamp": "2025-01-31T10:00:00Z"
}
}HTTP Status Codes
Use appropriate HTTP status codes with your JSON error responses:
| Status Code | Use Case |
|---|---|
| 400 | Bad Request - Invalid input |
| 401 | Unauthorized - Missing/invalid auth |
| 404 | Not Found - Resource doesn't exist |
| 500 | Internal Server Error |
3. Pagination Patterns
Offset-Based Pagination
Simple pagination using offset and limit. Good for small to medium datasets:
{
"data": [...],
"pagination": {
"offset": 0,
"limit": 20,
"total": 100,
"hasMore": true
}
}Cursor-Based Pagination
More efficient for large datasets. Uses cursors instead of offsets:
{
"data": [...],
"pagination": {
"cursor": "eyJpZCI6MTIzfQ",
"hasNext": true,
"nextCursor": "eyJpZCI6MTQzfQ"
}
}4. Filtering and Sorting Patterns
Query Parameters
Use query parameters for filtering and sorting. Keep it simple and consistent:
Example Request:
GET /api/users?status=active&sort=name&order=asc&limit=10JSON Query Language
For complex queries, accept JSON in the request body:
POST /api/users/search
{
"filters": {
"status": "active",
"age": { "$gte": 18, "$lte": 65 }
},
"sort": { "name": "asc" },
"limit": 20
}5. API Versioning Patterns
URL Versioning
Include version in the URL path. Most common and explicit:
/api/v1/usersHeader Versioning
Use custom headers to specify version:
X-API-Version: v2Version in Response
Always include version information in JSON responses:
{
"apiVersion": "v2",
"data": { ... }
}6. Nested Resources Pattern
Embedded Resources
Include related resources directly in the response when appropriate:
{
"id": 123,
"name": "John Doe",
"address": {
"street": "123 Main St",
"city": "New York"
}
}Linked Resources
For large or optional resources, provide links instead:
{
"id": 123,
"name": "John Doe",
"addressUrl": "/api/users/123/address"
}7. Bulk Operations Pattern
Batch Requests
Support batch operations for efficiency. Accept arrays of operations:
POST /api/users/batch
{
"operations": [
{ "action": "create", "data": { "name": "John" } },
{ "action": "update", "id": 123, "data": { "name": "Jane" } }
]
}Batch Response
Return results for each operation, including any errors:
{
"results": [
{ "success": true, "id": 124 },
{ "success": false, "error": "Validation failed" }
]
}8. Real-World API Examples
GitHub API Pattern
GitHub uses a clean, consistent structure with links for related resources:
{
"id": 123,
"name": "repository",
"full_name": "owner/repository",
"html_url": "https://github.com/owner/repository",
"owner": {
"login": "owner",
"id": 456
}
}Stripe API Pattern
Stripe uses nested objects and consistent error handling:
{
"id": "cus_123",
"object": "customer",
"email": "customer@example.com",
"created": 1640995200
}Test Your JSON APIs
Use our free online JSON tools to validate, format, and test your API responses. Compare API responses and ensure they follow best practices.
Try API Tools