UseToolSuite UseToolSuite

REST API JSON Best Practices

Best practices for designing JSON responses in REST APIs. Covers naming conventions, error handling, pagination, versioning, rate limiting, and performance optimization with real-world code examples.

Necmeddin Cunedioglu Necmeddin Cunedioglu 7 min read

Practice what you learn

JSON Formatter & Validator

Try it free →

REST API JSON Best Practices

Designing consistent, predictable JSON responses is one of the most impactful things you can do for your API’s usability. This guide covers the conventions used by top APIs like Stripe, GitHub, and Twilio — with code examples you can adopt immediately.

Naming Conventions

Choose one naming convention and use it consistently across all endpoints:

ConventionExampleUsed By
camelCasefirstName, createdAtJavaScript, Stripe
snake_casefirst_name, created_atPython, GitHub, Twitter
kebab-casefirst-nameRare in JSON (used in URLs)

Convert between conventions instantly with our String Case Converter.

Rule: Match the naming convention of your primary consumer. If your API is consumed mostly by JavaScript frontends, use camelCase. If by Python backends, use snake_case.

Common Naming Mistakes to Avoid

  1. Mixing conventions — Using firstName in one endpoint and first_name in another creates confusion and bugs.
  2. Abbreviations — Prefer description over desc, and configuration over config. Explicit names reduce documentation dependency.
  3. Boolean prefixes — Use is, has, or can prefixes for booleans: isActive, hasPermission, canEdit.
  4. Pluralization — Use plural nouns for arrays: items not item, addresses not address.

Response Envelope Pattern

Wrap your responses in a consistent envelope:

{
  "data": {
    "id": "usr_123",
    "name": "Alice Johnson",
    "email": "alice@example.com"
  },
  "meta": {
    "requestId": "req_abc123",
    "timestamp": "2026-03-22T14:30:00Z"
  }
}

This pattern separates the actual data from metadata and makes your API easier to extend without breaking changes.

When to Use Envelopes vs Flat Responses

ApproachProsConsBest For
Envelope (data wrapper)Extensible, consistent, room for metaSlightly verbosePublic APIs, multi-consumer APIs
Flat responseSimpler, less nestingHard to add metadata laterInternal microservices
JSON:API specStandardized, tooling availableComplex, steep learning curveLarge organizations

Recommendation: Use envelopes for public APIs. The overhead is minimal, but the extensibility is invaluable when you need to add pagination metadata, deprecation notices, or rate limit information later.

Error Response Format

Use a consistent error format across all endpoints:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request body contains invalid fields.",
    "details": [
      { "field": "email", "message": "Must be a valid email address" },
      { "field": "age", "message": "Must be a positive integer" }
    ]
  }
}

Rules:

  • Always return an error object (never a plain string).
  • Include a machine-readable code for programmatic handling.
  • Include a human-readable message for debugging.
  • Use details array for field-level validation errors.

HTTP Status Codes for JSON APIs

Pair your JSON error responses with the correct HTTP status codes:

StatusWhen to UseError Code Example
400Malformed request body, invalid JSONINVALID_REQUEST
401Missing or expired authenticationAUTHENTICATION_REQUIRED
403Authenticated but insufficient permissionsFORBIDDEN
404Resource not foundRESOURCE_NOT_FOUND
409Conflict (e.g., duplicate email)CONFLICT
422Valid JSON but fails business rulesVALIDATION_ERROR
429Rate limit exceededRATE_LIMIT_EXCEEDED
500Unexpected server errorINTERNAL_ERROR

Anti-pattern: Never return 200 OK with an error in the body. This confuses HTTP clients, caches, and monitoring tools.

Pagination

For list endpoints, use cursor-based pagination:

{
  "data": [
    { "id": "usr_001", "name": "Alice" },
    { "id": "usr_002", "name": "Bob" }
  ],
  "pagination": {
    "hasMore": true,
    "nextCursor": "usr_002",
    "totalCount": 150
  }
}

Cursor-based pagination is more reliable than offset-based (?page=2) because it handles insertions and deletions without skipping or duplicating records.

Pagination Comparison

MethodConsistencyPerformanceUse Case
Cursor-based✅ Stable during mutations✅ O(1) seekReal-time feeds, large datasets
Offset-based❌ Skips/duplicates on mutations❌ O(n) skipSimple admin panels, small datasets
Keyset-based✅ Stable✅ Uses indexSorted, immutable data

Implementation tip: Use btoa(JSON.stringify({id, createdAt})) to create opaque cursors that encode the sort key without exposing your database internals.

Versioning Your API

Choose a versioning strategy and apply it consistently:

# URL path versioning (most common)
GET /v1/users
GET /v2/users

# Header versioning
GET /users
Accept: application/vnd.myapi.v2+json

# Query parameter versioning
GET /users?version=2

Recommendation: URL path versioning (/v1/, /v2/) is the most explicit and easiest to understand. Header versioning is cleaner but harder to test in a browser.

