DEV Community

Preecha
Preecha

Posted on

Should REST APIs Implement HATEOAS Hypermedia Links?

Should REST APIs Implement HATEOAS Hypermedia Links?

TL;DR

HATEOAS (Hypermedia as the Engine of Application State) is theoretically elegant but practically complex. Most APIs skip full HATEOAS and use selective hypermedia links for pagination, related resources, and actions.

Modern PetstoreAPI takes a pragmatic approach: it provides useful links without requiring clients to be fully hypermedia-driven.

Introduction

You’re reading about REST API design. You encounter HATEOAS (Hypermedia as the Engine of Application State). The explanation says: “Clients should discover all actions through hypermedia links, not hardcode URLs.”

Try Apidog today

You might wonder whether anyone actually builds APIs this way.

The answer is: not often. HATEOAS is the most frequently skipped REST constraint. Roy Fielding, who defined REST, considers it essential. Most API designers consider full HATEOAS impractical for their use cases.

As a result, many APIs described as “REST” are more accurately HTTP APIs or REST-like APIs.

Modern PetstoreAPI takes a pragmatic approach: use hypermedia links where they add value—such as pagination, related resources, and state-dependent actions—without forcing clients to be fully hypermedia-driven.

In this guide, you’ll learn:

  • What HATEOAS is
  • Why teams debate its value
  • How to implement practical hypermedia links
  • How to test links with Apidog
  • When to use hypermedia and when to skip it

What Is HATEOAS?

HATEOAS is a REST constraint that says clients should discover API capabilities through hypermedia links instead of relying entirely on documented URL patterns.

A traditional API client

A traditional client hardcodes resource URLs:

const response = await fetch(
  "https://petstoreapi.com/v1/pets/123"
);

const pet = await response.json();

const ordersResponse = await fetch(
  `https://petstoreapi.com/v1/pets/${pet.id}/orders`
);

const orders = await ordersResponse.json();
Enter fullscreen mode Exit fullscreen mode

The client needs to know the URL structure for pets and orders.

A hypermedia-driven client

With HATEOAS, the client starts from a known entry point and follows links returned by the server:

const rootResponse = await fetch(
  "https://petstoreapi.com/v1"
);

const root = await rootResponse.json();

const petsResponse = await fetch(root._links.pets.href);
const pets = await petsResponse.json();

const petResponse = await fetch(pets._links.self.href);
const pet = await petResponse.json();

const ordersResponse = await fetch(pet._links.orders.href);
const orders = await ordersResponse.json();
Enter fullscreen mode Exit fullscreen mode

The client does not need to construct the URL for the orders resource. It follows the link exposed by the API.

What HATEOAS provides

1. Reduced coupling

Clients do not depend directly on URL structures. The server can change URL patterns without necessarily breaking clients, as long as link relations remain available.

2. Capability discovery

A link can indicate that an action is currently available. If the link is absent, the action may not be available or permitted for the current user.

3. Self-documenting navigation

Clients can explore an API by following links, similar to navigating a website.

Example: a full HATEOAS response

