Dev Tools

JSON Best Practices for API Development: Responses, Errors, and Performance

Introduction: JSON Is the Lingua Franca of Web APIs — But Poorly Structured JSON Causes Bugs

JSON is simple enough that any developer can start using it immediately. That simplicity is also its trap. Because there is no enforced structure beyond basic syntax, every team ends up with subtly different conventions: some use camelCase keys, others use snake_case; some wrap responses in a data envelope, others return bare objects; some use ISO 8601 dates, others use Unix timestamps; some return null for missing fields, others omit the field entirely. These inconsistencies multiply into client-side bugs, integration failures, and hours of debugging.

This guide covers the conventions that make JSON APIs predictable, maintainable, and safe. The principles apply regardless of framework or language. Use our JSON formatter to inspect, validate, and debug your API responses as you implement these patterns.

Consistent Response Envelopes

A response envelope is a standard wrapper object that every API response conforms to. Without one, a successful response might be a bare object, an array, or a string depending on the endpoint — forcing clients to handle each case differently.

A simple and widely adopted pattern:

{
"data": { ... },
"error": null,
"meta": {
"requestId": "a1b2c3",
"timestamp": "2026-03-17T12:00:00Z"
}
}

On success, data contains the response payload and error is null. On failure, data is null and error contains error details. The meta object carries request-level metadata — request IDs for tracing, timestamps, rate-limit headers, and pagination information for list responses.

This pattern means clients can always check response.error !== null to detect failures, rather than relying solely on HTTP status codes which may be swallowed by proxies or obscured by framework error handling. It also creates a clear place to add cross-cutting concerns (versioning, tracing, deprecation warnings) without breaking existing response shapes.

Naming Conventions: Pick One and Never Deviate

The two most common JSON key naming conventions for APIs are camelCase (e.g., firstName, createdAt) and snake_case (e.g., first_name, created_at). camelCase is the natural choice for JavaScript clients since it matches JavaScript variable naming. snake_case is common in Python and Ruby APIs. Neither is universally superior — what matters is absolute consistency within an API.

Rules that prevent the most common naming bugs:

  • Pick one convention and enforce it everywhere. A linter (ESLint with camelcase rule, or a custom JSON Schema) should enforce this automatically.
  • Use consistent pluralization. Arrays are always plural (users, items), single objects are always singular (user, item). Never mix user_list with items.
  • Avoid abbreviations. numRecords vs recordCount vs count — three different names for the same concept. Use the full word and pick one.
  • Boolean names should read naturally as a yes/no question. isActive, hasPermission, canEdit rather than active, permission, editable.

Error Response Format: RFC 7807 Problem Details

RFC 7807 "Problem Details for HTTP APIs" defines a standard JSON (and XML) format for error responses. Using it means clients can predict exactly where to find error information regardless of which endpoint produced the error.

A complete RFC 7807-compliant error response:

{
"type": "https://api.example.com/errors/validation",
"title": "Validation Failed",
"status": 422,
"detail": "The request body contains invalid fields.",
"instance": "/users/create",
"errors": [
{"field": "email", "message": "Must be a valid email address"},
{"field": "age", "message": "Must be a positive integer"}
]
}

Key fields: type is a URI that uniquely identifies the error class (links to documentation); title is a short human-readable summary; status mirrors the HTTP status code; detail provides actionable context; instance identifies the specific request that failed. The errors array extension (not in the RFC but widely adopted) provides field-level validation errors for form submissions.

Never expose internal error details (stack traces, SQL errors, internal IDs) in error responses. Log them server-side and return only a stable type URI and a human-readable detail to the client.

Dates and Times: Always ISO 8601 with Timezone

Date and time handling is the source of a disproportionate number of API bugs. The rules are simple and non-negotiable for any API used across timezones:

  • Always use ISO 8601 format: "2026-03-17T12:00:00Z" for UTC, or "2026-03-17T14:00:00+02:00" for an explicit offset.
  • Always include timezone information. A timestamp without a timezone ("2026-03-17T12:00:00") is ambiguous — clients in different timezones interpret it differently. Always use Z (UTC) or a numeric offset.
  • Prefer UTC for storage and transmission. Convert to local time in the client. This makes sorting, comparison, and arithmetic trivially correct.
  • Avoid Unix timestamps in responses. Unix timestamps (seconds since epoch) are compact but not human-readable, not self-documenting, and require implicit knowledge of whether the value is in seconds or milliseconds (a common bug). ISO 8601 strings are unambiguous and parseable by every modern language without custom code.
  • Date-only values: When time is genuinely irrelevant (a birth date, an invoice date), use "2026-03-17" (date only, no time or timezone) to signal that the value is a calendar date, not a point in time.

Null vs Absent: When to Include Fields

A common design decision is whether to include a key with a null value or omit the key entirely when the value is not available. Both approaches are technically valid JSON, but they have different semantic implications:

  • Include with null when the field is expected but has no value. Example: "middleName": null in a user profile — the field is part of the schema, the user simply has no middle name. Clients can rely on the field being present and handle null explicitly.
  • Omit the field when the field is conditionally present. Example: a shippingAddress on an order that uses in-store pickup. Omitting it distinguishes "field not applicable" from "field is null."

The critical rule: be consistent. If a field appears in some responses but not others with no discernible pattern, clients must defensively check hasOwnProperty for every optional field, or simply crash when the field is missing. Document which fields are always present and which are conditional.

Pagination: Cursor-Based vs Offset-Based

