API Design Principles That Stand the Test of Time

May 9, 2016

APIs are contracts. Once published, changing them breaks consumers. The cost of API mistakes compounds over time as more clients depend on poor decisions. Getting API design right matters more than getting implementation right—you can refactor implementation, but API changes require coordinating with everyone who depends on you.

These principles have guided my API design across dozens of services. They’re not revolutionary; they’re distilled from hard-won experience and the accumulated wisdom of the developer community.

Consistency Above All

The single most important quality of an API is consistency. A consistent API is predictable; developers learn patterns once and apply them everywhere. An inconsistent API forces developers to check documentation for every endpoint, remember special cases, and make mistakes.

Naming Consistency

Choose naming conventions and apply them everywhere:

Behavior Consistency

Developers will assume that patterns they’ve seen apply universally. When they don’t, developers debug phantom issues caused by violated assumptions.

Use HTTP Correctly

REST APIs should leverage HTTP semantics. Clients and infrastructure understand HTTP; fighting it creates confusion.

HTTP Methods

HTTP Status Codes

Use appropriate status codes:

Don’t use 200 for everything with error information in the body. Clients and infrastructure depend on status codes for routing, caching, and error handling.

HTTP Headers

Leverage standard headers:

Custom headers should use X- prefix (though this convention is deprecated, it remains common) or a vendor-specific prefix.

Design for Evolvability

APIs must evolve as requirements change. Design for change from the start.

Versioning Strategy

Decide on versioning early:

URL versioning: /v1/users vs /v2/users. Clear and explicit. Clients see the version in every request.

Header versioning: Accept: application/vnd.api+json;version=1. Keeps URLs clean but is less visible.

No versioning: Make all changes backward-compatible. Simplest when achievable, but eventually you’ll need breaking changes.

My recommendation: URL versioning for its clarity and tooling compatibility. Accept that URLs will include version segments.

Backward Compatibility

Adding fields to responses is backward compatible; removing fields breaks clients. Adding optional request parameters is compatible; making parameters required breaks clients.

Changes that seem minor can break consumers:

Document what constitutes a breaking change in your versioning policy.

Deprecation

When deprecating endpoints or fields:

  1. Mark as deprecated in documentation
  2. Add deprecation response headers (Deprecation, Sunset)
  3. Log usage to understand who depends on deprecated features
  4. Provide migration timeline
  5. Communicate directly with high-volume consumers

Never remove without warning. The sunset period should be proportional to your API’s stability promises.

Error Handling

Good error responses help developers fix problems quickly. Bad error responses waste hours of debugging time.

Error Structure

Provide consistent, useful error responses:

{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "code": "invalid_format",
        "message": "Email must be a valid email address"
      },
      {
        "field": "age",
        "code": "out_of_range",
        "message": "Age must be between 0 and 150"
      }
    ],
    "request_id": "req_abc123"
  }
}

Include:

Error Messages

Error messages should be:

Rate Limiting

When rate limiting, help clients adapt:

Pagination

Any endpoint returning lists must support pagination. Even if your list is small today, it will grow.

Cursor-Based Pagination

Cursor pagination provides stable results even when data changes:

GET /users?limit=20&cursor=eyJpZCI6MTIzfQ

Response includes next cursor:

{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6MTQzfQ",
    "has_more": true
  }
}

Cursors are opaque to clients—they shouldn’t parse or construct them. This lets you change cursor implementation without breaking clients.

Offset-Based Pagination

Simpler but has edge cases:

GET /users?limit=20&offset=40

Problems: if items are added/removed between requests, clients may miss items or see duplicates. For frequently-changing data, cursor pagination is safer.

Include Total Counts Carefully

Clients often want total counts for UI. But counting can be expensive at scale. Consider:

Security

API security goes beyond authentication.

Authentication

Support industry-standard authentication:

Authorization

Authentication proves identity; authorization controls access. Implement both:

Input Validation

Validate all input:

Reject invalid input early with clear error messages. Never trust client input.

Output Filtering

Don’t leak sensitive data:

Documentation

Even perfect APIs fail without good documentation.

What to Document

Documentation Formats

OpenAPI (Swagger) provides machine-readable documentation that can generate client libraries and interactive documentation. Maintain it alongside implementation.

Supplement OpenAPI with guides: getting started, common use cases, best practices. Reference documentation explains what’s possible; guides explain what to do.

Keep Documentation Current

Stale documentation is worse than no documentation—it creates false confidence. Integrate documentation updates into your development process. Test that examples work. Review documentation in code review.

Practical Advice

Start Minimal

Launch with the smallest API that serves your needs. Every endpoint is a commitment; every field is a commitment. It’s easier to add than to remove.

Design for Clients

Think about how clients will use your API, not how your implementation works. The goal is client productivity, not exposing your database schema.

Get Feedback Early

Before finalizing your API, have developers use it. Build a client yourself. Friction in early usage reveals design problems cheaper to fix before launch.

Monitor Usage

Log API calls to understand how clients actually use your API. This informs evolution decisions: what to deprecate, what to add, what to optimize.

Key Takeaways