"Fielding said your API isn't RESTful unless it's hypertext-driven. The industry shrugged and shipped HTTP+JSON APIs anyway, calling them REST. Both stances are defensible. Pretending the gap doesn't exist isn't."
The Empirical Reality of 'REST' in 2026
Survey the most-integrated APIs in production:
- Stripe — resource-oriented URIs, no HATEOAS. Clients use generated SDKs that hardcode URI patterns.
- OpenAI — RPC-shaped (
/v1/chat/completions). No HATEOAS pretense. - GitHub REST API — has some HATEOAS holdovers (URLs in responses, pagination Link headers), but most clients ignore them and hardcode URIs anyway.
- AWS — heavily RPC, signed requests, no hypermedia.
- Twilio, SendGrid, Slack, Anthropic, Google APIs — none are HATEOAS-driven.
What virtually all of them are, by Fielding's strict definition, is "RPC over HTTP with URI conventions and JSON bodies." Not REST in the dissertation sense. And they all work, scale, and ship.
Why HATEOAS Didn't Win
The benefits HATEOAS offers (URI evolution, dynamic capability discovery, generic clients) require costs that most API consumers couldn't or wouldn't pay:
1. Clients aren't generic. The dream of a single "HATEOAS client" walking any JSON API the way a browser walks any HTML site never materialized. Every API has business semantics that can't be expressed in links alone — error handling, retry policy, edge cases. A generic client that follows links but doesn't know what an order is can't actually do anything useful. So clients become API-specific, and the moment they're API-specific, they hardcode URI templates because that's faster than walking links every request.
2. Tooling assumes URI templates. OpenAPI, JSON Schema, generated SDKs, request/response validators — all are built around the assumption that the API has stable URI templates the client knows. Adopting HATEOAS means giving up most of this tooling, or building it twice.
3. Heavier responses. Every resource carries a links block that may double its size. For high-throughput APIs this is real bandwidth.
4. Testing is harder. You can't just "call /users/42" — you have to navigate from the root, follow links, find the user. Testing tools have to simulate the walk.
5. The "URI evolution" benefit is theoretical. Production APIs rarely change URIs in practice; they version (Track 2 lesson 5) instead. The escape hatch HATEOAS provides is for a problem most teams don't have.
What Most APIs Actually Ship — The Honest Middle
Real-world APIs adopt some hypermedia hints without committing to full HATEOAS:
Locationheader on 201 Created. The most universal hypermedia hint. New resource → here's its URI.- Pagination Link headers (RFC 8288).
Link: </items?cursor=abc>; rel="next". The client follows the next-page link instead of computing the URI. GitHub does this. - Redirects with Location. 301/302/307/308 all carry the next URI in Location. Hypermedia, lightweight.
- Sub-resource URIs in responses. A user resource may include
{"orders_url": "..."}as a convenience without making the client follow it.
This is the "REST API" most teams ship: resource-oriented URIs, the seven methods used semantically, status codes that actually mean something, plus a handful of hypermedia hints where they pay for themselves. Not pure REST. Not pure RPC. Coherent and pragmatic.
When HATEOAS Genuinely Wins
- Server-driven UIs. Frameworks like Spring HATEOAS, Hotwire/Turbo (HTML-based), or hypermedia-driven mobile apps use the server-driven-link approach to ship UI changes without redeploying clients.
- Workflow engines. When the available next-steps genuinely depend on resource state + user permissions + business rules, expressing them as links from the server is simpler than encoding the state machine on every client.
- Admin tools with dynamic permissions. The buttons a user sees should match their authorization. Server includes/omits links based on auth, client just renders.
- Long-lived, evolving APIs with diverse clients. If you're Twilio or Stripe with thousands of clients you can't recompile, HATEOAS gives you URI freedom — but you'd ALSO need to ship a generic client, which is its own project.
The Honest Framing
Stop arguing about whether your API "is REST." Frame the design choices instead:
- "This is a resource-oriented JSON-over-HTTP API. URIs name nouns. We use the seven HTTP methods semantically. Status codes communicate outcomes. Clients hardcode URI templates and use a generated SDK."
- "This is a workflow API with HATEOAS-style links. The client discovers available transitions from the server. We use HAL-formatted responses."
- "This is an RPC-over-HTTP API. URIs name operations; everything is POST; we return structured error objects."
Any of these is honest. "REST API" without elaboration is marketing.