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 approvalJWT 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 allowedKeep your own version of these notes — editable, searchable, and organised by your stack.
Start free