and when hypermedia really makes sense
HATEOAS is the principle that separates REST from a simple HTTP data transport, and at the same time the most debated and least implemented REST constraint. This article explains what hypermedia actually means, which formats have proven themselves, and when pragmatism should win over purity.
Table of Contents
- 1. What HATEOAS Actually Means
- 2. The Richardson Maturity Model, Level 0 to 3
- 3. HAL: Hypertext Application Language in Practice
- 4. JSON:API as a Hypermedia Alternative
- 5. Link Relations: rel, href and IANA Standards
- 6. Client-Driven Navigation Instead of Hardcoded URLs
- 7. When HATEOAS Really Makes Sense
- 8. The Pragmatic Middle Ground
- 9. HATEOAS Formats Compared
- 10. Summary
- 11. FAQ
1. What HATEOAS Actually Means
HATEOAS stands for Hypermedia as the Engine of Application State and is one of the six constraints Roy Fielding defined for REST in his 2000 dissertation. The core of the principle is simple: an API client should never need to know URLs in advance to interact with an API. Instead, every API response contains links to the next possible actions, much like a web browser navigates pages without needing to know every link's URL beforehand.
In practice this means: when a client fetches an article, the response contains not only the article data, but also links such as self (the URL of the article itself), edit (the URL for editing), delete and author. The client decides which action to take next based on these links, without having a single URL other than the API entry point hardcoded in its code. This principle theoretically makes APIs versioning-independent: if the server changes the URLs, all clients simply follow the new links.
Why is HATEOAS so rarely implemented in full? Because it involves considerable effort on both the client and server side, while offering real value only in certain scenarios. Most APIs serve a known, tightly coupled client. In that case, full HATEOAS is often over-engineering, but understanding the principle still helps produce better API designs.
2. The Richardson Maturity Model, Level 0 to 3
Leonard Richardson described a maturity model for REST APIs in 2008 that distinguishes four levels. Level 0 is the RPC-over-HTTP approach: a single URL, all requests via POST, and a self-defined XML or JSON protocol. SOAP web services and many older JSON-RPC APIs fall into this category. HTTP here is merely a transport protocol, not a semantic foundation.
Level 1 introduces resources: instead of a single URL, there are separate URLs for each resource type (/orders, /customers, /products). Level 2 uses HTTP verbs correctly: GET for reads, POST for creation, PUT/PATCH for updates, DELETE for deletion. HTTP status codes are used with the correct semantics: 200, 201, 204, 404, 409, 422. Most production REST APIs that deserve the name sit at Level 2.
Level 3 is HATEOAS: every response contains hypermedia links to the next possible actions. The API client only needs to know the entry point and navigates from there through every state. The model is a useful classification, not a quality judgment: a Level 2 API can be excellently designed, and a Level 3 API can be poorly designed. The maturity model describes properties, not merit.
// Level 2: Plain REST, client must know /orders/{id}/cancel
{
"id": 42,
"status": "pending",
"total": 129.90,
"customerId": 7
}
// Level 3: HATEOAS, client just follows the link
{
"id": 42,
"status": "pending",
"total": 129.90,
"_links": {
"self": { "href": "/orders/42" },
"cancel": { "href": "/orders/42/cancel", "method": "POST" },
"pay": { "href": "/orders/42/payment", "method": "POST" },
"customer": { "href": "/customers/7" }
}
}
3. HAL: Hypertext Application Language in Practice
HAL (Hypertext Application Language) is the best-known hypermedia format for JSON APIs. It defines two reserved fields: _links for links to related resources and actions, and _embedded for embedded resources that are fully included in the response to avoid roundtrips. HAL has the media type application/hal+json and is specified as an internet draft at the IETF, even though the draft never became an RFC.
The strength of HAL lies in its simplicity. A _links object contains named link relations as keys, where each relation is either a link object with href or an array of link objects. Optional fields such as title, type (media type of the linked resource), and templated (for URI templates like /orders{?page,size}) round out the base format. The _embedded field allows a list of orders to be embedded directly in the response instead of forcing an additional GET request per order.
// HAL response: order list with embedded resources
{
"_links": {
"self": { "href": "/orders?page=1&size=10" },
"next": { "href": "/orders?page=2&size=10" },
"first": { "href": "/orders?page=1&size=10" },
"last": { "href": "/orders?page=5&size=10" },
"create": { "href": "/orders", "method": "POST", "title": "Create a new order" }
},
"totalCount": 47,
"page": 1,
"size": 10,
"_embedded": {
"orders": [
{
"id": 42,
"status": "pending",
"total": 129.90,
"_links": {
"self": { "href": "/orders/42" },
"cancel": { "href": "/orders/42/cancel", "method": "POST" },
"customer": { "href": "/customers/7" }
}
}
]
}
}
4. JSON:API as a Hypermedia Alternative
JSON:API (jsonapi.org) is a complete specification for JSON-based APIs that goes beyond simple hypermedia links. JSON:API defines a standardized format for resources, relationships, errors, pagination and links. Its media type is application/vnd.api+json. Every resource has a unique id and a type, attributes are bundled in an attributes object, and relationships live in a relationships object.
JSON:API contains standardized link fields at both the document and resource level. A document can contain links.self, links.first, links.prev, links.next and links.last for pagination. Resources can contain individual links to themselves and to related resources. The advantage over HAL: JSON:API is far more extensively specified, which leads to greater interoperability between client libraries. The downside: the format is more verbose and requires more effort to understand.
The choice between HAL and JSON:API mostly depends on the client landscape. If several independent clients consume the API and client libraries are expected to be used, JSON:API is more attractive thanks to its broader ecosystem support. For internal APIs with a single known client, HAL is often the better choice due to its simplicity.
5. Link Relations: rel, href and IANA Standards
The heart of HATEOAS is not the format, but the semantics of link relations. The key in a _links object, for example self, next, author or cancel, is a link relation (short: rel). IANA maintains an official registry of standardized link relations: self for the canonical URL of the resource, next and prev for pagination, alternate for alternative representations, edit for the edit endpoint.
For application-specific relations not found in the IANA registry, there are two approaches. The RFC-compliant path is an absolute URI as the relation: "https://api.mironsoft.de/rels/cancel-order". In practice, short, self-explanatory identifiers like cancel, approve or ship are usually used and described in the API documentation. That is more pragmatic, but less interoperable. Anyone implementing HATEOAS consistently documents all link relations in an API profile or refers to IANA-registered relations wherever possible.
6. Client-Driven Navigation Instead of Hardcoded URLs
The real promise of HATEOAS is that the API client needs to know no URLs other than the entry point. In practice this means the client sends a request against the API root (GET /api/) at startup and extracts the URLs for all further actions from that response. This entry point is the only contract between client and server; every other URL can change without breaking the client.
What does that look like concretely? An order-process client calls GET /api/, finds _links.orders.href in it, and then performs GET /orders. In that response it finds _links.create for new orders. After creating an order, the 201 response contains _links.pay and _links.cancel. The client never knows how /orders/42/payment is constructed; it simply follows the link. This pattern has been standard in web browsers for decades; for API clients it requires a rethink in architecture.
# HATEOAS navigation: only the entry point is hardcoded
BASE="https://api.mironsoft.de/api"
# Step 1: fetch the entry point
ROOT=$(curl -s -H "Accept: application/hal+json" "$BASE/")
ORDERS_URL=$(echo "$ROOT" | jq -r '._links.orders.href')
# Step 2: load orders (URL from link relation, not hardcoded)
ORDERS=$(curl -s -H "Accept: application/hal+json" "$ORDERS_URL")
CREATE_URL=$(echo "$ORDERS" | jq -r '._links.create.href')
# Step 3: create a new order
NEW_ORDER=$(curl -s -X POST "$CREATE_URL" \
-H "Content-Type: application/json" \
-H "Accept: application/hal+json" \
-d '{"productId": 15, "quantity": 2}')
# Step 4: extract the payment URL from the response, no hardcoded URL
PAY_URL=$(echo "$NEW_ORDER" | jq -r '._links.pay.href')
curl -s -X POST "$PAY_URL" -H "Content-Type: application/json" \
-d '{"method": "credit_card", "token": "tok_xyz"}'
7. When HATEOAS Really Makes Sense
HATEOAS unfolds its value in very specific scenarios. The first is the public API with unknown clients: when an API is consumed by third-party developers who evolve their clients independently, hypermedia links significantly reduce coupling to concrete URL structures. If the server changes URL patterns, for example when introducing API versioning or restructuring resource hierarchies, clients that have consistently implemented HATEOAS remain functional.
The second scenario is state-dependent workflows, where certain actions are only available in certain states. An order can only be paid if it is in the pending status. It can only be canceled if it has not yet shipped. In a HATEOAS API, the absence of the corresponding link directly signals that the action is unavailable, with no client-side state management and no hardcoded business rules in the frontend. The cancel link only appears if the server actually allows cancellation.
HATEOAS, on the other hand, is not worthwhile for tightly coupled internal APIs consumed by a single known client. If frontend and backend live in the same repository and are deployed together, HATEOAS brings no decoupling benefit but considerable implementation effort. For mobile apps that update rarely and are bound by app store processes, HATEOAS can solve versioning problems, but only if the client framework actually navigates in a link-driven way.
8. The Pragmatic Middle Ground
The pragmatic recommendation for most projects: partial hypermedia. That means adding links selectively where they offer real value, without fully implementing HATEOAS. Concretely: a self link in every resource is always worthwhile and costs almost nothing. Pagination links (next, prev, first, last) are a universally useful pattern that simplifies client implementation. State-dependent action links for workflows make sense wherever the allowed actions change depending on resource state.
What partial hypermedia does not mean: adding URLs for every conceivable resource. A user object does not need a link to all of its orders if the client never navigates there dynamically anyway and instead always calls /orders?userId=7 directly. The middle ground requires deliberate decisions: where does the client actually navigate dynamically? Where are the links just formal completeness without practical benefit? That trade-off matters more than dogmatically sticking to Level 3.
9. HATEOAS Formats Compared
| Criterion | Plain JSON | HAL | JSON:API |
|---|---|---|---|
| Entry barrier | Very low | Low | Medium to high |
| Standardization | None | Internet draft (IETF) | Full specification |
| Client libraries | None | Few | Extensive ecosystem |
| Pagination | Define yourself | Links convention | Standardized |
| Response size | Small | Medium | Large |
| State-dependent links | Not supported | Possible | Possible |
The choice of format is less important than consistency in implementation. An inconsistent HATEOAS implementation that only delivers links on some endpoints offers neither the benefits of the partial hypermedia approach nor full decoupling. Anyone who chooses HAL or JSON:API should apply it consistently across all endpoints and clearly describe the link relations in the API documentation.
Mironsoft
REST API design, hypermedia architecture and API documentation
REST APIs that are actually RESTful?
We design and implement REST APIs that use HATEOAS pragmatically, with clear link relations, consistent hypermedia patterns and API documentation that genuinely helps clients.
API design review
Analysis of existing APIs for REST conformance, hypermedia potential and consistency
HAL / JSON:API
Implementing hypermedia links in existing REST APIs, incrementally and backward-compatibly
OpenAPI documentation
Complete API specification with link relations, state diagrams and example flows
10. Summary
HATEOAS is not an end in itself, but a tool for the right situation. The Richardson Maturity Model helps with classification, but it is not a certification program. Level 2, correct HTTP verbs, status codes and resource-oriented URLs, is the solid foundation on which the vast majority of production APIs should stand. Level 3 with full HATEOAS pays off when real decoupling benefits emerge: for public APIs with independent clients, for state-dependent workflows, and for APIs that must be versioned over long periods without breaking clients.
The pragmatic middle ground, pagination links, self links and state-dependent action links, offers the greatest benefit per unit of implementation effort. HAL is the right format for most projects: simple enough to consume without special libraries, and standardized enough to be interoperable. JSON:API is the better choice when a strong ecosystem with client libraries is to be used and the more extensive format is not a problem.
HATEOAS and Hypermedia, The Essentials at a Glance
Richardson Maturity Model
Level 0 (RPC) → Level 1 (resources) → Level 2 (HTTP verbs) → Level 3 (HATEOAS). Most APIs should aim for Level 2, Level 3 only where real decoupling is needed.
Link relations
Use IANA-standardized relations such as self, next, prev wherever possible. Describe custom relations in the API documentation. Consistency matters more than completeness.
HAL vs. JSON:API
HAL is simpler and well suited to internal APIs. JSON:API is more fully specified and has more client libraries, better for public APIs with a broad client landscape.
Pragmatic starting point
Start with self links and pagination links. Add state-dependent action links for workflows. Full HATEOAS navigation only where real decoupling is needed.