System Design's API lesson covers resource-oriented URLs and using HTTP methods correctly — genuinely useful, and also only level two of a four-level scale most APIs never reach the top of, on purpose.
3 min read
Almost every API described as "RESTful" — including the resource-oriented, correctly-status-coded style covered in System Design's API design lesson — stops short of REST's original, fuller definition. The Richardson Maturity Model names this gap directly as four levels, and understanding where a given API actually sits on it explains a lot about what tools like GraphQL (lessons 4–5) were reacting against.
POST /api)
that takes a payload describing what operation to perform, much like a
traditional RPC call. HTTP is used as a plain transport, nothing more./users,
/orders/5) instead of one endpoint for everything, but still often
using POST for every operation, ignoring the rest of the HTTP verb
vocabulary.GET/POST/PUT/DELETE and status
codes. This is where the overwhelming majority of real-world "REST APIs"
actually sit, and it's a perfectly reasonable, deliberate stopping point.The idea: instead of a client needing prior knowledge of every URL pattern an API exposes, each response includes links to the relevant next actions, the same way a website's HTML includes links a browser follows without the browser needing to know the site's URL scheme in advance.
{
"id": 42,
"status": "pending",
"total": 89.99,
"_links": {
"self": { "href": "/orders/42" },
"cancel": { "href": "/orders/42/cancel", "method": "POST" },
"pay": { "href": "/orders/42/pay", "method": "POST" }
}
}Notice what this response tells a client that a plain {"id": 42, "status": "pending", "total": 89.99} doesn't: this specific order, in this specific
state, can currently be canceled or paid — and if the order later ships,
those links simply stop appearing, without the client needing separate
business logic encoding "which actions are valid in which states." The
server, which actually owns that state machine, communicates it directly.
HATEOAS's theoretical appeal (a client that discovers behavior instead of hardcoding it) runs into a practical wall: nearly every real API client already hardcodes which endpoints to call, because that's what makes building a predictable frontend or mobile app tractable — a client parsing links out of every response and deciding what to do dynamically is significantly more complex to build than one with URLs already known at compile time. The genuine value HATEOAS provides (server-driven state transitions) rarely justifies that complexity for typical CRUD-shaped APIs, which is exactly why Level 2 is the practical industry default, not a failure to reach Level 3.
Not as a checklist to climb, but as vocabulary: recognizing that an API
using POST /doThing for everything is at Level 0 explains precisely why
it feels unpredictable to integrate against, and recognizing that full
HATEOAS is rarely worth its complexity cost explains why a well-designed
Level 2 API isn't "unfinished REST" — it's a deliberate, reasonable
trade-off most real systems make.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What distinguishes Level 2 from Level 3 in the Richardson Maturity Model?
2. Why do most real-world 'RESTful' APIs stop at Level 2 instead of reaching Level 3?
3. What does a HATEOAS-style response's '_links' field actually communicate that a plain data object doesn't?
API Design