Everyone understands the word "REST" differently. For one person it's "JSON over HTTP", for another it's a strict set of rules about resources and methods, and for a third it's responses that tell the client what to do next. To avoid the argument, there's a handy ladder — the Richardson maturity model: it breaks "RESTfulness" into four steps. The top step is called HATEOAS. Let's see what it is, why it exists, and why almost nobody reaches it — and why that's fine.
The Richardson maturity model
Leonard Richardson proposed looking at an API as four levels of maturity. Each next level adds one idea.
- Level 0 — a single entry point. There's one endpoint, say
/api, and everything is sent to it, usually aPOSTwith a body like "action: get order, id: 42". HTTP here is just a pipe for moving data. This is what classic RPC over HTTP looks like. - Level 1 — resources. Separate addresses appear for entities:
/orders/42,/customers/7. We stop sending everything to one point and start addressing specific "things". But methods are still used haphazardly. - Level 2 — HTTP methods and statuses. We use verbs for their purpose:
GETreads,POSTcreates,PUT/PATCHchanges,DELETEremoves. And we answer with the right codes:200,201,404,409. This is exactly the level where most APIs called "REST" live — and it's covered in detail in the article on REST API. - Level 3 — HATEOAS. The response contains not only data but also hyperlinks to the actions currently available. The client doesn't build URLs itself — it follows the links, the way a person clicks through the pages of a website.
Important: this is not a "good/bad" score. It describes how many REST ideas you've applied. Level 2 is a perfectly working and respectable place to be.
What HATEOAS is
HATEOAS is an acronym for "Hypermedia As The Engine Of Application State". It sounds bulky, but the idea is simple.
Picture a website in a browser. You don't memorize the URL of every page — you see links and buttons and click the ones available right now. If an order isn't paid yet, there's a "Pay" button; after payment it disappears and a "Refund" one shows up. The browser doesn't know the site's structure in advance — it just follows the links the server sent it.
HATEOAS proposes the same thing, but for a program acting as a client. Along with the data, the server sends a list of available next actions as links. The client doesn't hardcode addresses like /orders/42/cancel — it takes the needed link from the response. If an action isn't available right now (the order is already cancelled), the link simply won't be there, and the client doesn't need to know the "when can I cancel" rules itself.
An example response with hyperlinks
Most often the links go into a separate block. One popular format is HAL (Hypertext Application Language), where links sit under the _links key:
{
"id": 42,
"status": "PAID",
"amount": 1500,
"_links": {
"self": { "href": "/orders/42" },
"refund": { "href": "/orders/42/refund" },
"invoice":{ "href": "/orders/42/invoice" }
}
}
Here's what's happening. Besides the order data itself, the server said: "here's a link to me (self), here's the 'refund' action (refund), and here's 'view the invoice' (invoice)." The client doesn't need to know how the refund URL is assembled — it takes the ready href.
Now the same order, but already refunded:
{
"id": 42,
"status": "REFUNDED",
"amount": 1500,
"_links": {
"self": { "href": "/orders/42" },
"invoice": { "href": "/orders/42/invoice" }
}
}
The refund link is gone — there's nothing to refund. A client that "just draws buttons from links" automatically hides the refund button. The rule "when a refund is allowed" stayed on the server, and the client didn't have to duplicate it.
Why it exists
HATEOAS has two honest upsides.
Loose coupling between client and server. The client doesn't bake in a map of URLs and the rules of "when which action is available". The server can change the refund address from /orders/42/refund to something else — the client won't break, because it takes the link from the response instead of assembling it. The "what's available" logic lives in one place: the server.
Self-documentation. The response itself shows what you can do with the resource next. Ideally the client can "explore" the API by following links from the root, much like a person browses a website, without constantly consulting separate documentation.
Why it's rarely reached in practice
Now, honestly, about the downsides — they're why level 3 stays mostly theory.
Harder for the server. Every response has to be decorated with links, you have to compute which actions are available right now, and support a format like HAL. That's noticeably more code than simply returning the data.
Harder for the client — if it even wants to use this. The pretty idea of "the client follows links and hardcodes nothing" requires a smart client that can find actions by name and react to them appearing and disappearing. In reality almost all clients know the API structure in advance anyway and just ignore the _links block — and then all the server's extra work goes to waste.
Few tools and little habit. The whole ecosystem is built around level 2: client generators, OpenAPI documentation, testing tools. A full HATEOAS client that dynamically follows links is supported by very few, and teams have no established practices.
The sober conclusion: most APIs called "REST" actually stay at level 2 — resources plus proper HTTP methods. And that's completely fine. HATEOAS is worth knowing as an idea and as the model's ceiling, but you don't need to drag it into every project.
Where this applies
Individual pieces of HATEOAS show up more often than the full level 3. Usually teams take not everything, but the most useful bits:
- Links to related resources. Returning a link to the customer and to the invoice along with the order is cheap and convenient, even if the rest stays at level 2.
- Pagination. A classic good case: the server sends
nextandprevlinks, and the client doesn't have to glue query parameters together for the next page. - Stateful processes. Where an entity has a lifecycle (draft → paid → shipped → refunded), links to the available transitions save the client from duplicating rules.
Where beginners stumble
- They confuse "REST" with level 3. Having heard that "real REST is HATEOAS", they start considering their working API "not real". Level 2 is a legitimate and by far the most common target — don't feel bad about it.
- They build HATEOAS nobody uses. They decorate responses with links, yet the client still hardcodes URLs and ignores
_links. The server's work goes to waste — first make sure the client will actually follow the links. - They invent their own link format in every endpoint. If you're going to do it, adopt a ready convention (HAL) consistently, not "
_linkshere,actionsthere, and justurlnext to it". - They drag the full level 3 into simple CRUD. For a couple of reference tables, hyperlinks to actions are extra complexity with no payoff. Start with clean level 2.
What to learn next
HATEOAS is the top of the maturity model, so the foundation beneath it matters most: first get comfortable with REST API at the level of resources and HTTP methods. Alongside it are other contract styles — gRPC for fast internal calls and GraphQL for flexible data slices; it helps to understand how they differ from REST. The transport under all of this is the HTTP protocol, whose methods and status codes are exactly what makes up level 2. And how the choice of contract style fits into designing a whole system is in the system design section.