Api Design Principles

Production engineering guide for api design principles covering patterns, implementation strategies, and operational best practices.

This page describes foundational principles for designing APIs that are reliable, maintainable, and predictable. These principles apply when creating new HTTP-based services or extending existing ones, particularly when working with REST-style interfaces. They guide decisions about resource structure, method usage, response format, error handling, and data representation.

Design Resources and Methods

Use Consistent Resource Naming

Resource names should be nouns in plural form and consistently use lowercase with hyphens for compound terms. For example: /users, /payment-methods, /notification-settings.

Define Explicit HTTP Methods

Use standard HTTP methods for their intended purposes:

Example request using curl:

curl -X PUT \
  -H "Content-Type: application/json" \
  -d '{"name": "Updated Name"}' \
  http://api.example.com/users/123

Structure Collections with Pagination

Collections should support pagination via query parameters page and size. Default page size is 20, maximum allowed is 100.

Example request:

curl "http://api.example.com/users?page=2&size=50"

Response includes meta object with pagination information:

{
  "data": [...],
  "meta": {
    "page": 2,
    "size": 50,
    "total": 1234,
    "pages": 25
  }
}

Define Response Formats

Return Standardized Success Responses

All successful responses use HTTP 200 OK with a consistent JSON structure. For collections, include data array and metadata.

Example:

{
  "data": {
    "id": "123",
    "name": "John Doe",
    "email": "[email protected]"
  }
}

Return Standardized Error Responses

Errors must return HTTP status codes with structured JSON bodies containing error, message, and optional details:

{
  "error": "ValidationFailed",
  "message": "The request body is invalid",
  "details": [
    {
      "field": "email",
      "message": "Email is required"
    }
  ]
}

Handle HTTP Status Codes Correctly

Use appropriate status codes:

Design for Versioning

Include Version in API Path

Version information must be part of the URL path, not headers or query parameters. Example: /v1/users, /v2/payment-methods.

Maintain Backward Compatibility

Changes to existing endpoints must not break clients relying on previous behavior. New features should be additive.

Handle Data Types and Serialization

Represent Dates as ISO 8601 Strings

All date fields use RFC 3339 format: 2023-12-01T15:30:00Z

Example field:

{
  "created_at": "2023-12-01T15:30:00Z"
}

Use Consistent Integer Sizes

All numeric fields that are not floating-point use 64-bit integers. For large numbers, return as strings to prevent overflow in clients.

Example:

{
  "account_balance": "1234567890123456789"
}

Support JSON Schema Validation

All responses must be valid against a defined schema. Clients should validate responses against the documented structure.

Handle Authentication and Authorization

Support Bearer Token Authentication

Authentication uses standard HTTP Authorization header with Bearer scheme:

curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  http://api.example.com/users

Define Clear Permission Scopes

Each endpoint requires specific authorization scopes. These are documented in the API specification.

Design for Idempotency

Make PUT and PATCH Idempotent

Multiple identical requests must produce the same result as a single request. This enables safe retries.

Use Client-Side Generated IDs for POST

When creating resources, clients can provide their own identifiers. The system should store these values and return them in responses.

Example:

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"id": "client-generated-123", "name": "New User"}' \
  http://api.example.com/users

Response:

{
  "data": {
    "id": "client-generated-123",
    "name": "New User",
    "created_at": "2023-12-01T15:30:00Z"
  }
}

Handle Concurrent Updates

Use ETags for Conflict Detection

Every resource must support ETag-based optimistic concurrency control. When updating, clients must send the current ETag in If-Match header:

curl -X PUT \
  -H "If-Match: \"abc123def456\"" \
  -H "Content-Type: application/json" \
  -d '{"name": "Updated Name"}' \
  http://api.example.com/users/123

Return 412 Precondition Failed for Conflicts

When ETag doesn't match, return HTTP 412 with error details:

{
  "error": "ResourceModified",
  "message": "The resource was modified by another process"
}

Support Filtering and Sorting

Allow Query Parameter Filtering

Collections support filtering via query parameters that match field names:

curl "http://api.example.com/users?status=active&role=admin"

Support Sorting via Query Parameters

Sorting uses sort parameter with - prefix for descending order:

curl "http://api.example.com/users?sort=-created_at"

Handle Batch Operations

Batch operations should be available when multiple related resources need to be modified. Use POST with array of operations:

{
  "operations": [
    {
      "method": "PUT",
      "path": "/users/123",
      "body": {"name": "Updated User"}
    },
    {
      "method": "DELETE",
      "path": "/users/456"
    }
  ]
}

Design for Rate Limiting

Return Rate Limit Headers

API responses must include headers indicating current limits:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1672531200

Design for Caching

Include Cache Control Headers

Responses should include Cache-Control headers when appropriate:

Cache-Control: public, max-age=3600

Handle Errors Gracefully

Return Meaningful Error Messages

All error responses must provide clear, actionable messages that help clients understand and resolve the problem.

Provide Error Codes for Programmatic Handling

Use consistent error codes across all endpoints. These codes should be documented in API specification.

Support Graceful Degradation

When parts of the system are unavailable, return partial results or appropriate fallback responses rather than failing completely.

Design for Extensibility

Allow Additional Fields in Requests

Accept unknown fields in request bodies without validation errors. This allows future extensions without breaking existing clients.

Support Optional Fields in Responses

All new fields should be optional in existing API versions to allow gradual adoption.

Common Pitfalls and Silent Failures

Missing Content-Type Headers

Without Content-Type: application/json, requests may be rejected or parsed inconsistently.

Inconsistent Error Response Format

Mixing error response structures leads to client-side parsing failures.

Ignoring HTTP Status Codes

Using 200 OK for errors or failing to return appropriate codes makes clients unable to distinguish success from failure.

Not Supporting Idempotency

Making PUT requests non-idempotent prevents safe retries and leads to data inconsistency.

Poor Pagination Design

Returning inconsistent page sizes or missing metadata breaks client-side pagination logic.

Overlooking Authentication Requirements

Not requiring authentication for protected endpoints creates security vulnerabilities.

Misusing HTTP Methods

Using POST for updates instead of PUT or PATCH results in incorrect semantics and prevents idempotency.

Ignoring ETag Support

Failing to implement ETag-based concurrency control leads to data conflicts during concurrent updates.

Validation Considerations

Validate Input Parameters

All request parameters must be validated, including presence, type, and range checks.

Return Validation Errors for Invalid Data

Invalid input should return HTTP 400 with detailed error information in the response body.

Support Partial Updates

PATCH operations should support partial updates to prevent overwriting valid fields.

Performance Considerations

Minimize Response Size

Only include necessary fields in responses. Avoid returning full objects when partial data is sufficient.

Support Field Selection

Allow clients to request only specific fields using a fields query parameter:

curl "http://api.example.com/users?fields=id,name,email"

Implement Efficient Database Queries

Ensure that API endpoints translate to efficient database operations without unnecessary joins or redundant queries.

This page was rewritten on 15 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.