Versioning, error payloads, pagination, filtering, and idempotency keys for consistent and maintainable APIs.
Versioning, error payloads, pagination, filtering, and idempotency keys for consistent and maintainable APIs.
Lesson outline
APIs evolve: you add fields, change behavior, or remove endpoints. Versioning lets clients keep working while you ship changes. URL versioning (/v1/users, /v2/users) is common and easy to read. Header versioning (Accept: application/vnd.api+v2+json) keeps URLs clean but is less visible. Pick one and stick with it.
When you release v2, keep v1 supported for a deprecation period and document the sunset date. Do not break v1; add new behavior in v2 or new endpoints.
Return errors in a consistent structure. Example: { "error": { "code": "VALIDATION_ERROR", "message": "Invalid email", "details": [{ "field": "email", "message": "Must be a valid email" }] } }. Use code for machine-readable handling and message for humans. details can hold field-level validation errors.
Map to status codes: validation → 400, not found → 404, auth → 401, forbidden → 403, conflict → 409, server error → 500. Never leak stack traces or internal paths to the client.
List endpoints (GET /users) must paginate to avoid returning millions of rows. Offset-based: ?page=2&limit=20 maps to LIMIT 20 OFFSET 20. Simple but slow on large offsets (the DB still scans skipped rows). Cursor-based: ?cursor=abc123&limit=20 returns the next 20 after that cursor (e.g. created_at or an opaque token). More efficient and stable when data changes between requests.
Include metadata in the response: next_cursor, has_more, or total_count (if cheap). Document the pagination contract so clients know how to iterate.
Let clients filter: ?status=active&role=admin. Map to WHERE clauses; validate and whitelist allowed params to avoid injection. Sorting: ?sort=created_at&order=desc. Limit sort keys to indexed columns when possible so queries stay fast.
Use consistent names (e.g. snake_case or camelCase) and document which query params each endpoint supports. Avoid over-flexible "query everything" APIs that make indexing and security hard.
For POST (and sometimes PUT/PATCH) that have side effects (payments, orders), clients may retry. Accept Idempotency-Key with a unique value (e.g. a UUID) in the header. Store the key with the result; if the same key is sent again within a window (e.g. 24 hours), return the stored result without re-executing. This prevents duplicate charges or duplicate orders.
Generate keys on the client (UUID or similar). The server only needs to store key → response (and optionally key → request) for the retention window.
Ready to see how this works in the cloud?
Switch to Career Paths for structured paths (e.g. Developer, DevOps) and provider-specific lessons.
View role-based pathsSign in to track your progress and mark lessons complete.
Questions? Discuss in the community or start a thread below.
Join DiscordSign in to start or join a thread.