Most APIs described as RESTful stop at level 2 of the Richardson Maturity Model. What does level 3 add, and what does an API that stops at level 2 actually give up?
answer
- level 2 leaks URL construction + state machine to client
- hidden Cancel button vs 409 Conflict symptom
- links present only when action is legal
- forfeits evolvability, not correctness
- cheap middle: _links or availableActions for state-dependent UI
basics
~20 sLevel 3 puts links in responses so clients discover what they can do next. Stopping at level 2 means clients hardcode URL templates and duplicate the server's state machine - so URL changes and workflow rule changes become breaking changes.
solid answer
~50 sLevel 2 gives you correct verbs, status codes and addressable resources. What it does not give you is **runtime discovery**. At level 2 the client is compiled against knowledge it should not own: - **URL structure**: the client builds `/orders/{id}/cancel` itself, so any restructuring is a breaking change even though the data model did not move. - **The state machine**: the client re-implements *when* cancellation is legal. The server knows an order is shipped; the client guesses from a status field, so business rules exist twice and drift. Level 3 moves both into the response: an order carries a `cancel` link only while it is cancellable, and a `next` link only if another page exists. The client follows relation names; the server owns URLs and rules. What you give up at level 2 is therefore evolvability, not correctness. That is a fair trade when you control both sides and ship them together, and a poor one for long-lived APIs with many uncontrolled clients.
code
json · 17 lines{
"id": 42,
"status": "PENDING",
"_links": {
"self": { "href": "/orders/42" },
"cancel": { "href": "/orders/42/cancellation", "method": "POST" }
}
}
{
"id": 42,
"status": "SHIPPED",
"_links": {
"self": { "href": "/orders/42" },
"track": { "href": "/shipments/91" }
}
}go deeper
It is enough to say level 3 adds links so the client learns the next action from the response instead of building URLs itself.
Name both leaked responsibilities - URL construction and the state machine - and give a concrete example of each going wrong.
Be precise that the loss is evolvability, not correctness, and propose the pragmatic middle: conditional links for state-dependent UI decisions only.
Frame it as a coupling-budget decision driven by who controls the clients and how often they can be redeployed, and say when you would not pay for it.
## Where level 2 actually leaves you A level-2 API uses HTTP correctly: resources have URLs, GET is safe and cacheable, POST creates, DELETE removes, and status codes carry the outcome. Almost everything called a REST API in industry is here, and it works. The gap is not in the protocol mechanics - it is in **who knows what**. ## Knowledge the client should not own Two pieces of server knowledge leak into level-2 clients. **URL construction.** The client concatenates strings: base URL, `/orders/`, id, `/cancel`. The URL layout becomes part of the public contract, so a reorganisation - moving cancellation under a different collection, sharding customers onto another host, inserting a tenant segment - breaks clients that never asked for a data-model change. **The state machine.** Whether an order can be cancelled is a server rule involving shipment state, payment state and possibly time windows. A level-2 client typically reproduces that rule to decide whether to render a Cancel button: `if (order.status === 'PENDING')`. Now the rule lives in two places, and when the server adds "cancellable within 30 minutes of shipping", every client is stale until it is redeployed. The usual symptom is a button that is visible but produces 409 Conflict, or a hidden button for an action that is actually legal. ## What level 3 changes At level 3 the representation carries the affordances. The order response includes a link with relation `cancel` **only when cancelling is currently permitted**, and a `next` link only when another page exists. The client's logic becomes "render a Cancel button if a `cancel` link is present, and POST to whatever href it carries". URLs become server-owned implementation detail, and the workflow rule stays in one place. This is what "hypermedia as the engine of application state" means in practice: the response, not the client's compiled knowledge, drives what happens next. ## What you actually forfeit by stopping at level 2 Be precise, because the honest answer is narrower than the slogan: - **Independent evolvability of URLs.** You forfeit it, and must instead version and coordinate releases. - **A single home for workflow rules.** You forfeit it, and must accept duplicated conditionals or a separate "permissions" field. - **Generic clients.** You forfeit the (largely theoretical) ability for a client to navigate an unfamiliar API. You do *not* forfeit caching, retry safety, gateway policy, or good ergonomics. Those all come at level 2. ## The pragmatic middle Many teams take the cheap 80%: return a per-resource `_links` object or an `availableActions` array for exactly the state-dependent decisions the UI makes, and leave the rest of the client template-driven. That kills the duplicated state machine - the expensive half of the problem - without demanding a full hypermedia format, a media-type negotiation story, or client rewrites. Saying this out loud is usually the strongest senior answer: name the two losses, then say which one you would buy back and why.
- Give a concrete bug that level-3 links would have prevented.A UI shows a Cancel button based on `status == 'PENDING'`, but the server also refuses cancellation once a warehouse pick has started. Users click and get 409 Conflict. With a state-dependent `cancel` link the button simply is not rendered, because the server - the only party that knows about picking - decided.
- Does adding links let you stop versioning the API?No. Links remove coupling to URL layout and to workflow rules, but clients still couple to field names, types and relation-name meanings. You still need a versioning or additive-change policy for the representation itself; hypermedia narrows the surface rather than eliminating it.
saying these in an interview costs you the question
- Saying level 2 APIs are 'not cacheable' or 'not retry-safe' - those come at level 2
- Claiming hypermedia removes the need for any versioning
- Believing a static `_links` block that is always present delivers the benefit - the value is in links appearing conditionally
- Describing the loss as correctness rather than evolvability
- Assuming clients will auto-discover an unfamiliar API in practice