{
  "id": "019b4132-70aa-764f-b315-e2803d882a24",
  "name": "Fluffy",
  "species": "CAT",
  "status": "AVAILABLE",
  "_links": {
    "self": {
      "href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24"
    },
    "update": {
      "href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24",
      "method": "PUT"
    },
    "delete": {
      "href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24",
      "method": "DELETE"
    },
    "orders": {
      "href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/orders"
    },
    "adopt": {
      "href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/adopt",
      "method": "POST"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The client can use the links without knowing the URL patterns in advance.

The HATEOAS Debate

The debate exists because the REST theory and common API implementation practices are different.

Arguments for HATEOAS

Loose coupling

Clients do not depend on URL structures. The server can change URLs while continuing to expose the same link relations.

Discoverability

Clients can explore an API without relying exclusively on external documentation.

State-driven actions

Links can represent the actions available for the current resource state. For example, an available pet may expose an adopt link, while an adopted pet does not.

Alignment with REST

Roy Fielding considers hypermedia-driven interaction an essential REST constraint. Without HATEOAS, an API does not meet the full REST definition.

Arguments against HATEOAS

Complexity

Clients need logic to parse links and navigate the API. A simple HTTP client can become a state machine.

Performance

A client may need additional requests to discover URLs that could have been called directly.

Debugging difficulty

Following a chain of links can be harder to debug than calling a known URL directly with tools such as curl.

Limited tooling support

Many HTTP clients, testing tools, and documentation generators assume that clients already know endpoint URLs.

Limited adoption

Major APIs such as GitHub, Stripe, Twilio, and Twitter do not generally require clients to be fully hypermedia-driven. They may provide selected links, but their clients typically rely on documented endpoint structures.

The practical reality

Most APIs described as REST skip full HATEOAS. They are commonly better described as HTTP APIs or REST-like APIs.

That does not make links useless. It means teams often adopt only the hypermedia features that solve a specific problem.

Practical Hypermedia Links

Instead of implementing full HATEOAS, add links where they reduce client-side URL construction or clarify available actions.

1. Pagination links

The problem

Clients should not need to reconstruct pagination URLs, especially when pagination uses filters, sorting, or opaque cursors.

The solution

Return links for the current, first, previous, next, and last pages when those pages exist:

{
  "data": [],
  "pagination": {
    "page": 2,
    "limit": 20,
    "totalPages": 10
  },
  "links": {
    "self": "https://petstoreapi.com/v1/pets?page=2&limit=20",
    "first": "https://petstoreapi.com/v1/pets?page=1&limit=20",
    "prev": "https://petstoreapi.com/v1/pets?page=1&limit=20",
    "next": "https://petstoreapi.com/v1/pets?page=3&limit=20",
    "last": "https://petstoreapi.com/v1/pets?page=10&limit=20"
  }
}
Enter fullscreen mode Exit fullscreen mode

A client can follow links.next instead of constructing the next URL.

2. Related-resource links

The problem

Clients need to know how to navigate from one resource to related resources.

The solution

Expose links for relationships such as the owner or orders:

{
  "id": "019b4132-70aa-764f-b315-e2803d882a24",
  "name": "Fluffy",
  "_links": {
    "self": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24",
    "orders": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/orders",
    "owner": "https://petstoreapi.com/v1/users/019b4127-54d5-76d9-b626-0d4c7bfce5b6"
  }
}
Enter fullscreen mode Exit fullscreen mode

This lets clients navigate related data without having to know every URL pattern.

3. Action links

The problem

Clients may not know which actions are available for the current resource state.

The solution

Return links for the actions the client can currently perform:

{
  "id": "019b4132-70aa-764f-b315-e2803d882a24",
  "status": "AVAILABLE",
  "_links": {
    "adopt": {
      "href": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/adopt",
      "method": "POST"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

If the pet has already been adopted, omit the action link:

{
  "id": "019b4132-70aa-764f-b315-e2803d882a24",
  "status": "ADOPTED",
  "_links": {}
}
Enter fullscreen mode Exit fullscreen mode

The absence of adopt tells the client not to display or attempt that action.

4. Cursor-based pagination

The problem

Cursor values are often opaque. Clients should not parse or modify them.

The solution

Return the complete next or previous URL:

{
  "data": [],
  "links": {
    "next": "https://petstoreapi.com/v1/pets?cursor=eyJpZCI6IjAxOWI0MTMyIn0"
  }
}
Enter fullscreen mode Exit fullscreen mode

The client treats the cursor as an implementation detail and follows the URL directly.

How Modern PetstoreAPI Uses Hypermedia

Modern PetstoreAPI uses selective hypermedia links instead of requiring a fully hypermedia-driven client.

Pagination links

Collection endpoints include pagination links:

GET /v1/pets?limit=20
Enter fullscreen mode Exit fullscreen mode
{
  "data": [],
  "pagination": {
    "limit": 20,
    "hasMore": true
  },
  "links": {
    "self": "https://petstoreapi.com/v1/pets?limit=20",
    "next": "https://petstoreapi.com/v1/pets?cursor=eyJpZCI6IjAxOWI0MTMyIn0&limit=20"
  }
}
Enter fullscreen mode Exit fullscreen mode

The client can use links.next without reading or generating the cursor itself.

Related-resource links

Individual resources include links to related resources:

GET /v1/pets/019b4132-70aa-764f-b315-e2803d882a24
Enter fullscreen mode Exit fullscreen mode
{
  "id": "019b4132-70aa-764f-b315-e2803d882a24",
  "name": "Fluffy",
  "ownerId": "019b4127-54d5-76d9-b626-0d4c7bfce5b6",
  "_links": {
    "self": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24",
    "owner": "https://petstoreapi.com/v1/users/019b4127-54d5-76d9-b626-0d4c7bfce5b6",
    "orders": "https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/orders"
  }
}
Enter fullscreen mode Exit fullscreen mode

No full HATEOAS requirement

Modern PetstoreAPI does not require clients to be hypermedia-driven.

Option 1: Follow links

const petResponse = await fetch(petUrl);
const pet = await petResponse.json();

const ownerResponse = await fetch(pet._links.owner.href);
const owner = await ownerResponse.json();
Enter fullscreen mode Exit fullscreen mode

Option 2: Construct URLs

const petResponse = await fetch(
  `https://petstoreapi.com/v1/pets/${petId}`
);

const pet = await petResponse.json();

const ownerResponse = await fetch(
  `https://petstoreapi.com/v1/users/${pet.ownerId}`
);

const owner = await ownerResponse.json();
Enter fullscreen mode Exit fullscreen mode

Both approaches work. Links are provided for convenience, not enforcement.

Testing Hypermedia APIs with Apidog

When an API returns links, test more than whether the response has a links property. Verify that expected links are present, correctly formatted, and usable.

Test link presence

Verify that a collection response includes the expected pagination links:

pm.test("Response includes pagination links", () => {
  const links = pm.response.json().links;

  pm.expect(links).to.have.property("self");
  pm.expect(links).to.have.property("next");
});
Enter fullscreen mode Exit fullscreen mode

For optional links such as prev or last, test them only when the response state indicates they should exist.

Test link validity

Follow a returned link and verify that it works:

const nextUrl = pm.response.json().links.next;

pm.sendRequest(nextUrl, (error, response) => {
  pm.test("Next link returns 200", () => {
    pm.expect(error).to.equal(null);
    pm.expect(response.code).to.equal(200);
  });
});
Enter fullscreen mode Exit fullscreen mode

This catches links that are present but point to invalid or unavailable resources.

Test link format

Verify that links use the format your clients expect:

pm.test("Links are absolute HTTPS URLs", () => {
  const links = pm.response.json().links;

  Object.values(links).forEach((link) => {
    pm.expect(link).to.match(/^https:\/\//);
  });
});
Enter fullscreen mode Exit fullscreen mode

If your API uses nested link objects, extract the href value before validating it:

pm.test("Action links are valid", () => {
  const links = pm.response.json()._links;

  Object.values(links).forEach((link) => {
    if (typeof link === "object" && link.href) {
      pm.expect(link.href).to.match(/^https:\/\//);
    }
  });
});
Enter fullscreen mode Exit fullscreen mode

When to Use HATEOAS

Use hypermedia links when they solve a clear client-side problem. Skip full HATEOAS when its complexity does not provide enough value.

Use hypermedia links for:

  1. Pagination

    Clients should not construct pagination URLs, especially when using opaque cursors.

  2. Related resources

    Links provide convenient navigation between related resources.

  3. State-dependent actions

    Links can show which actions are available for the current resource state.

  4. Complex workflows

    Links can guide clients through multi-step processes.

Consider skipping full HATEOAS for:

  1. Simple CRUD APIs

    Clients can construct predictable URLs easily.

  2. Internal APIs

    Teams may be able to coordinate URL changes directly.

  3. Performance-critical APIs

    Additional links increase response size.

  4. Mobile APIs

    Bandwidth constraints may make extra metadata undesirable.

Conclusion

HATEOAS is theoretically elegant but practically complex. Most APIs skip full HATEOAS and use selective hypermedia links where they provide clear value.

Modern PetstoreAPI demonstrates this pragmatic approach with:

  • Pagination links
  • Related-resource links
  • State-dependent action links
  • Optional hypermedia navigation

Clients can follow the links or continue constructing URLs directly.

Use Apidog to test link presence, validate URL formats, follow returned links, and verify that hypermedia navigation works as expected.

Key takeaways

  • Full HATEOAS is rare and complex.
  • Selective hypermedia links provide useful navigation without requiring a complex client.
  • Pagination links are one of the most practical hypermedia features.
  • Action links can represent state-dependent capabilities.
  • Do not force clients to be hypermedia-driven unless you have a specific reason.
  • Test links as part of your API contract.

Explore Modern PetstoreAPI documentation to see a practical hypermedia implementation.

FAQ

Is HATEOAS required for REST APIs?

According to Roy Fielding, the inventor of REST, HATEOAS is required for an API to meet the full REST definition.

In practice, most APIs skip HATEOAS and are better described as HTTP APIs or REST-like APIs.

What does HATEOAS stand for?

HATEOAS stands for Hypermedia as the Engine of Application State.

It means that clients discover API capabilities through hypermedia links instead of relying entirely on hardcoded URLs.

Do major APIs use HATEOAS?

GitHub, Stripe, Twilio, and most other major APIs do not require clients to be fully hypermedia-driven.

They may provide selected links for pagination or related resources, but clients generally rely on documented endpoint structures.

What is the difference between HATEOAS and hypermedia links?

HATEOAS is a REST constraint that requires clients to be fully hypermedia-driven.

Hypermedia links are simply links included in API responses. An API can provide links for pagination or navigation without requiring clients to use them for every request.

Should I implement HATEOAS in my API?

Probably not full HATEOAS unless you have a specific need for it.

A practical starting point is to add links for:

  • Pagination
  • Related resources
  • State-dependent actions
  • Complex workflows

Do not force clients to be hypermedia-driven unless the benefits justify the additional complexity.

How do I test HATEOAS APIs?

Test that:

  1. Expected links are present.
  2. Links use the expected format.
  3. Links point to valid resources.
  4. Following a link returns the expected status and response shape.
  5. State-dependent links appear or disappear correctly.

What is the HAL format?

HAL, or Hypertext Application Language, is a standard format for representing hypermedia links.

It commonly uses _links and _embedded fields. Modern PetstoreAPI uses a HAL-inspired link format.

Can clients ignore hypermedia links?

Yes. If an API provides links but does not require clients to use them, clients can construct URLs directly.

This is the pragmatic approach used by many APIs.

Top comments (0)