Richardson Maturity Model and HATEOAS
The most-discussed and least-applied REST theory. Roy Fielding’s REST dissertation (2000) defined a strict interpretation; almost nobody fully implements it. The Richardson Maturity Model (Leonard Richardson, 2008) describes how close an API gets.
The four levels
| Level | Means |
|---|---|
| Level 0 | one URL, one HTTP method (often POST), payload contains everything (RPC over HTTP) |
| Level 1 | many URLs (resources), but still one HTTP method usually |
| Level 2 | proper HTTP verbs (GET/POST/PUT/PATCH/DELETE) + status codes |
| Level 3 | HATEOAS — responses include links to next actions |
Most “REST APIs” in production are Level 2. Level 3 is rare. Fielding considered only Level 3 actual REST; everything else is just “HTTP API.”
Level 0 — RPC over HTTP
POST /service HTTP/1.1
Content-Type: application/json
{"action": "getUser", "id": 42}
One URL. Action in the body. SOAP and XML-RPC live here.
Level 1 — Resources
POST /users HTTP/1.1
{"action": "get", "id": 42}
Now there’s a URL per resource type but methods are still ignored.
Level 2 — HTTP verbs and status codes
GET /users/42 → 200 OK + user data
POST /users → 201 Created + Location header
PUT /users/42 → 200 OK
DELETE /users/42 → 204 No Content
GET /users/99 → 404 Not Found
POST /users → 422 (validation failed)
The conventional “RESTful API.” Resources as URLs, verbs as actions, status codes as outcomes. This is what most teams mean by REST.
Level 3 — HATEOAS
Hypermedia As The Engine Of Application State. The response tells the client what they can do next.
GET /users/42
{
"id": 42,
"name": "Alice",
"balance": "100.00",
"_links": {
"self": {"href": "/users/42"},
"deposit": {"href": "/users/42/deposit"},
"withdraw": {"href": "/users/42/withdraw"},
"close-account": {"href": "/users/42/close"}
}
}
The client doesn’t need to know URL patterns. It navigates by following links from the entry point. Server can change URLs freely; client only depends on link relations.
If the account is overdrawn, the server might omit the withdraw link — the available actions are part of the response.
Why HATEOAS hasn’t won
In theory: clients are loosely coupled, URLs are server-controlled, the API self-describes.
In practice:
- Clients hardcode URL patterns anyway (it’s faster).
- Frontend devs need to know what actions exist to design UI; the runtime hypermedia tells them too late.
- OpenAPI/Swagger documentation solves most “discoverability” needs already.
- The extra payload size + complexity isn’t worth it for typical CRUD.
HATEOAS shows up in some niches: HAL, JSON:API, Spring HATEOAS. For the median API, Level 2 is plenty.
HAL — Hypertext Application Language
The most-implemented HATEOAS format:
{
"id": 42,
"name": "Alice",
"_links": {
"self": {"href": "/users/42"},
"orders": {"href": "/users/42/orders"},
"manager": {"href": "/users/7"}
},
"_embedded": {
"orders": [
{
"id": 1001,
"_links": {"self": {"href": "/orders/1001"}}
}
]
}
}
_links for navigation, _embedded for inlined related resources (avoids extra round trips for known-needed relations).
Standardized as application/hal+json.
JSON:API
More opinionated:
{
"data": {
"type": "user",
"id": "42",
"attributes": {"name": "Alice", "balance": "100.00"},
"relationships": {
"orders": {
"links": {"related": "/users/42/orders"},
"data": [{"type": "order", "id": "1001"}]
}
},
"links": {"self": "/users/42"}
},
"included": [
{"type": "order", "id": "1001", "attributes": {...}}
]
}
Verbose but consistent across APIs. Has a sparse fieldsets (?fields[user]=name), filtering (?filter[status]=active), and pagination spec built in.
Use when: building a public API with many clients; want a standard most JSON:API tooling can read.
REST’s real constraints (per Fielding)
Beyond the maturity model, REST as defined has six constraints. Most APIs violate at least a few:
| Constraint | Means | Real-world adherence |
|---|---|---|
| Client-server | separation of UI and data | universal |
| Stateless | each request stands alone (no server-side session) | partial — many APIs use sessions |
| Cacheable | responses indicate cacheability | partial — most ignore caching |
| Layered system | client can’t tell if it’s talking to proxies | universal |
| Uniform interface | resources, verbs, status codes, HATEOAS | partial — HATEOAS rare |
| Code on demand | (optional) server can send code to client | almost never |
“Stateless” doesn’t mean “no auth” — it means the server doesn’t hold per-client session state. Auth tokens (JWT) are fine; opaque session IDs that require server lookup technically violate.
In practice: nobody cares. “REST” colloquially means “HTTP + JSON + resources + verbs.”
Where HATEOAS actually pays off
- Long-lived APIs with many client types where versioning is painful (banking, government). The hypermedia evolves; clients follow.
- Workflow APIs where state machines determine available actions. The next-step links match the current state.
- APIs with many resource types and complex navigation — a HATEOAS client can crawl.
For most B2B SaaS and consumer apps: Level 2 + OpenAPI is more practical.
How to argue for/against in an interview
For:
- Self-describing — clients don’t hardcode URLs.
- Workflow APIs where actions are state-dependent.
- Long-term API evolution.
Against:
- Most clients hardcode URLs anyway.
- Payload bloat.
- Tooling poorer than for plain REST + OpenAPI.
- Frontend devs need design-time knowledge, which HATEOAS doesn’t help.
Common interview confusions
- “REST means HATEOAS.” — Fielding said so; the industry didn’t follow. “REST” colloquially is Level 2.
- “Level 3 is always better.” — better in some dimensions (loose coupling), worse in others (payload size, client complexity).
- “JSON:API is a HATEOAS implementation.” — supports it; not all JSON:API responses include actionable links.
Interview angle
- “What’s the Richardson Maturity Model?” — four levels: 0=RPC-over-HTTP, 1=resources, 2=HTTP verbs + status codes (the common “RESTful”), 3=HATEOAS (hypermedia links in responses).
- “What level is most APIs at?” — Level 2. Level 3 is the strict-REST ideal but rare in practice.
- “What is HATEOAS?” — Hypermedia As The Engine Of Application State. Responses include links to follow for next actions. Clients navigate the API by relation names, not hardcoded URLs.
- “Why isn’t HATEOAS universal?” — clients prefer hardcoded URLs (less work). OpenAPI solves the discoverability problem at design time without the runtime cost. The benefits don’t justify the complexity for most CRUD APIs.
- “What’s HAL vs JSON:API?” — both add hypermedia structure to JSON responses. HAL is simpler (
_links,_embedded); JSON:API is more opinionated (type/id/attributes/relationships, mandatory envelope). - “What does Fielding consider ‘real’ REST?” — Level 3, plus all six constraints (stateless, cacheable, uniform interface, layered, etc.). Most “REST APIs” violate at least the stateless and HATEOAS constraints.