Development
JSON REST API Design Best Practices: Structure, Naming, and Versioning
Published: 2026-05-01 · 4-6 min read
| By Li Xiaoyao
A well-designed JSON API is predictable. Developers can guess how new endpoints behave before reading the documentation. A poorly designed one requires constant reference to docs, produces subtle bugs from inconsistent naming, and breaks clients when fields change. The cost of bad API design compounds over time —every client that integrates against it inherits the technical debt.
There are three common JSON key naming styles: snake_case (used by Python, GitHub API, Stripe), camelCase (used by JavaScript-heavy APIs, Twilio, Salesforce), and kebab-case (rare in JSON, common in URL paths). The choice matters less than consistency. Pick one for your organization and enforce it with linting. Mixing conventions within a single API —userId in one endpoint, user_id in another —is the worst outcome.
Standard Response Envelope
{
"success": true,
"data": {
"id": "usr_abc123",
"email": "[email protected]",
"name": "Alice Chen",
"created_at": "2026-05-01T08:00:00Z"
},
"meta": {
"request_id": "req_xyz789",
"version": "2.0",
"timestamp": "2026-05-01T09:00:00Z"
}
}
// Error response —same structure
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Email address is already registered",
"details": [
{
"field": "email",
"rule": "unique",
"message": "This email is already in use"
}
]
},
"meta": {
"request_id": "req_abc456",
"version": "2.0",
"timestamp": "2026-05-01T09:00:01Z"
}
}
Pagination Patterns
{
"data": [
{ "id": "usr_001", "name": "Alice" },
{ "id": "usr_002", "name": "Bob" },
{ "id": "usr_003", "name": "Carol" }
],
"pagination": {
"cursor": "eyJpZCI6InVzcl8wMDMifQ",
"has_next": true,
"has_prev": false,
"limit": 20,
"total": 1542
}
}
// Client requests next page:
// GET /api/users?cursor=eyJpZCI6InVzcl8wMDMifQ&limit=20
// Offset-based (simpler but degrades at scale):
{
"pagination": {
"page": 1,
"per_page": 20,
"total_pages": 78,
"total_count": 1542
}
}
Versioning Strategies
| Strategy | Example | Pros | Cons |
|---|
| URL path | /api/v2/users | Explicit, cache-friendly | URL pollution |
| Header | API-Version: 2.0 | Clean URLs | Harder to test in browser |
| Query param | /api/users?version=2 | Easy to test | Can be ignored by proxies |
| Accept header | Accept: application/vnd.api+json;version=2 | RESTful purist approach | Complex for clients |
// Version in URL path —most practical choice
GET /api/v1/users // Legacy version
GET /api/v2/users // Current version with breaking changes
GET /api/v2/users/:id/orders // Consistent prefix
// Rules for breaking vs non-breaking changes:
// Non-breaking (safe to add without version bump):
// - Adding new optional fields to responses
// - Adding new optional query parameters
// - Adding new endpoints
// Breaking changes (REQUIRE version bump):
// - Renaming or removing fields
// - Changing field types
// - Changing error response format
// - Making previously optional fields required
// Deprecation header for old versions:
// Deprecation: true
// Sunset: Sat, 1 Jan 2027 00:00:00 GMT
Boolean and Null Handling
Use native JSON booleans (true/false), not strings ("true"/"false") or integers (1/0). Reserve null for genuinely missing or unknown values —avoid using it to mean 'not applicable' when omitting the field entirely is cleaner. Never return undefined in JSON responses; serialize all defined fields explicitly.
Date and Time Fields
// Always use ISO 8601 with timezone for timestamps
{
"created_at": "2026-05-01T09:00:00Z", // UTC preferred
"updated_at": "2026-05-01T09:15:00Z",
"scheduled_for": "2026-05-02T14:00:00+08:00", // Local time with offset
"expires_at": "2026-05-08T09:00:00Z",
// For dates without time: use date-only format
"birth_date": "1990-03-15",
"fiscal_year_end": "2026-12-31",
// For durations: use ISO 8601 duration or seconds
"session_duration": "PT30M", // ISO 8601: 30 minutes
"timeout_seconds": 1800 // Or just seconds as integer
}
ID Formats
| ID Format | Example | Best For |
|---|
| UUID v4 | 550e8400-e29b-41d4-a716-446655440000 | Distributed systems, privacy |
| Prefixed ID | usr_abc123xyz | Multi-entity APIs (Stripe style) |
| Sequential int | 12345 | Simple apps, SQL-native |
| ULID | 01ARZ3NDEKTSV4RRFFQ69G5FAV | Time-sortable, UUID-compatible |
| Nanoid | V1StGXR8_Z5jdHi6B-myT | URL-safe, compact |
Key Takeaways
- Consistency beats perfection —pick a naming convention (snake_case or camelCase) and enforce it across the entire API
- Use a standard response envelope with
success, data, error, and meta fields - Always include timestamps in ISO 8601 format with timezone information
- Version your API in the URL path for clarity and cache-friendliness
- Use cursor-based pagination for large datasets; offset pagination degrades past a few thousand records
- Prefixed IDs (
usr_abc123) prevent mixing ID types across different entity types
Core Principles of JSON REST API Design
REST (Representational State Transfer) APIs that use JSON as their data format have become the dominant pattern for web service communication. A well-designed JSON REST API follows several core principles that ensure consistency, scalability, and developer happiness. Resource-oriented design means modeling your API around nouns (resources) rather than verbs (actions). For example, /users/123 represents a specific user resource, and HTTP methods (GET, POST, PUT, PATCH, DELETE) define the operations on that resource.
Consistent naming conventions significantly impact API usability. Use camelCase for JSON property names (as preferred by JavaScript/TypeScript ecosystems) or snake_case (as preferred by Python/Ruby ecosystems), but never mix the two within a single API. Property names should be descriptive and self-documenting: "createdAt" is better than "ts", and "emailAddress" is better than "em". Avoid abbreviations unless they're universally understood (like "id" or "url").
Proper HTTP status code usage communicates API semantics clearly. Return 200 for successful GET requests, 201 for successful resource creation, 204 for successful deletions, 400 for client errors, 401 for authentication failures, 403 for authorization failures, 404 for missing resources, 409 for conflicts (like duplicate entries), 422 for validation errors, and 500 for unexpected server errors. Always include a descriptive error message in the response body with a machine-readable error code.
Key Insight: Resource-oriented design with consistent HTTP status codes creates APIs that developers can navigate without documentation. When every endpoint follows the same conventions, integrating a new resource becomes pattern recognition, not guesswork.
Designing JSON Response Structures
The structure of JSON API responses directly affects how easily developers can integrate with your service. Envelope vs. non-envelope responses: An envelope wraps the response data in a container object with metadata like pagination, error status, and API version. Example envelope: { "data": { ... }, "meta": { "page": 1, "totalPages": 42 }, "errors": null }. Non-envelope responses return the raw data directly, which is simpler but provides less context for error handling and pagination.
Pagination strategies are critical for endpoints returning collections. Offset-based pagination (?page=3&limit=20) is the simplest to implement but can produce inconsistent results when underlying data changes between requests. Cursor-based pagination (?cursor=eyJpZCI6MTIzfQ==&limit=20) provides stable pagination even with concurrent data modifications. Keyset pagination (?after_id=123&limit=20) offers a good balance of simplicity and stability. Always include total count or a "hasMore" flag in pagination metadata.
Partial responses and sparse fieldsets allow clients to request only the fields they need, reducing payload size and improving performance. Support field selection via query parameters: ?fields=id,name,email. This is particularly valuable for mobile clients with limited bandwidth and devices with constrained memory. The JSON:API specification provides a standardized approach to sparse fieldsets that's worth adopting for public APIs.
Key Insight: Cursor-based pagination outlasts offset-based pagination at scale—offset drifts when underlying data changes between requests, while cursors provide stable, repeatable pages even under concurrent modifications.
Versioning Strategies for JSON APIs
API versioning is one of the most debated topics in REST API design. URL-based versioning (/v1/users, /v2/users) is the most visible and easiest to implement, but it violates the REST principle that URLs should identify resources, not API versions. Header-based versioning (Accept: application/vnd.company.v2+json) keeps URLs clean but requires clients to set custom headers and complicates testing with tools like curl.
Query parameter versioning (/users?version=2) is simple but mixes resource identification with version control. Content negotiation using standard Accept headers (Accept: application/json;version=2) follows HTTP semantics most closely. For most teams, URL-based versioning provides the best balance of simplicity and clarity, despite its theoretical impurity. The practical benefit of being able to test different API versions side by side in a browser outweighs the purist concerns for most use cases.
Regardless of the versioning strategy, backward compatibility is paramount. New API versions should extend, not break, the previous version's contract. Additive changes (new fields, new endpoints) don't require a version bump if they don't affect existing clients. Breaking changes (removing fields, changing field types, renaming endpoints) require a new version and a migration period where both old and new versions run concurrently.
Key Insight: URL-based versioning (/v2/users) wins on simplicity despite REST purist objections. The ability to test v1 and v2 side by side in a browser outweighs theoretical concerns for most production teams.
Security Best Practices for JSON APIs
Security must be designed into JSON APIs from the first endpoint, not bolted on later. Authentication: Use industry-standard protocols (OAuth 2.0, OpenID Connect) rather than custom authentication schemes. JSON Web Tokens (JWT) provide a stateless authentication mechanism that scales well across microservices. Always validate JWT signatures, check expiration, and verify the issuer before trusting the token's claims.
Input validation: Never trust client-supplied data. Validate all input against a JSON Schema definition before processing. Check for injection attacks—even in JSON payloads, SQL injection, NoSQL injection, and command injection are possible if you're not careful. Use parameterized queries for database operations and avoid dynamic construction of queries from user input. Validate string lengths, number ranges, and array sizes to prevent denial-of-service attacks through excessively large payloads.
Rate limiting and throttling: Protect your API from abuse by implementing rate limits. Return 429 (Too Many Requests) with a Retry-After header when limits are exceeded. Use token bucket or sliding window algorithms for accurate rate limiting. Consider implementing graduated rate limits: higher limits for authenticated users, lower limits for anonymous requests. Always document rate limits clearly in your API documentation.
Key Insight: JSON API security is layered—authentication (OAuth 2.0/JWT), input validation (JSON Schema), and rate limiting (token bucket) must all be in place before the first endpoint goes live. Each layer fails independently; defense in depth is the only reliable strategy.
CORS configuration: Cross-Origin Resource Sharing headers must be carefully configured. Never use Access-Control-Allow-Origin: * with credentials. Specify exact allowed origins rather than wildcards. Only allow the HTTP methods your API actually supports. Set appropriate Access-Control-Max-Age values to reduce preflight request overhead for performance-sensitive clients.