REST API JSON Best Practices: Naming, Errors, Pagination & Versioning
Why these decisions matter
Once your API is in production, every shape choice becomes load-bearing. Clients depend on field names, error formats and pagination semantics. Changing them is a breaking change. Get them right early.
Naming conventions
Use camelCase for keys. It's the most common convention in JSON ecosystems and matches JavaScript naturally. snake_case is also fine if your backend is Python or Ruby — just be consistent across the whole API.
{
"userId": 42,
"createdAt": "2026-04-22T10:30:00Z",
"isActive": true
}
Always use ISO 8601 for dates
"createdAt": "2026-04-22T10:30:00Z"
Never invent custom formats. Never send Unix timestamps unless you have a specific reason — they're harder to debug.
Use strings for large IDs
JSON numbers are IEEE-754 doubles. IDs over 2^53 lose precision. Database row IDs, snowflake IDs and similar should always be strings:
{ "id": "9007199254740993" }
A consistent error envelope
Every error response should have the same shape so clients can write one handler:
{
"error": {
"code": "user_not_found",
"message": "No user exists with id 42.",
"details": { "id": 42 },
"requestId": "req_abc123"
}
}
- code is machine-readable and stable across releases
- message is human-readable
- requestId lets users quote it when they email support
- details is structured context for debugging
Pagination patterns
Cursor-based (recommended for most APIs):
{
"data": [],
"pageInfo": { "endCursor": "eyJpZCI6MTAwfQ==", "hasNextPage": true }
}
Offset-based (simpler, but slow on large tables):
{
"data": [],
"page": 3,
"pageSize": 50,
"total": 12345
}
Cursor pagination scales; offset pagination doesn't.
Versioning
Three common strategies — pick one and stick with it:
- URL versioning —
/v1/users,/v2/users— most explicit - Header versioning —
Accept: application/vnd.myapi.v2+json— keeps URLs clean - No versioning, evolve carefully — only add fields, never remove or change semantics. Works for small private APIs.
Field stability rules
- Adding a new field is non-breaking — clients should ignore unknown fields
- Removing a field is breaking
- Changing a field's type is breaking
- Changing a field's semantics is breaking (and worse, silent)
Document these rules and enforce them in code review.
Null vs missing
Be intentional. null means "we know there's no value." A missing field means "we don't have data" or "this isn't applicable." Pick one convention per field and document it.
Don't reinvent: study Stripe or use JSON:API
If you're stuck designing from scratch, study Stripe's API — it's the gold standard. Or adopt the JSON:API spec for a fully-specified format.
Test your responses
Format and validate your sample responses with JSONNeat and the validator to catch shape regressions before clients do.
Try the tools: JSON Formatter · Validator · Minifier