JSON API Design Best Practices: Naming, Dates, Money, Errors and Pagination

API design 11 min readUpdated October 12, 2026

A JSON API is a promise to other developers about the shape of your data. Small decisions made early, such as how you write dates or whether a missing value is null, are very hard to change once clients depend on them. These practices come from the mistakes teams repeat most often. Use them as a checklist when you design a new API or review an existing one.

1. Be consistent about names

  • Pick one key style, either camelCase (createdAt) or snake_case (created_at), and use it everywhere. Mixing them is the most common and most annoying inconsistency.
  • Use the same name for the same concept across endpoints. If a user's id is userId in one response, it should not be uid or user_id in another.
  • Use plural names for arrays (orders) and singular names for single objects (order).
  • Do not use data as keys. {"2026-10-12": 5} is hard to document and validate; prefer a list of objects such as [{"date": "2026-10-12", "count": 5}].

2. Use the right type for each value

Kind of dataRecommendedAvoid
Dates and timesISO 8601 / RFC 3339 strings in UTC, such as "2026-10-12T09:30:00Z"Unix timestamps with no stated unit, or local formats like "12/10/2026" that mean different things in different countries
MoneyInteger minor units (24950 paise or cents) plus a currency code, or a decimal string like "249.50"Floating-point numbers, which cannot represent most decimal fractions exactly
IdentifiersStrings, particularly if they are large or could have leading zerosNumbers beyond 9,007,199,254,740,991, which JavaScript clients silently round
FlagsReal booleans: true and false"yes", "Y", 1 or "true" as a string
Fixed choicesLowercase string constants like "shipped"Magic numbers such as 3
UnitsA clear field name (weightKg) or a separate unit fieldA bare number whose unit has to be guessed

Keep each field to one type. A field that is sometimes a string and sometimes an object, or sometimes a single object and sometimes an array, forces every client to write defensive code and eventually breaks one of them.

3. Decide what null and "missing" mean

There are three states for an optional value: it has a value, it is explicitly empty, or it is not mentioned. Choose a convention and document it:

  • In responses, either always include the key (using null for no value) or omit absent keys. Always including keys gives clients a predictable shape and is easier to type-check.
  • In update requests, treat a missing key as "leave unchanged" and null as "clear this field". This distinction is what makes partial updates (PATCH) work.
  • Return empty arrays as [], not null, so clients can loop without checking.

4. Return an object at the top level

A response that is a bare array cannot grow. If you later need to add a total count or a link to the next page, you would have to break every client. Wrapping the list in an object leaves room for it.

{
  "data": [
    { "id": "ord_1001", "status": "shipped", "total": { "amount": 24950, "currency": "INR" } },
    { "id": "ord_1002", "status": "pending", "total": { "amount": 8000, "currency": "INR" } }
  ],
  "page": {
    "nextCursor": "eyJpZCI6Im9yZF8xMDAyIn0",
    "hasMore": true
  }
}
A list response that can gain new metadata without breaking clients

5. Paginate every list

A list endpoint that returns everything works on day one and fails when the data grows. Always put a limit on the page size, and prefer cursor-based pagination for data that changes: the client sends back the cursor from the previous page and gets the next items, with no skipped or repeated records when new data arrives. Offset pagination (?page=3) is simpler and fine for small, stable data sets, but it gets slow for deep pages and unreliable under concurrent writes.

6. Make errors consistent and machine-readable

Use proper HTTP status codes (400 for invalid input, 401 and 403 for authentication and permission problems, 404 for not found, 409 for conflicts, 429 for rate limits) and give every error the same body shape. RFC 9457, "Problem Details for HTTP APIs", defines a standard format with the media type application/problem+json.

{
  "type": "https://api.example.com/problems/validation-error",
  "title": "Your request is not valid",
  "status": 400,
  "detail": "2 fields failed validation.",
  "errors": [
    { "field": "email", "message": "must be a valid email address" },
    { "field": "age", "message": "must be at least 18" }
  ]
}
Report every problem at once, so the caller can fix them in one pass

Never leak stack traces, SQL or internal hostnames in error bodies.

7. Change the API without breaking clients

  • Safe changes: adding a new optional field, a new endpoint, or a new value to a response. Tell clients in your documentation to ignore fields they do not recognize.
  • Breaking changes: removing or renaming a field, changing its type or meaning, making an optional input required, or adding a value to an enum that clients switch on without a default case.
  • Release breaking changes as a new version (in the path, such as /v2/orders, or in a header), and keep the old one running for a published period.
  • Never reuse a field name for something different.

8. Protect your server from bad JSON

  • Limit the size of request bodies, and the nesting depth that the parser accepts, to avoid memory and stack problems.
  • Validate the shape and values of every incoming document, ideally against a JSON Schema, and reject unknown fields if strictness is appropriate.
  • Send Content-Type: application/json; charset=utf-8 and expect it on requests.
  • Build JSON with a serializer, never by joining strings, so quoting and escaping are always correct.
  • Do not return more than the caller is allowed to see. Filter fields on the server rather than trusting clients to ignore them.

9. Document with real examples and a schema

Publish a machine-readable description of your API, such as an OpenAPI document using JSON Schema for the bodies, together with complete example requests and responses for the normal case, empty results and common errors. Examples that were generated from the real API stay accurate, and you can use them as test fixtures.

Quick checklist

  1. One naming style, used consistently.
  2. Dates in ISO 8601 UTC; money as integers or decimal strings; large ids as strings.
  3. A documented rule for null versus missing; empty lists are [].
  4. Object at the top level; every list paginated.
  5. One standard error format with correct status codes.
  6. Additive changes only within a version.
  7. Input size, depth and shape validated on the server.
  8. Schema and real examples published.

Frequently asked questions

Should I use camelCase or snake_case in JSON?

Either is fine. JavaScript-heavy ecosystems usually use camelCase and Python- and Ruby-heavy ones often use snake_case. What matters is being consistent across the whole API, and matching what your main clients expect.

Is it OK to put a version in the JSON body?

It is better to version in the URL or a header, because the version then applies before the body is read. A schema version field inside documents is useful for stored data, where a record may be many years old.

Should dates include a time zone?

Yes. Send UTC with the Z suffix, or an explicit offset, and let clients convert to local time for display. A date-and-time string without a zone is ambiguous.

What is JSON:API, and is it the same thing?

JSON:API is a specific specification that defines a fixed document structure for resources, relationships and errors. It is one way to apply many of these ideas. This guide describes general practices that apply whether or not you adopt that specification.

Related guides

See all 12 guides

© 2026 TrueJson. All rights reserved.