API contracts & REST
An API is a promise about shapes and verbs. Design the contract well and the AI fills in the implementation correctly.
An API is a promise about shapes and verbs: hit this URL with this method and this body, and you’ll get back that shape with this status code. Get the contract right and the AI fills in the implementation correctly — and your frontend and backend can move independently. This chapter is how to design that contract precisely. (The interview-depth HTTP/REST material is 2.1 and 2.2; this is the builder’s view.)
The noun goes in the URL, the verb in the method
A RESTful API names a resource in the URL (/api/todos, /api/todos/:id) and uses
the HTTP method for the action. The same path serves several operations by varying the
method — that’s the whole convention, and it’s why anyone can guess your API once they know
the resource. The anti-pattern that screams “not thought through” is verbs in the URL:
POST /createTodo, GET /deleteTodo/1. Name it right and the AI gets the verb, the path,
and the status code on the first try.
Status codes that tell the truth
The status code is a contract too — clients, caches, and monitoring all rely on it.
Returning 200 OK for everything (and hiding failure in the body) breaks all three. Use the
families: 2xx for success, 4xx when the caller did something wrong, 5xx when
the server broke. The leading digit tells you where to look first: a 4xx is on the request,
a 5xx is in your logs.
| Code | Means | Use it when |
|---|---|---|
| 200 / 201 / 204 | OK / Created / No Content | success; 201 after a create, 204 after a delete |
| 400 / 422 | Bad Request / Unprocessable | malformed or invalid input |
| 401 / 403 | Unauthenticated / Forbidden | not logged in vs logged in but not allowed |
| 404 / 409 | Not Found / Conflict | no such resource; or a duplicate/conflicting state |
| 429 / 500 | Too Many Requests / Server Error | rate-limited; or the handler threw |
The contract: shapes in, shapes out
The valuable artifact isn’t the code — it’s the agreed shapes: what the request body must contain, what comes back, and what an error looks like. Pin that down and the frontend can build against it before the backend exists (and vice versa).
POST /api/todos
Request { title: string (required), dueDate?: string }
201 → { id: string, title: string, dueDate: string | null, done: boolean }
400 → { error: "title required" }
401 → (not authenticated)
GET /api/todos → 200 { todos: Todo[] } (this user's only)
PATCH /api/todos/:id { done?: boolean } → 200 Todo | 403 (not owner) | 404Handing the AI this contract — not “make a todos API” — gets you correct verbs, status codes, required-field checks, and ownership scoping in one pass.
REST, GraphQL, or RPC?
REST is the default and the right starting point. The alternatives solve specific pains: GraphQL exposes one endpoint where the client asks for exactly the fields it needs (great when many clients need different slices, at the cost of more setup); RPC (like tRPC) is “call a typed function across the wire” (great inside one TypeScript codebase). At orientation level: pick REST unless you have a concrete reason, and know the words when the AI proposes an alternative.
01 Learning objectives
0 / 6 done02 Curated reading
03 Knowledge check
- 01easy
Which is the RESTful way to delete the note with id 42?
- 02medium
A logged-in user requests something they're not permitted to do. The right status code is…
- 03medium
Returning 200 for every response and signalling failure only in the body is fine.
04 Interview questions
browse all ↗What gets asked on this topic — tap a card for how to approach it, the follow-ups, and the trap. Company tags are best-effort & sourced.
-
When would you return 401 vs 403 vs 404, and why is “200 for everything” a problem?
401 Unauthorized means *not authenticated* — we don't know who you are (log in). 403 Forbidden means *authenticated but not allowed* — we know you, you still can't do this. 404 Not Found means the resource doesn't exist (and is sometimes returned instead of 403 to avoid leaking that a resource exists). Returning 200 for everything and hiding failure in the body breaks clients, caches, and monitoring, which all key off the status code — the first digit is supposed to tell you whose fault it is (4xx caller, 5xx server).
What a strong answer covers401 = not authenticated; 403 = authenticated but not authorized.
404 = doesn't exist (sometimes used instead of 403 to avoid leaking existence).
Status codes are a contract clients/caches/monitoring depend on.
200-for-everything hides failure and breaks all three.
Follow-ups they push on- When might you deliberately return 404 instead of 403?
- What status code fits a validation failure? (400/422)
Red flag Using 200 with an { error } body — tooling and caches treat it as success, so failures go unnoticed.
source: MDN — HTTP response status codes ↗