Good APIs are invisible. You use them without thinking. Bad APIs make you read docs for every call. Here's how to build the good kind.

Be Predictable

If GET /users returns a list of users, then GET /posts should return a list of posts. Same pattern, same structure.

// Consistent response shape
{
  "data": [...],
  "meta": {
    "total": 100,
    "page": 1,
    "per_page": 20
  }
}

Once developers learn your patterns, they can guess endpoints they've never seen.

Use Standard HTTP

Methods mean things:

  • GET — Read (safe, cacheable)
  • POST — Create
  • PUT — Replace entirely
  • PATCH — Update partially
  • DELETE — Remove

Status codes mean things:

  • 200 — Success
  • 201 — Created
  • 400 — Client error (bad request)
  • 401 — Not authenticated
  • 403 — Not authorized
  • 404 — Not found
  • 500 — Server error

Don't return 200 OK with {"error": "Something failed"}. Use status codes correctly.

Error Responses

Errors should help developers fix the problem:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format"
      },
      {
        "field": "age",
        "message": "Must be a positive integer"
      }
    ]
  }
}

Include:

  • Machine-readable error code
  • Human-readable message
  • Specific field errors for validation
  • Request ID for debugging

Naming Conventions

Use nouns, not verbs:

GET /users          ✓
GET /getUsers       ✗
POST /users         ✓
POST /createUser    ✗

Plural for collections:

GET /users          ✓
GET /user           ✗
GET /users/123      ✓

Lowercase, hyphen-separated:

/user-profiles      ✓
/userProfiles       ✗
/user_profiles      ✗

Filtering and Pagination

Filter with query params:

GET /users?status=active&role=admin
GET /posts?created_after=2024-01-01

Paginate everything that could grow:

GET /users?page=2&per_page=50

Return pagination metadata:

{
  "data": [...],
  "meta": {
    "total": 500,
    "page": 2,
    "per_page": 50,
    "total_pages": 10
  }
}

Versioning

Version from day one. APIs change; clients can't always update immediately.

URL versioning (my preference):

/v1/users
/v2/users

Header versioning:

Accept: application/vnd.myapi.v1+json

URL versioning is more visible and easier to test.

Authentication

Use standard patterns:

Bearer tokens:

Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

API keys (for server-to-server):

X-API-Key: sk_live_abc123

Don't invent custom auth schemes. Security is hard; use proven patterns.

Rate Limiting

Protect your API and communicate limits clearly:

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640995200
Retry-After: 60

Document limits. Return headers showing current usage. Give clear retry guidance.

Documentation

Good docs include:

  • Authentication guide
  • Every endpoint with request/response examples
  • Error code reference
  • Rate limit information
  • Changelog

Great docs include:

  • Quick start guide
  • Code examples in multiple languages
  • Sandbox environment
  • Postman/OpenAPI collections

My Checklist

Before shipping an endpoint:

  • Uses correct HTTP method
  • Returns appropriate status codes
  • Error responses are helpful
  • Follows existing naming conventions
  • Has pagination if returning lists
  • Is authenticated/authorized properly
  • Is rate limited
  • Is documented

The Test

The best test: can a developer integrate without asking you questions?

If they can read your docs and build something in an hour, you've designed a good API. If they need a meeting to understand it, something's wrong.

React to this post: