From harness-claude
Explains HATEOAS for REST APIs with HAL and JSON:API examples, contrasting Level 3 dynamic links vs Level 2 hardcoded URLs. Use for state-dependent workflows and self-describing APIs.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Hypermedia As The Engine Of Application State (HATEOAS) embeds links to available next actions in every API response. Clients navigate the API by following links rather than constructing URLs — making the API self-describing and decoupling clients from URL structure.
Provides Ruby on Rails REST API guidelines to Richardson Maturity Level 3 (HATEOAS), covering resource modeling, HTTP semantics, hypermedia links, pagination, errors, and caching. Use for designing, building, reviewing, refactoring APIs.
Grades REST APIs on Richardson Maturity Model levels 0-3 (POX swamp to hypermedia controls) for design maturity evaluation, PR reviews, stakeholder explanations, and REST onboarding.
Generates hypermedia link operations for REST, GraphQL, and OpenAPI APIs. Activates automatically on hypermedia link generator mentions in API development tasks.
Share bugs, ideas, or general feedback.
Hypermedia As The Engine Of Application State (HATEOAS) embeds links to available next actions in every API response. Clients navigate the API by following links rather than constructing URLs — making the API self-describing and decoupling clients from URL structure.
"authorId": 42 is different from returning "author": { "href": "/users/42" }HATEOAS is the constraint that distinguishes Level 3 from Level 2 in the Richardson Maturity Model (see api-rest-maturity-model). A HATEOAS response tells the client what it can do next, not just what the current state is.
Without HATEOAS (Level 2):
{
"id": 42,
"status": "pending",
"amount": 150.0
}
The client knows the order is pending, but must have out-of-band knowledge that it can call DELETE /orders/42 to cancel or POST /orders/42/payments to pay. If those URLs change, clients break silently.
With HATEOAS (Level 3):
{
"id": 42,
"status": "pending",
"amount": 150.0,
"_links": {
"self": { "href": "/orders/42" },
"cancel": { "href": "/orders/42", "method": "DELETE" },
"pay": { "href": "/orders/42/payments", "method": "POST" },
"customer": { "href": "/customers/7" }
}
}
The server controls what actions are available. When the order is paid, the pay link disappears and a refund link appears. The client does not need to know that paid orders cannot be paid again — the server stops advertising the action.
HAL (Hypertext Application Language):
HAL is the most widely adopted hypermedia format. It uses _links for navigation and _embedded for inline related resources.
{
"id": 42,
"status": "shipped",
"total": 89.99,
"_links": {
"self": { "href": "/orders/42" },
"customer": { "href": "/customers/7" },
"track": { "href": "/shipments/s99" }
},
"_embedded": {
"items": [
{
"sku": "ABC-123",
"quantity": 2,
"_links": { "self": { "href": "/products/ABC-123" } }
}
]
}
}
JSON:API link format:
{
"data": {
"type": "orders",
"id": "42",
"attributes": { "status": "shipped", "total": 89.99 },
"links": { "self": "/orders/42" },
"relationships": {
"customer": {
"links": { "related": "/customers/7" }
}
}
}
}
A document approval workflow has states: draft, submitted, under_review, approved, rejected. Valid transitions depend on current state and the caller's role.
Without HATEOAS:
Clients must embed a state machine: "if status is submitted and I am a reviewer, I can call POST /documents/d1/approvals or POST /documents/d1/rejections." This logic must be duplicated in every client. When the workflow changes, every client must be updated.
With HATEOAS:
GET /documents/d1
→ 200 OK
{
"id": "d1",
"title": "Q4 Report",
"status": "submitted",
"_links": {
"self": { "href": "/documents/d1" },
"approve": { "href": "/documents/d1/approvals", "method": "POST" },
"reject": { "href": "/documents/d1/rejections", "method": "POST" },
"history": { "href": "/documents/d1/history" }
}
}
A reviewer sees approve and reject links. The document author sees only self and history. The state machine lives on the server; clients follow what is advertised.
Pagination as a practical HATEOAS application:
GET /orders?page=3
{
"items": [...],
"_links": {
"self": { "href": "/orders?page=3" },
"prev": { "href": "/orders?page=2" },
"next": { "href": "/orders?page=4" },
"first": { "href": "/orders?page=1" },
"last": { "href": "/orders?page=11" }
}
}
This is HATEOAS in practice. The client never constructs pagination URLs. See api-pagination-cursor for cursor-based navigation links.
HATEOAS without documented link relations. Links like "action123": { "href": "..." } are meaningless without documentation. Use IANA-registered link relations (self, next, prev, edit, delete) or define custom relations in your API documentation with consistent semantics.
Adding _links to every response regardless of whether it adds value. A self link on a deeply internal resource no client navigates to is noise. Apply HATEOAS where it actually changes client behavior — state machines, pagination, and discovery.
HATEOAS as a substitute for documentation. Links advertise available transitions; they do not describe request bodies, required fields, or error semantics. HATEOAS and OpenAPI are complementary, not alternatives.
Mixing HATEOAS depth inconsistently. Some resources return _links, others return bare IDs. Inconsistency forces clients to handle both patterns. If you adopt HATEOAS, apply it uniformly or not at all.
Hardcoding URLs in the client and ignoring the links. The entire benefit of HATEOAS is that clients follow links rather than construct URLs. If your clients build URLs from templates and ignore the _links, you are paying the response-size cost with none of the decoupling benefit.
Implementing HATEOAS increases:
Cache-Control values.When the cost is worth it:
When to skip it:
Most major APIs implement partial HATEOAS: pagination links universally, state-transition links selectively, full HAL rarely.
Link header for pagination (next, prev, last). Does not use HAL body links.url fields on resources but not HAL _links. Uses expand parameters rather than embedded resources._links including self, approve, capture, void on payment objects. One of the most complete HATEOAS implementations in a major public API.Link headers.The pragmatic conclusion: implement pagination links always, state-transition links for complex workflows, and full HAL only when client-server decoupling over time is a first-class requirement.
status field with constrained valid transitions (order lifecycle, document approval, subscription state)._links to responses that advertise only currently valid transitions for the caller's role. Use IANA-registered relation names where possible.next, prev, first, last) on all collection endpoints — this is the highest-ROI HATEOAS application.harness validate to confirm skill files are well-formed.next, prev, first, and last links in _links or Link headers._links, filtered by caller role.self, next, prev, edit) or are documented custom relations._links and others return bare IDs for the same type of relationship.