JSON API Best Practices

How to design clean, consistent JSON REST APIs — naming, response structure, error handling, dates, pagination, and versioning, with practical examples.

Why consistency matters

A JSON API is a contract between your server and every client that consumes it. When responses are predictable — same key style, same shape, same error format — clients can be written once and trusted everywhere. When they are inconsistent, every endpoint becomes a special case that developers have to read, guess at, and work around.

The practices below are not about being clever. They are about being boring in the best possible way: a well-designed API is one where, after you have used a few endpoints, you can correctly guess how the rest behave. That predictability is the single biggest gift you can give the people building against your API.

Use consistent key naming

Pick one casing convention and use it for every key in every response. camelCase is common in JavaScript-heavy stacks, while snake_case is the norm in many Python, Ruby, and Go ecosystems. Either is fine — what matters is that you never mix them.

{
  "firstName": "Ada",
  "lastName": "Lovelace",
  "isAdmin": true
}

Avoid responses where one object uses first_name and another uses firstName. Inconsistent keys force clients to handle both, which is exactly the kind of friction a good API removes.

Keep response structure predictable

A given resource should always serialize to the same shape. Don't drop fields depending on context or change a field's type between requests. Many APIs wrap payloads in a small envelope so there is a stable place for the resource and for metadata:

{
  "data": {
    "id": 42,
    "title": "Hello World"
  },
  "meta": {
    "requestId": "a1b2c3"
  }
}

Return collections as JSON arrays, not as objects keyed by id. An array preserves order, is trivial to iterate, and keeps the shape consistent whether it holds zero, one, or a thousand items:

{
  "data": [
    { "id": 1, "title": "First" },
    { "id": 2, "title": "Second" }
  ]
}

Return meaningful status codes and structured errors

Use HTTP status codes for what they were designed for: 200 for success, 201 for a created resource, 400 for bad input, 401 and 403 for auth problems, 404 for missing resources, and 500 for server faults. Never return 200 with an error hidden in the body.

Pair the right status code with a consistent, machine-readable error object. A stable code lets clients branch on the failure, while message helps a developer debug it:

HTTP/1.1 422 Unprocessable Entity

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "The email field is required.",
    "field": "email"
  }
}

Use ISO 8601 for dates

Always serialize timestamps as ISO 8601 strings in UTC. They sort correctly as plain text, parse natively in nearly every language, and leave no ambiguity about time zones. Avoid Unix epoch integers and locale-specific formats like 06/28/2026.

{
  "createdAt": "2026-06-28T10:00:00Z",
  "updatedAt": "2026-06-28T14:30:15Z"
}

Paginate large collections

Never return an unbounded list. Offer pagination through query parameters such as ?page=2&limit=20 or a cursor, and include the totals clients need in meta so they can build paging controls without guessing:

{
  "data": [
    { "id": 21, "title": "Item 21" },
    { "id": 22, "title": "Item 22" }
  ],
  "meta": {
    "page": 2,
    "limit": 20,
    "totalItems": 137,
    "totalPages": 7
  }
}

For very large or frequently changing datasets, cursor-based pagination (returning a nextCursor token) avoids the drift and performance problems that offset paging can cause.

Version your API

APIs change. Versioning lets you ship breaking changes without breaking existing clients. The most common approach is to put the version in the URL path, which is explicit and easy to route:

GET https://api.example.com/v1/users
GET https://api.example.com/v2/users

Treat a published version as immutable: add fields freely, but introduce a new version before you remove or rename anything clients already depend on.

Other tips

  • Avoid leaking nulls. Omit fields that have no value rather than padding every object with null, unless a present-but-null field carries real meaning to clients.
  • Be consistent with booleans. Use real JSON true/false values, not "true" strings or 0/1 integers.
  • Don't expose internal fields. Keep password hashes, internal flags, and database internals out of responses. Serialize an explicit view of each resource rather than dumping the raw record.
  • Document the schema. Publish an OpenAPI specification or clear reference so consumers know every field, type, and error code without reverse-engineering your responses.

Validate your API responses

Paste a sample response into our free formatter to validate it, inspect its structure, and confirm your JSON is clean and consistent before you ship it.

Open JSON Formatter