API design best practices including resource naming, validation, pagination, and versioning strategies
Software 10 min read

API Design Patterns That Prevent Problems Later

API design decisions made early become difficult to change once clients depend on them. Poorly designed APIs create maintenance burden, confuse developers, and cause production incidents. Good API design anticipates growth: clear resource naming, thorough validation, proper authentication, pagination for scale, versioning for evolution, idempotency for reliability, useful error responses, and backwards compatibility.

Resource naming and URL structure

Clear, consistent resource naming makes APIs intuitive. Poor naming creates confusion and forces clients to memorize arbitrary conventions.

Naming conventions

  • Use nouns for resources, not verbs: /users not /getUsers
  • Use plural nouns for collections: /orders not /order
  • Use hierarchical paths for relationships: /users/{id}/orders
  • Use lowercase and hyphens for readability: /order-items not /orderItems
  • Keep URLs short and predictable

Use HTTP methods correctly

  • GET retrieves resources (safe, idempotent, cacheable)
  • POST creates resources (not idempotent)
  • PUT replaces resources (idempotent)
  • PATCH updates resources partially (may be idempotent)
  • DELETE removes resources (idempotent)

Input validation

Validate all inputs at the API boundary. Invalid data should be rejected before processing.

What to validate

  • Required fields are present
  • Data types are correct (string, number, boolean)
  • Values are within acceptable ranges
  • Formats are valid (email, URL, date)
  • Enums match allowed values
  • String lengths are within limits

Validation error responses

Return clear error messages indicating what is wrong and how to fix it:

{
  "error": "validation_failed",
  "message": "Request validation failed",
  "details": [
    {
      "field": "email",
      "message": "Invalid email format"
    },
    {
      "field": "age",
      "message": "Must be between 0 and 120"
    }
  ]
}

Authentication and authorization

Security must be enforced at the API layer, not just in the client. For detailed security patterns, see Security-First API Design.

Authentication patterns

  • API keys: simple but less secure, suitable for server-to-server
  • OAuth 2.0: standard for delegated authorization
  • JWT tokens: stateless, contain claims, can be validated without database lookups

Authorization checks

  • Verify identity (authentication) before checking permissions (authorization)
  • Enforce object-level authorization (users can only access their own resources)
  • Use role-based or attribute-based access control
  • Never trust client-provided IDs without verification

Pagination

APIs that return large collections must paginate results. Without pagination, responses grow unbounded as data scales.

Pagination strategies

  • Offset-based: ?limit=20&offset=40 (simple but can miss items if data changes)
  • Cursor-based: ?limit=20&cursor=abc123 (consistent across changes, better for real-time data)
  • Page-based: ?page=3&per_page=20 (familiar but has offset issues)

Include pagination metadata

{
  "data": [...],
  "pagination": {
    "total": 1547,
    "limit": 20,
    "offset": 40,
    "next": "/users?limit=20&offset=60",
    "previous": "/users?limit=20&offset=20"
  }
}

Versioning

APIs evolve. Versioning allows changes without breaking existing clients.

Versioning strategies

  • URL versioning: /v1/users (simple, explicit)
  • Header versioning: Accept: application/vnd.api+json;version=1 (clean URLs)
  • Query parameter: /users?version=1 (less common)

Versioning guidelines

  • Version major breaking changes (removing fields, changing behaviour)
  • Make backwards-compatible changes within a version (adding optional fields)
  • Support previous versions long enough for clients to migrate
  • Communicate deprecation timelines clearly

Idempotency

Idempotent operations can be safely retried without duplicating effects. This is critical for reliable distributed systems.

Idempotent HTTP methods

  • GET, PUT, DELETE are naturally idempotent
  • POST is not idempotent (repeated requests create multiple resources)

Making POST idempotent

Use idempotency keys to prevent duplicate processing:

POST /orders
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
{
  "product_id": 123,
  "quantity": 2
}

If the same key is sent again, return the original result without reprocessing.

Error responses

Clear error messages help developers debug problems quickly.

Use HTTP status codes correctly

  • 200 OK - successful GET, PUT, PATCH
  • 201 Created - successful POST that created a resource
  • 204 No Content - successful DELETE or update with no response body
  • 400 Bad Request - invalid input
  • 401 Unauthorized - missing or invalid authentication
  • 403 Forbidden - authenticated but not authorized
  • 404 Not Found - resource does not exist
  • 429 Too Many Requests - rate limit exceeded
  • 500 Internal Server Error - server error

Error response format

{
  "error": "not_found",
  "message": "User not found",
  "request_id": "req_abc123",
  "timestamp": "2026-10-22T10:30:00Z"
}

Rate limiting

Rate limits prevent abuse and ensure fair resource allocation. Communicate limits clearly in responses.

Rate limit response headers

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 742
X-RateLimit-Reset: 1634910000

Rate limiting strategies

  • Per-user limits (authenticated requests)
  • Per-IP limits (unauthenticated requests)
  • Different limits for different endpoints
  • Burst allowances for short spikes

Observability

APIs must be observable for debugging and monitoring.

Request IDs

Include a unique request ID in responses for tracing:

X-Request-Id: req_abc123

Log this ID with all operations so requests can be traced through the system.

What to log

  • Request method, path, and parameters
  • Response status code and latency
  • Authentication and authorization failures
  • Validation errors
  • Rate limit hits

Backwards compatibility

Maintaining backwards compatibility avoids breaking existing clients.

Safe (backwards-compatible) changes

  • Adding new optional fields
  • Adding new endpoints
  • Adding new enum values
  • Relaxing validation (accepting more inputs)

Breaking changes

  • Removing fields
  • Renaming fields
  • Changing field types
  • Changing URL structure
  • Removing endpoints
  • Tightening validation (rejecting previously accepted inputs)

Breaking changes require a new API version.

Documentation

Good documentation reduces support burden and improves adoption.

What to document

  • All endpoints with request and response examples
  • Authentication methods
  • Error codes and meanings
  • Rate limits
  • Pagination behaviour
  • Idempotency requirements
  • Versioning policy

Use OpenAPI specification

OpenAPI (formerly Swagger) provides machine-readable API documentation that can generate client SDKs, test cases, and interactive documentation.

Design for clients, not convenience

APIs are contracts. Once clients depend on them, changes become expensive. Design with clear resource naming, thorough validation, proper authentication, pagination, versioning, idempotency, useful errors, and backwards compatibility. Good API design anticipates growth and prevents problems before they occur.


Published by the DSSS Engineering Team. For corrections or topic requests, use the contact page.