Deprecation Strategy

When deprecating an API version, communicate clearly:

{
  "data": { "id": "usr_123" },
  "meta": {
    "deprecation": {
      "message": "API v1 will be removed on 2027-01-01. Migrate to v2.",
      "sunsetDate": "2027-01-01",
      "migrationGuide": "https://docs.example.com/migrate-v1-to-v2"
    }
  }
}

Add the Sunset HTTP header as well: Sunset: Sat, 01 Jan 2027 00:00:00 GMT.

Dates and Timestamps

Always use ISO 8601 format with timezone:

{
  "createdAt": "2026-03-22T14:30:00Z",
  "updatedAt": "2026-03-22T15:45:30+03:00"
}

Convert timestamps between Unix epoch and human-readable dates with our Unix Timestamp Converter.

Rules:

  • Use UTC (Z suffix) for server-generated timestamps.
  • Include timezone offset for user-facing times.
  • Never use ambiguous formats like 03/22/2026 (US) or 22/03/2026 (EU).
  • For date-only values (no time component), use YYYY-MM-DD format: "birthDate": "1990-05-15".

Null vs Absent Fields

Be explicit about null values:

{
  "name": "Alice",
  "avatar": null,
  "bio": null
}

Rule: Include fields with null values rather than omitting them. This tells the consumer “this field exists but has no value” versus “this field doesn’t exist,” which are semantically different.

Partial Updates (PATCH)

For partial updates, only send the fields being modified. The server should interpret absent fields as “no change” and explicit null as “clear this field”:

// PATCH /users/usr_123
{
  "bio": "Software developer",
  "avatar": null
}
// Result: bio is updated, avatar is cleared, name is unchanged

Rate Limiting

Always include rate limit information in response headers:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1679500800
Retry-After: 30

When the limit is exceeded, return a helpful JSON response:

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "You have exceeded the rate limit of 1000 requests per hour.",
    "retryAfter": 30,
    "limit": 1000,
    "resetAt": "2026-03-22T15:00:00Z"
  }
}

Performance Tips

  1. Minify production responses — Remove whitespace with JSON.stringify(data) (no indentation). Use our JSON Formatter during development, minified in production.
  2. Use pagination — Never return unbounded lists. Default to 20-50 items per page.
  3. Support field selection — Let consumers request only the fields they need: ?fields=id,name,email. This reduces payload size significantly for mobile clients.
  4. Compress responses — Enable gzip/brotli compression at the server level. JSON compresses extremely well (often 80-90% reduction).
  5. Generate TypeScript types — Create client-side type safety from your API responses with our JSON to TypeScript Converter.
  6. Use ETags for caching — Return ETag headers and support If-None-Match to enable 304 Not Modified responses for unchanged resources.

Response Size Optimization

TechniqueTypical SavingsComplexity
Gzip compression80-90%Low (server config)
Field selection30-70%Medium
Sparse fieldsets40-60%Medium
Remove null fields5-15%Low
Minified JSON10-20%None (default)

Security Considerations

  1. Never expose internal IDs — Use UUIDs or prefixed IDs (usr_123) instead of sequential integers that reveal record counts.
  2. Sanitize output — Escape HTML entities in JSON string values to prevent XSS when rendered in browsers.
  3. Limit response depth — Set a maximum nesting level to prevent circular reference attacks.
  4. Validate Content-Type — Reject requests without Content-Type: application/json to prevent CSRF attacks.
  5. Use HTTPS only — Never serve API responses over HTTP. Set Strict-Transport-Security headers.

FAQ

Should I use JSON:API or GraphQL instead of REST?

REST with well-designed JSON responses covers 90% of use cases. Consider GraphQL when clients need flexible queries across many related resources (e.g., a mobile app showing user profiles with posts, comments, and followers in one screen). Consider JSON:API when you need a formal specification for large teams. For most projects, simple REST with consistent conventions is the best choice.

How do I handle API versioning for breaking changes?

A breaking change is anything that removes a field, changes a field’s type, or alters the behavior of an endpoint. For breaking changes, increment the major version (/v1//v2/). For additive changes (new fields, new endpoints), keep the same version — these are backward-compatible.

Keep individual responses under 1 MB for web clients and under 256 KB for mobile clients. For larger datasets, use pagination or streaming (NDJSON). If a single resource consistently exceeds these limits, consider splitting it into sub-resources with separate endpoints.

How should I handle file uploads in a JSON API?

Don’t embed large binary data in JSON. Use multipart/form-data for file uploads, or implement a two-step process: (1) Get a pre-signed URL from your API, (2) Upload directly to cloud storage (S3, GCS). Return the file URL in the JSON response after upload completes.


This article is part of our JSON Developer Guide series.

Necmeddin Cunedioglu
Necmeddin Cunedioglu Author

Software developer and the creator of UseToolSuite. I write about the tools and techniques I use daily as a developer — practical guides based on real experience, not theory.