โ† Back to Blog

API Response JSON Formatting Best Practices

2026-04-16 ยท 5 min read

โ† Back to Blog

API Response JSON Formatting Best Practices

ยท 5 min read

Unified Response Structure: Consistency for Success and Failure

One of the core principles of good API design is response structure consistency. Whether a request succeeds or fails, the outer structure of the response should maintain the same form, allowing clients to handle all responses in a unified way. The most common unified structure wraps everything in an envelope object at the top level, containing status information and data:

This wrapping structure is especially valuable for successful list responses: it provides both the actual data and metadata like pagination information and request identifiers, so clients can get complete context without parsing response headers. Once a team establishes this structure, all endpoints should strictly adhere to it โ€” you shouldn't return a bare array from some endpoints and an object from others.

// ๆˆๅŠŸๅ“ๅบ” / Success response
{
  "success": true,
  "data": {
    "user": {
      "id": 12345,
      "name": "Alice",
      "email": "[email protected]"
    }
  },
  "meta": {
    "requestId": "req_abc123",
    "timestamp": "2025-01-01T00:00:00Z"
  }
}

// ้”™่ฏฏๅ“ๅบ” / Error response
{
  "success": false,
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "The requested user does not exist",
    "details": {"userId": 99999}
  },
  "meta": {
    "requestId": "req_def456",
    "timestamp": "2025-01-01T00:00:01Z"
  }
}

Field Naming Conventions: camelCase vs snake_case

The choice of API field naming style has a direct impact on the client development experience. camelCase (e.g., firstName, createdAt) is the natural choice in the JavaScript ecosystem since JSON itself originated from JavaScript and aligns with JS object property naming conventions. Most modern public APIs (Twitter/X, GitHub, Stripe) use camelCase.

snake_case (e.g., first_name, created_at) is more common in Python, Ruby, and database field naming, and feels more natural when the server side uses these languages. If the API is primarily targeted at Python clients, snake_case can eliminate one layer of conversion work. The most important principle: maintain consistency throughout the entire API โ€” don't mix both styles.

Pagination Response Format

Pagination is one of the most important design decisions for list-type APIs. There are two mainstream approaches: offset-based pagination and cursor-based pagination. Offset-based pagination is more intuitive and suitable for relatively static data sets; cursor-based pagination is suited for large, highly real-time data sets (like social media feeds). Regardless of which approach you use, the response should include complete pagination metadata:

// ๅŸบไบŽ้กต็ ็š„ๅˆ†้กต / Offset-based pagination
{
  "success": true,
  "data": [...],
  "pagination": {
    "page": 2,
    "pageSize": 20,
    "total": 157,
    "totalPages": 8,
    "hasNext": true,
    "hasPrev": true
  }
}

// ๅŸบไบŽๆธธๆ ‡็š„ๅˆ†้กต / Cursor-based pagination
{
  "success": true,
  "data": [...],
  "pagination": {
    "cursor": "eyJpZCI6MTIzfQ==",
    "nextCursor": "eyJpZCI6MTQzfQ==",
    "hasMore": true,
    "limit": 20
  }
}

Standardizing Error Responses

Consistent and informative error responses are key to API usability. Error responses should include: machine-readable error codes (code) for programmatic handling, human-readable error messages (message) for debugging, optional field-level validation errors (fieldErrors), and details. Error codes should use meaningful strings rather than pure numbers, so their general meaning is understandable even without documentation:

HTTP status codes and JSON error bodies should work together: 400 for client errors (missing params, format errors), 401 for unauthenticated, 403 for unauthorized, 404 for resource not found, 422 for business validation failures, 500 for server internal errors. Don't return 200 for all responses and distinguish errors in the JSON body โ€” that makes it hard for clients to use standard HTTP client library error handling mechanisms.

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "fieldErrors": [
      {
        "field": "email",
        "code": "INVALID_FORMAT",
        "message": "Must be a valid email address"
      },
      {
        "field": "age",
        "code": "OUT_OF_RANGE",
        "message": "Must be between 0 and 150"
      }
    ]
  }
}

Formatting Date and Time Fields

Date-time fields are the most prone to format inconsistency in APIs. Strong recommendations: standardize all date-time fields to ISO 8601 format (2025-01-01T00:00:00Z), always include timezone information (UTC recommended, ending with Z), and Unix timestamps (seconds or milliseconds) are suitable for scenarios requiring precise time comparison and sorting.

What to avoid: don't use localized date strings (like "Jan 1, 2025") which are difficult for clients to parse; don't mix different date formats within the same API; if the API has international users, avoid pure numeric month-day formats (01/02/2025 means different things in different regions). For date-only fields without time (like birthdays), use the YYYY-MM-DD format.

Handling Null Values and Missing Fields

In API responses, null values and completely omitted fields carry different semantic meanings. General principle: null indicates the field exists but has no value (e.g., user hasn't set an avatar, "avatar": null); omitting a field entirely indicates the field doesn't apply to this object (e.g., a certain user type doesn't have that field concept). Within the same API, the team should reach clear consensus on this and document it.

A common mistake is returning inconsistent representations for different "empty" cases: sometimes returning null, sometimes an empty string "", sometimes omitting the field. This forces clients to write complex defensive code. Best practice: always return all fields declared in the Schema (even if null), so clients can rely on a stable structure; only omit fields for truly "non-existent" concepts, and document this clearly.

Large Numbers and Precision Issues

When an API returns integers exceeding JavaScript's safe integer range (Number.MAX_SAFE_INTEGER = 9007199254740991), precision loss occurs. The most common scenario is using 64-bit integer IDs (Snowflake IDs, Twitter IDs, etc.) โ€” these IDs lose precision when processed by JavaScript's JSON.parse(), causing IDs to be incorrectly identified.

Solutions: return large integer IDs in both string and number form (Twitter's historical approach), or just return string form only. While this makes JSON slightly redundant, it avoids requiring all downstream clients to implement special handling. For Go backends, the json:",string" tag can automatically serialize int64 fields as strings; for Java backends, Jackson can be configured to serialize Long as strings.

API Versioning and Response Format Evolution

As the business evolves, API response formats inevitably need to change. Common versioning strategies: URL path versioning (/v1/users, /v2/users) is the clearest and most intuitive, easy to document; header versioning (Accept: application/vnd.api.v2+json) is more REST-aligned but more complex to implement; query parameter versioning (?version=2) is simple but not as elegant.

Backward-compatible response format evolution without introducing new versions: safely add new fields (clients should ignore unknown fields); do not remove or rename existing fields; do not change the data type of existing fields. For breaking changes, a new API version must be introduced while maintaining the old version for a period, giving clients adequate migration time. Deprecation information can be provided in response headers or the JSON response body to prompt clients to migrate promptly.

Try the online tool now โ€” no installation, completely free.

Open Tool โ†’

Try the free tool now

Use Free Tool โ†’