Model resources, not actions
URLs are nouns; verbs are HTTP methods. POST /trades, GET /portfolios/{id},
DELETE /watchlist/{symbol} — not /createTrade or /getPortfolio.
| Method | Use | Idempotent? | Safe? |
|---|---|---|---|
| GET | read | ✅ | ✅ |
| POST | create / non-idempotent action | ❌ | ❌ |
| PUT | replace (full) | ✅ | ❌ |
| PATCH | partial update | usually ❌ | ❌ |
| DELETE | remove | ✅ | ❌ |
Idempotent = doing it twice == doing it once. It matters because networks
retry: a client that times out on PUT or DELETE can safely resend. POST
isn't idempotent — which is why creating money-moving resources needs an
idempotency key.
Status codes that signal correctly
200/201/204success (201 create, 204 no body).400bad input ·401unauthenticated ·403authenticated-but-forbidden ·404missing ·409conflict ·422validation ·429rate-limited.500us ·503down/overloaded.
Return a consistent error body (code, message, maybe details) so
clients can branch on code, not parse prose.
Pagination
- Offset/limit (
?page=3&size=20): simple, but slow deep in the list and skips/dupes rows when data shifts under you. - Cursor/keyset (
?after=<opaque>): pass the last seen sort key; stable and fast at any depth. Prefer cursors for feeds and large/changing lists.
Versioning & idempotency keys
- Versioning:
/.../v1/...(or a header). Version when you make a breaking change; add fields additively otherwise. - Idempotency key: client sends a unique
Idempotency-Keyon aPOST; the server records the result for that key and returns the same response on retry — so a retried "place trade" or "charge card" doesn't double-execute. (Razorpay webhooks in StockVision are verified + deduped for the same reason.)
Here's the retry that would have double-charged, made safe:
The key insight: the server answers "have I finished this key?" before doing any work — so the retry replays the stored answer instead of re-executing.
Seniors are judged on the unhappy path: what's the error contract, how does a client retry safely (idempotency), what's the rate-limit response (429 + Retry-After), and how does the API evolve without breaking old clients (additive changes + versioning)?