HATEOAS: Hypermedia-Driven REST APIs
HATEOAS (Hypermedia as the Engine of Application State) is a constraint of REST that separates truly RESTful APIs from mere HTTP APIs. In a HATEOAS-driven API, the server guides the client entirely through hypermedia links embedded in responses — the client does not need any out-of-band knowledge about available endpoints. This is the same model that powers the web: a browser knows a single URL, receives HTML with embedded links, and follows those links to navigate the entire application.
Coined by Roy Fielding in his 2000 doctoral dissertation that defined REST, HATEOAS is the most misunderstood and least implemented REST constraint. Most production APIs stop at Level 2 of the Richardson Maturity Model, using HTTP verbs and resources but not hypermedia controls. Level 3, HATEOAS, represents the full vision of REST but requires significant shifts in how both servers and clients operate.
Richardson Maturity Model
Leonard Richardson proposed a maturity model for REST APIs with four levels that measure how closely an API follows REST principles:
- Level 0: HTTP as a tunnel (single endpoint, all actions via POST — essentially RPC over HTTP). SOAP and XML-RPC APIs at Level 0.
- Level 1: Resources (multiple endpoints, one per resource, but all actions use POST). Introduces the concept of resources but not HTTP verbs.
- Level 2: HTTP verbs (GET, POST, PUT, DELETE mapped correctly with proper status codes). This is where most modern “REST” APIs operate.
- Level 3: Hypermedia controls (HATEOAS — links in responses drive state transitions dynamically).
Most production APIs stop at Level 2. Level 3 — HATEOAS — is rare but powerful. The distinction is practical: at Level 2, the client still needs documentation to know that /orders/42 exists and supports DELETE. At Level 3, the server tells the client what it can do next by embedding links in every response. The client becomes a state machine driven by server responses rather than hard-coded URL patterns.
Hypermedia in Practice
A HATEOAS response includes a links array alongside the resource data, describing available state transitions:
{
"orderId": 42,
"status": "pending",
"total": 2999.99,
"links": [
{ "rel": "self", "href": "/orders/42", "method": "GET" },
{ "rel": "pay", "href": "/orders/42/payment", "method": "POST" },
{ "rel": "cancel", "href": "/orders/42", "method": "DELETE" }
]
---When the order status changes to “shipped”, the links change accordingly:
{
"orderId": 42,
"status": "shipped",
"total": 2999.99,
"links": [
{ "rel": "self", "href": "/orders/42", "method": "GET" },
{ "rel": "track", "href": "/orders/42/tracking", "method": "GET" },
{ "rel": "return", "href": "/orders/42/return", "method": "POST" }
]
---The client never hard-codes URLs. It follows rel values — “pay”, “cancel”, “track” — and reads the href from the response. This is the same model as a web page: a link with rel="stylesheet" tells the browser to fetch a CSS file without the browser knowing the URL in advance. The API client becomes a generic hypermedia consumer rather than a URL-templating machine.
This approach enables significant decoupling. The server can reorganize URL paths without breaking clients as long as the link relation names remain stable. The server controls the application workflow by deciding which links to include — a canceled order does not include a “pay” link. The client simply follows available links based on the user’s intent.
Media Types and Formats
HATEOAS requires a media type that supports hypermedia. The media type defines the format for expressing links and embedded resources. Common choices include:
- application/hal+json — HAL (Hypertext Application Language) with
_linksand_embedded - application/vnd.api+json — JSON:API with
linksandrelationships - application/collection+json — Collection+JSON with query templates
- application/problem+json — RFC 9457 problem details for error responses
HAL+JSON is the most widely adopted. A HAL response looks like:
{
"_links": {
"self": { "href": "/orders/42" },
"customer": { "href": "/customers/99" },
"items": { "href": "/orders/42/items" }
},
"orderId": 42,
"status": "pending",
"_embedded": {
"items": [
{ "_links": { "self": { "href": "/items/1" } }, "product": "Widget", "qty": 2 }
]
}
---HAL’s _embedded extension allows including related resources inline, reducing the number of requests the client must make. This is similar to GraphQL’s ability to fetch nested data in a single request, but it is server-driven rather than client-driven — the server decides which related resources to embed.
JSON:API offers a different approach with explicit relationships that separate resource identifiers from linkage data. Collection+JSON provides query templates that describe how to construct search and filter URLs. Each media type has trade-offs in expressiveness, complexity, and tooling support.
Implementing HATEOAS
A Spring Boot example using Spring HATEOAS:
@GetMapping("/orders/{id}")
public ResponseEntity<OrderResource> getOrder(@PathVariable Long id) {
Order order = orderService.findById(id);
OrderResource resource = new OrderResource(order);
resource.add(linkTo(methodOn(OrderController.class).getOrder(id)).withSelfRel());
if (order.getStatus() == OrderStatus.PENDING) {
resource.add(linkTo(methodOn(PaymentController.class).pay(id)).withRel("pay"));
resource.add(linkTo(methodOn(OrderController.class).cancel(id)).withRel("cancel"));
}
return ResponseEntity.ok(resource);
---Spring HATEOAS provides EntityModel, CollectionModel, and Link classes for building hypermedia responses. The WebMvcLinkBuilder generates correct URIs from controller method references, eliminating URL string manipulation.
In Python with Flask and Marshmallow:
class OrderSchema(Schema):
id = fields.Int()
status = fields.Str()
_links = fields.Method("get_links")
def get_links(self, obj):
links = {"self": {"href": f"/orders/{obj.id}"}}
if obj.status == "pending":
links["pay"] = {"href": f"/orders/{obj.id}/payment"}
links["cancel"] = {"href": f"/orders/{obj.id}"}
return linksThe key implementation pattern is conditionally including links based on resource state. A state machine encoded in link availability makes the API self-documenting — the available links at each step tell the client exactly what actions are possible.
Discoverability
The primary benefit of HATEOAS is discoverability. A client can start at a single entry point (e.g., GET /api), follow links, and navigate the entire API without prior knowledge of the URL structure:
GET /api
{
"links": [
{ "rel": "orders", "href": "/orders" },
{ "rel": "products", "href": "/products" },
{ "rel": "customers", "href": "/customers" }
]
---This enables generic API clients — think of browsers for web APIs — that can explore and interact with any HATEOAS-compliant service. It also decouples client and server: the server can reorganize URL paths without breaking clients, as long as the link relation names remain stable.
Discoverability is most valuable in ecosystems where multiple APIs must work together. A workflow automation tool could explore any HATEOAS-compliant API without custom connectors, constructing workflows by following link relations. This vision of interoperable web APIs, sometimes called “the Web of Data,” remains largely unrealized in practice.
Trade-offs and Challenges
| Benefit | Challenge |
|---|---|
| Loose coupling between client and server | No standard way to describe link relations |
| Self-documenting responses | Larger payloads from link metadata |
| Server-driven navigation | Requires more complex client navigation logic |
| No URL hard-coding | Discoverability tools are immature |
The biggest practical challenge is that most API consumers want static documentation (OpenAPI/Swagger). HATEOAS replaces or supplements static docs with runtime discovery, which breaks the common “read the docs, call the endpoint” workflow. Link relation registries (IANA maintained) help standardize common relation types like self, next, prev, edit, delete, but they are limited compared to OpenAPI’s rich description model.
Another challenge is client complexity. A HATEOAS client must parse link relations, follow links dynamically, handle missing links (state transitions that are not currently available), and manage navigation state. Most existing HTTP clients assume fixed URL patterns, so building a HATEOAS client requires custom infrastructure.
HATEOAS vs GraphQL
GraphQL solves a different problem — flexible data fetching — while HATEOAS addresses API navigation. They can coexist: a HATEOAS response can include GraphQL endpoint links for complex queries, while the GraphQL schema provides the type system that HATEOAS lacks. The GraphQL endpoint itself could return HATEOAS-style links in responses.
A pragmatic approach is to use HATEOAS for state transitions (workflows) while using OpenAPI for endpoint documentation. The links tell clients which transitions are available right now, while the schema documents the shape of each resource. This hybrid approach gives clients both static reference documentation and runtime workflow guidance.
Frequently Asked Questions
Is HATEOAS practical for real-world APIs?
HATEOAS is most practical within controlled ecosystems where both server and client are developed by the same organization. It is less practical for public APIs consumed by a broad developer audience, where URL conventions, comprehensive documentation, and SDK generation are more valuable than runtime discoverability. Many teams implement a lightweight version: include self and collection links in responses without going to full state-driven link generation.
What is a link relation?
A link relation (rel) is a string that describes the relationship between the current resource and the linked resource. Standardized relation types are registered with IANA (e.g., self, next, prev, edit, delete). Custom relation types use a URI format (e.g., https://api.example.com/rels/pay) to avoid naming collisions. Clients use rel values to find links, not URL patterns.
Does HATEOAS require a specific media type?
No, but it helps to use a standard hypermedia media type like HAL+JSON or JSON:API. Standard media types provide consistency, tooling support, and client library implementations. Custom hypermedia formats work but require custom client code for every API that uses them.
How does HATEOAS relate to OpenAPI?
OpenAPI documents the API surface statically — it describes available endpoints, request formats, and response schemas. HATEOAS provides dynamic navigation — the server tells clients which transitions are available for a specific resource at a specific time. They are complementary: OpenAPI for reference and code generation, HATEOAS for runtime workflow guidance.
Can I add HATEOAS to an existing API?
Yes, incrementally. Start by adding self-links to all responses — this immediately improves client navigation. Then add state-specific links for one workflow at a time. Monitor whether client teams use the links or continue hard-coding URLs. The incremental approach lets you test whether HATEOAS provides value in your ecosystem before full adoption.
For more on REST API design, see the REST API Design Guide. For API-first development practices, read API-First Development Approach. For REST vs GraphQL, see REST vs GraphQL Comparison.
For a comprehensive overview, read our article on Api Authentication Guide.
For a comprehensive overview, read our article on Api Caching Strategies.