Any endpoint that may return more items than fit in a single response must be paginated. The two dominant strategies have different trade-offs:

Offset-Based Pagination

{"data": [...], "meta": {"total": 1500, "offset": 0, "limit": 25}}

Simple to implement and to navigate (jump to any page). The downside: unstable under insertions and deletions. If a user fetches page 2 and a new item was inserted into page 1 since the first request, page 2 now contains a duplicate of the last item from page 1. Total count queries can be expensive on large datasets.

Cursor-Based Pagination

{"data": [...], "meta": {"nextCursor": "eyJpZCI6MTUwfQ==", "hasMore": true}}

The cursor encodes the position of the last seen item (typically its ID or creation timestamp). The next request fetches items after that cursor. Cursor-based pagination is stable — insertions and deletions do not cause duplicates or skips. It does not support random access (you cannot jump to page 7 without iterating through pages 1–6). Cursor-based pagination is preferred for real-time feeds, infinite scrolling, and large datasets.

API Versioning

APIs change. Clients cannot always update simultaneously. Versioning is how you make breaking changes while keeping existing clients working.

URL Path Versioning

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

The version is explicit in the URL. Easy to test in a browser, easy to route at the load balancer, unambiguous in logs. The downside: technically not RESTful (the URL identifies a resource, not a version of a resource). This is the most pragmatic approach and the most widely adopted in practice.

Header Versioning

Accept: application/vnd.example.api+json; version=1

Cleaner from a REST purist perspective — the URL identifies the resource, the header identifies the representation version. Harder to test manually, less visible in logs, requires middleware to parse. Suitable for mature API platforms with sophisticated client toolchains.

Regardless of strategy: distinguish breaking changes (field removed, type changed, endpoint removed) from non-breaking changes (new optional field, new endpoint). Only breaking changes require a version bump. Provide a minimum 12-month deprecation window for major version migrations.

Security: Defensive JSON Practices

JSON itself is not a vector for injection attacks — it is just text. But careless JSON handling enables serious vulnerabilities:

  • Never expose internal identifiers unnecessarily. Database auto-increment IDs reveal record counts and enable enumeration attacks. Where possible, use UUIDs or other opaque identifiers in API responses.
  • Sanitize user-generated content before including it in JSON responses. If a user submitted a malicious string and it is included in an API response that a client renders as HTML, the client may be vulnerable to XSS. JSON encoding does not substitute for HTML sanitization.
  • Set Content-Type: application/json explicitly. Never let the client sniff the response type. Some browsers execute JS embedded in JSON-like responses if the content type is ambiguous.
  • Set X-Content-Type-Options: nosniff to prevent content type sniffing in browsers.
  • Validate incoming JSON against a schema. Reject requests with unexpected fields when additionalProperties: false is set in your schema. This prevents mass assignment vulnerabilities where a client sets fields they should not have access to (e.g., including "role": "admin" in a user update request).

Performance: Size, Compression, and Partial Responses

For high-traffic APIs, JSON payload size directly impacts bandwidth costs and response latency.

Minification and Compression

Minify JSON responses in production (remove all unnecessary whitespace). Enable gzip or brotli compression at the server or reverse proxy level. Brotli achieves 15–25% better compression than gzip for text. With compression enabled, the difference between pretty-printed and minified JSON largely disappears — but minification still reduces in-memory parsing cost and benefits clients that do not send Accept-Encoding: gzip.

Partial Responses

For resources with many fields, allow clients to request only the fields they need: GET /users/1?fields=id,email,name. The response contains only the requested fields. This is especially valuable for mobile clients on metered connections. Implement with a fields query parameter and a whitelist of allowed field names.

ETags and Conditional Requests

For read-heavy resources that change infrequently, implement ETags. The server returns ETag: "a1b2c3" with each response. Clients send If-None-Match: "a1b2c3" on subsequent requests. If the resource has not changed, the server returns 304 Not Modified with an empty body, saving bandwidth entirely.

Testing JSON APIs: Schema Validation and Contract Testing

Manual testing of JSON API responses does not scale. Three automated approaches cover different layers:

JSON Schema Validation in CI

Define JSON Schemas for every request and response type. In your CI pipeline, run integration tests that validate actual API responses against their schemas using ajv (Node.js) or equivalent. A CI failure when a field type changes or a required field is removed catches breaking changes before they reach production.

Postman Collections

Postman's test scripts support JavaScript assertions against response bodies. Maintain a collection of critical API calls with assertions that verify field presence, types, and value ranges. Export the collection to a postman_collection.json file committed to the repository and run it in CI via Newman runner.

Contract Testing

Pact is a consumer-driven contract testing framework. The API client defines the contract (what fields it expects in responses), and the server verifies it can fulfill that contract. Contract tests detect breaking changes from the consumer's perspective — more practical than trying to anticipate all clients when making server-side changes.

Using Our JSON Formatter During API Development

Our JSON formatter and validator integrates naturally into API development workflows:

  • Debugging API responses: Paste a raw API response to instantly pretty-print and validate it. Nested structures become readable in seconds instead of minutes of manual parsing.
  • Verifying request bodies: Before sending a complex request, format it to verify the structure is correct. Catching a missing comma before the API call saves a round trip and an often-unhelpful error message.
  • Comparing before and after: Minify both versions of a response to compare sizes and verify that a minification step did not corrupt the data structure.

All operations run in your browser. No JSON is sent to any server — important when working with production API credentials or user-identifying data in response payloads.

← Back to Blog