REST APIs — design and conventions
REST: resources at URLs, HTTP verbs for actions (GET safe/idempotent, POST create, PUT/PATCH update, DELETE), status codes (2xx/3xx/4xx/5xx) with semantic meaning, JSON bodies, versioning (URL or header), pagination (cursor preferred), filtering/sort via query params, HATEOAS (rarely fully adopted). Idempotency keys for unsafe retries.
REST is a set of conventions for HTTP APIs around resources, verbs, and status codes. Most production APIs are "RESTish" — they follow most conventions and skip the ideological parts (HATEOAS, hypermedia). What matters is predictability.
1. Resources, not actions
URLs name nouns, not verbs:
GET /users # list
GET /users/42 # one
POST /users # create
PUT /users/42 # replace
PATCH /users/42 # partial update
DELETE /users/42 # delete/getUser?id=42 and /deleteUser are RPC-style, not REST.
2. HTTP verbs and their guarantees
| Verb | Safe | Idempotent | Body |
|---|---|---|---|
| GET | ✓ | ✓ | no |
| HEAD | ✓ | ✓ | no |
| OPTIONS | ✓ | ✓ | no |
| PUT | ✗ | ✓ | yes |
| DELETE | ✗ | ✓ | no |
| POST | ✗ | ✗ | yes |
| PATCH | ✗ | not required | yes |
- Safe: doesn't modify state.
- Idempotent: same request, same effect.
- POST is not idempotent — that's why retries need an Idempotency-Key header for create operations (Stripe's pattern).
3. Status codes
- 2xx success: 200 OK (with body), 201 Created (+
Location), 204 No Content. - 3xx redirect: 301 permanent, 304 not modified (caching).
- 4xx client: 400 bad request (validation), 401 unauthorized (auth needed), 403 forbidden (no access), 404 not found, 409 conflict, 422 unprocessable, 429 too many requests.
- 5xx server: 500, 502, 503 (use 503 for maintenance), 504.
Use the right code. 200 with { error: "..." } is a hostile API.
4. Versioning
- URL path:
/v1/users— most common, easy to route, easy to deprecate. - Header:
Accept: application/vnd.myapi.v1+json— cleaner URLs, harder to debug.
Either is fine; pick one and be consistent. Avoid query-string versioning.
5. Pagination
- Offset/limit:
?page=2&limit=20— simple, breaks on inserts, expensive on deep pages. - Cursor:
?cursor=abc&limit=20— stable under inserts, supports infinite scroll. - Always include
nextCursor/prevCursor(or links) in the response.
6. Filtering, sorting, sparse fields
GET /products?category=shoes&price[gte]=100&sort=-price&fields=id,name,priceConventions vary; document them.
7. Errors
A consistent error envelope:
{ "error": { "code": "user_not_found", "message": "...", "details": {...} } }JSON:API and Problem Details (RFC 7807) are standardized options.
8. Idempotency, retries, concurrency
- Idempotency-Key for POST creates so retries don't duplicate.
- ETag + If-Match for optimistic concurrency on updates.
- Retry-After header on 429 / 503.
9. Auth
- Bearer tokens in
Authorizationheader. - OAuth 2 / OIDC for delegated auth.
- API keys for server-to-server.
- Never put secrets in URLs (logged everywhere).
10. Caching
Cache-Control,ETag,Last-Modified.- Conditional GET → 304.
11. Discoverability (HATEOAS)
The pure REST ideal: responses include links to next actions ("_links": { "self": "/users/42", "delete": "..." }). In practice, most production APIs skip this — clients hardcode URLs from a spec (OpenAPI). It's fine.
12. Common smells
- Verbs in URLs (
/getUsers,/deleteUser). - 200 for everything regardless of outcome.
- Inconsistent error shapes.
- Offset pagination for feeds where new items arrive.
- No idempotency story on creates.
Interview framing
"REST is HTTP-shaped: resources at URLs (nouns, not verbs), verbs for actions (GET safe/idempotent, POST create — not idempotent, PUT/PATCH update, DELETE), status codes with semantic meaning. Real conventions matter: cursor pagination for feeds (offset breaks under inserts), Idempotency-Key on POST creates so retries don't duplicate, a consistent error envelope, ETag/If-Match for optimistic concurrency, URL-path versioning, and proper use of 401 vs 403 vs 404. Full HATEOAS is rare in production — clients work from OpenAPI specs instead, and that's fine."
Follow-up questions
- •Why isn't POST idempotent and how do you make creates retry-safe?
- •Cursor vs offset pagination — when does the difference matter?
- •401 vs 403 vs 404 — when do you use each?
- •How does ETag-based optimistic concurrency work?
Common mistakes
- •Verbs in URLs.
- •200 with error body.
- •Offset pagination on a feed.
- •No idempotency on POST creates.
- •Different error shapes per endpoint.
Performance considerations
- •Caching headers + 304 conditional responses cut bandwidth. Compression (Brotli/gzip). Avoid n+1 on the client (batch endpoints or GraphQL).
Edge cases
- •PUT vs PATCH — replace vs partial update semantics.
- •DELETE returning 404 on a second call (idempotent? still 404).
- •Rate limits — 429 + Retry-After.
Real-world examples
- •Stripe, GitHub, Twilio — well-known examples of well-designed REST.