API Design
02 / 05

Auth, Versioning & Pagination

API Design: Auth, Versioning & Pagination

Authentication Patterns

# API Key — simple, good for server-to-server
Authorization: Api-Key sk_live_abc123
X-API-Key: sk_live_abc123

# Bearer Token (JWT or opaque)
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...

# Basic Auth (avoid — only over HTTPS, not for production APIs)
Authorization: Basic base64(user:password)

# OAuth 2.0 flows:
  Authorization Code (+ PKCE): user-facing apps — redirect to auth server
  Client Credentials:          server-to-server — no user involved
  Device Flow:                 CLIs, smart TVs — poll for approval

JWT Best Practices

  • Use RS256 (asymmetric) over HS256 — public key can be shared for verification

  • Short expiry: access tokens 15min, refresh tokens 7-30 days

  • Never put secrets (passwords, card numbers) in JWT payload — it's base64, not encrypted

  • Validate: signature, expiry (exp), issuer (iss), audience (aud)

  • Token rotation: issue new refresh token when access token is refreshed (detect token theft)

  • Revocation: JWTs are stateless — use a blocklist for critical revocations or use short expiry

API Versioning Strategies

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

# Header versioning (cleaner URLs, harder to test in browser)
Accept: application/vnd.myapi.v2+json
API-Version: 2

# Query parameter (least recommended)
/users?version=2

# Subdomain
v2.api.example.com/users

Best practices:
- Keep v1 working for at least 12-18 months after v2 launch
- Deprecation headers: Sunset: Sat, 01 Jan 2027 00:00:00 GMT
                       Deprecation: true
- Version should change when breaking: renaming fields, changing types, removing endpoints
- Do NOT version for additive changes (new optional fields, new endpoints)

Pagination Patterns

# Offset/limit (simple, but poor performance on large datasets)
GET /users?offset=100&limit=25

Response:
{
  "data": [...],
  "meta": { "total": 1247, "offset": 100, "limit": 25 }
}

# Cursor-based (recommended for large datasets, real-time data)
GET /posts?cursor=eyJpZCI6MTAwfQ&limit=25

Response:
{
  "data": [...],
  "meta": {
    "nextCursor": "eyJpZCI6MTI1fQ",  // base64 of last item's sort key
    "hasMore": true
  }
}

# Page-based
GET /users?page=5&perPage=25

Response:
{
  "data": [...],
  "pagination": { "page": 5, "perPage": 25, "totalPages": 50, "total": 1247 }
}

# Link header (RFC 5988)
Link: <https://api.example.com/users?page=6>; rel="next",
      <https://api.example.com/users?page=50>; rel="last"

Rate Limiting Headers

X-RateLimit-Limit: 1000        — requests allowed per window
X-RateLimit-Remaining: 842     — requests left in current window
X-RateLimit-Reset: 1716768000  — Unix timestamp when window resets

# When rate limited (429):
Retry-After: 60                — seconds until retry allowed

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free