Principles
- Explicit contracts (OpenAPI/Protobuf). Minimize breaking changes.
- Errors: consistent codes and schemas; correlation IDs.
- Pagination: cursor > offset; HATEOAS optional.
- Idempotency: keys for sensitive operations.
Versioning
- URI:
/v1
→/v2
(simple but coarse). - Header:
Accept: application/vnd.company.v2+json
(finer control). - GraphQL: additive evolution; deprecate before removal.
Error model
{
"error": {
"code": "INVALID_INPUT",
"message": "email is required",
"correlationId": "c-123"
}
}
Pagination (cursor)
# language-http
GET /users?limit=50&cursor=eyJpZCI6IjEyMyIsImNyZWF0ZWRBdCI6IjIwMjUtMDktMTUiLjk5OSJ9
-- language-sql
SELECT * FROM users
WHERE (created_at, id) < ($createdAt, $id)
ORDER BY created_at DESC, id DESC
LIMIT 50;
Idempotency example
# language-http
POST /orders
Idempotency-Key: 2b0f...
Contract first
# language-yaml
openapi: 3.1.0
info: { title: Users API, version: 1.0.0 }
paths:
/users/{id}:
get:
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
responses:
'200': { description: OK }
Consistency and naming
- Nouns for resources, verbs for actions; kebab‑case paths, lower_snake_case fields or follow platform norms.
- Avoid mixing plurals/singular; prefer
/users/{id}/posts
over custom verbs.
Security
- OAuth2 scopes, mTLS where needed, rate limits per client.
Observability
- Correlation IDs (
traceparent
), structured logs, SLOs per endpoint.
Analogy
An API is your product’s contract. Clarity and stability build trust; surprises and inconsistency break it.
FAQ
- Do I need HATEOAS? Not for most APIs; links can help discoverability but keep it simple.
- Offset pagination OK? For small lists yes; use cursors for large/real‑time lists.