REST API HATEOAS Explained
Understand Hypermedia as the Engine of Application State, why most REST APIs skip it, and when adding hypermedia links actually pays off.
What you'll learn
- ✓What HATEOAS actually means
- ✓How hypermedia links drive state
- ✓HAL and JSON:API formats
- ✓When HATEOAS adds real value
- ✓Why most APIs skip it
Prerequisites
- •Familiar with HTTP
What and Why
HATEOAS, short for Hypermedia as the Engine of Application State, is the part of REST that most APIs quietly ignore. It says a client should not need out-of-band knowledge of your URLs. Instead, every response includes links describing the next legal actions, and the client navigates them like a browser navigates HTML.
Roy Fielding’s original constraint argued that this is what makes an API truly RESTful. In practice, most APIs are HTTP-based RPC. HATEOAS still matters in specific cases: long-lived integrations, workflow-driven APIs, and systems where state transitions change often.
Mental Model
Think of a web browser. You do not memorize URLs. You click links and submit forms. The server tells you what is possible next by what it renders. HATEOAS applies that idea to JSON APIs: each resource returns its own state plus links to related resources and allowed transitions.
GET /orders/42
{
"id": 42,
"status": "pending",
"_links": {
"self": { "href": "/orders/42" },
"pay": { "href": "/orders/42/pay" },
"cancel": { "href": "/orders/42/cancel" }
}
}
|
v client POSTs to /orders/42/pay
+---------------------+
| status: "paid" |
| _links: ship, refund|
+---------------------+ The set of links changes as state changes. A paid order no longer exposes pay; instead it exposes ship or refund.
Hands-on Example
The two most common hypermedia formats are HAL and JSON:API.
HAL is minimal. Add _links and optionally _embedded:
{
"id": 42,
"total": 1999,
"status": "pending",
"_links": {
"self": { "href": "/orders/42" },
"pay": { "href": "/orders/42/pay" },
"cancel": { "href": "/orders/42/cancel" }
},
"_embedded": {
"customer": {
"id": 7,
"name": "Ada Lovelace",
"_links": { "self": { "href": "/customers/7" } }
}
}
}
JSON:API is stricter. It mandates a data envelope, typed resources, and a relationships block:
{
"data": {
"type": "orders",
"id": "42",
"attributes": { "total": 1999, "status": "pending" },
"relationships": {
"customer": {
"data": { "type": "customers", "id": "7" },
"links": { "related": "/customers/7" }
}
},
"links": {
"self": "/orders/42",
"pay": "/orders/42/pay",
"cancel": "/orders/42/cancel"
}
}
}
A HATEOAS-aware client checks for the pay link instead of hardcoding the URL. If the server later moves it to /orders/42/checkout, the client keeps working because it follows the link by relation name, not by path.
For collections, include pagination links: first, prev, next, last. This is the most adopted form of HATEOAS in the wild because it solves a concrete problem.
Common Pitfalls
Adding links nobody follows. If every client hardcodes URLs anyway, you have added bytes and complexity for zero benefit. HATEOAS pays off only when clients actually use links.
Inventing your own link format. Pick HAL, JSON:API, or Siren. Custom shapes mean every client writes a custom parser.
Treating links as documentation. Links are for runtime navigation, not for replacing your API reference. You still need human docs that explain what pay does.
Coupling link rels to URLs. A link relation like pay is a contract. The URL behind it can change. Do not let clients break when you reorganize routes.
Forgetting state. The whole point is that links reflect current state. If you always emit every link regardless of status, you have hypermedia in name only.
Practical Tips
- Start small. Add
selflinks and pagination links first. They are cheap and useful. - Document link relations in a
rels/directory or a Profile URI. Treat them as part of your API contract. - Use IANA-registered relations (
self,next,prev,up) where they fit. Invent your own only when needed, and namespace them. - Pair HATEOAS with content negotiation. Serving
application/hal+jsonorapplication/vnd.api+jsonlets clients opt in. - Consider it for partner integrations, where you cannot easily push SDK updates. Internal microservices rarely need it.
- If using OpenAPI, you can describe links via the
linksfield on responses. This gives you discoverability without going full hypermedia.
Wrap-up
HATEOAS is the most-cited and least-implemented REST constraint. It is powerful when clients are long-lived and state machines are complex, and overhead when they are not.
Be honest about your use case. Pagination links and self references are almost always worth it. Full state-driven hypermedia is a strategic choice, not a default. Pick a standard format, document your link relations, and add links only where they let clients do something they could not before.
Related articles
- REST APIs REST API Error Handling Conventions
Design clear, consistent error responses for REST APIs using HTTP status codes, problem details, and error envelopes that clients can actually handle.
- REST APIs REST API Pagination Patterns
Compare offset, cursor, and keyset pagination for REST APIs. Pick the right pattern for your data, scale, and client experience.
- REST APIs REST API Throttling and Rate Limiting
Protect your API from abuse and accidental overload using token buckets, leaky buckets, and standard rate-limit headers that clients can actually respect.
- REST APIs REST API Versioning Strategies
Compare URL, header, and content-type versioning for REST APIs. Learn when to bump versions and how to retire old ones without breaking clients.