Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

HATEOAS and Hypermedia APIs

By Kokil Thapa | Last reviewed: September 2026

HATEOAS and Hypermedia APIs embed navigational links inside JSON responses so clients discover available actions at runtime. Most teams skip that step. They ship plain JSON and hard-code every URL in mobile apps, SPAs, and partner integrations. That works until you version routes, rename resources, or onboard clients you do not control. On production API development projects, I treat hypermedia as an architectural choice—not a REST purity test. This guide explains what HATEOAS actually means, how it maps to the Richardson Maturity Model, and when the extra payload complexity pays off in Laravel 13, Symfony 8.1, and public integration work.

What is HATEOAS and how does it relate to hypermedia APIs?

HATEOAS stands for Hypermedia As The Engine Of Application State. It is constraint six in Roy Fielding's REST definition. The server drives client state by returning hypermedia controls alongside data.

A hypermedia API response includes both the resource representation and the transitions the client may take. A booking record might expose self, cancel, and pay links. The client reads those links and chooses an action. It does not assemble URLs from a static route map.

Hypermedia is the broader idea: machine-readable links and forms inside representations. HATEOAS is the REST-specific application of that idea. In practice, engineers use the terms together when discussing REST API design at Richardson Level 3.

Richardson Maturity ModelLevel 0Single URI, XML-RPCLevel 1Many resourcesLevel 2HTTP verbsLevel 3HATEOASLevel 3 Response Shape{ "id": 42, "status": "pending","_links": { "self": "...", "pay": "..." } }Client follows links — no hard-coded routesMost production APIs stop at Level 2
Richardson Maturity Model levels — HATEOAS and Hypermedia APIs sit at Level 3 REST

Core terms you will see in hypermedia responses

  • Link relation (rel): A semantic label such as self, next, or cancel. Defined in RFC 5988 Web Linking.
  • href: The target URI for that relation.
  • method: Optional hint for POST, PATCH, or DELETE when GET alone is ambiguous.
  • Curie: A compact namespace prefix for custom relations, common in HAL payloads.

Level 2 REST—correct verbs and status codes on resource URLs—is where most Laravel and Symfony APIs live today. That is fine for first-party SPAs where you own the client. HATEOAS earns its keep when third parties integrate over years and you need API versioning without breaking unknown consumers.

How do you implement HATEOAS in a Laravel API?

Laravel 13 does not ship a dedicated HATEOAS layer. You compose it from API Resources, conditional logic, and a consistent link envelope. I have used this pattern on booking portals and legal-tech client APIs where document state machines drive available actions.

Centralise URL generation so links stay consistent across controllers and tests. Never scatter route names inside twenty Resource classes without a shared helper.

<?php

namespace App\Http\Resources\Concerns;

trait BuildsHypermediaLinks
{
    protected function link(string $rel, string $route, array $params = [], string $method = 'GET'): array
    {
        return [
            'rel' => $rel,
            'href' => route($route, $params, absolute: false),
            'method' => $method,
        ];
    }
}

Return links based on model state and policy checks. A cancelled invoice should not expose a pay link. That is where hypermedia carries business rules—not just navigation.

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources\Concerns\BuildsHypermediaLinks;

class InvoiceResource extends JsonResource
{
    use BuildsHypermediaLinks;

    public function toArray(Request $request): array
    {
        $links = [
            $this->link('self', 'invoices.show', ['invoice' => $this->id]),
        ];

        if ($this->status === 'pending' && $request->user()?->can('pay', $this->resource)) {
            $links[] = $this->link('pay', 'invoices.pay', ['invoice' => $this->id], 'POST');
        }

        if ($this->status === 'pending' && $request->user()?->can('cancel', $this->resource)) {
            $links[] = $this->link('cancel', 'invoices.cancel', ['invoice' => $this->id], 'POST');
        }

        return [
            'id' => $this->id,
            'status' => $this->status,
            'amount' => $this->amount,
            'links' => $links,
        ];
    }
}

This mirrors patterns from building RESTful APIs with Laravel. The difference is explicit: available transitions live in the payload, not in client code.

List endpoints need self, next, and prev relations. Pair hypermedia pagination with cursor or offset strategies from cursor versus offset pagination guidance. Wrap paginated data so clients always find the next page through links.

return response()->json([
    'data' => InvoiceResource::collection($invoices),
    'links' => [
        ['rel' => 'self', 'href' => $invoices->url($invoices->currentPage())],
        ['rel' => 'next', 'href' => $invoices->nextPageUrl()],
        ['rel' => 'prev', 'href' => $invoices->previousPageUrl()],
    ],
]);
HATEOAS Request CycleAPI ClientHypermediaAPI ServerResource+ link relsGET /invoices/42JSON + linkspay link presentcancel link absentClient chooses POST pay — no URL assemblyServer state change returns fresh links for the next valid actions
HATEOAS and Hypermedia APIs cycle — clients discover transitions from each response

Validate link output in feature tests. Assert that a pending invoice includes pay and that a paid invoice omits it. Hypermedia regressions are silent: clients do not crash—they just expose wrong buttons. Use the JSON formatter tool to inspect payloads during development.

When should you choose HATEOAS over plain JSON REST?

Not every endpoint needs hypermedia. I default to Level 2 REST for internal Vue or Livewire frontends on the same repo. I reach for HATEOAS when the API surface is public, long-lived, or state-heavy.

ScenarioPlain JSON RESTHATEOAS / Hypermedia
First-party SPA on same codebaseStrong fit — shared types and routesUsually unnecessary overhead
Public partner API with unknown clientsWorks with heavy documentationStrong fit — clients adapt to link changes
Mobile apps with slow upgrade cyclesBreaks when URLs shiftStrong fit — store rel names, not paths
State machines (bookings, legal workflows)Clients duplicate server rulesStrong fit — server exposes valid actions
High-throughput read APIsStrong fit — minimal payloadWeak fit — link objects add bytes
GraphQL or BFF aggregation layerClient selects fields explicitlyRare — different discovery model

On a legal-tech client portal similar to Mijar Law Associates, document workflows change with case status. Exposing only valid upload or sign links prevents clients from calling endpoints that will return 403 errors. That is a UX win and a security boundary.

Payment integrations are the opposite story. Gateways like eSewa or Stripe expect fixed callback URLs. Hypermedia helps your internal order state machine. It does not replace gateway contract docs. Combine both: hypermedia for your domain, OpenAPI for external fixed endpoints. See idempotency key patterns for payment POST links that must be safe to retry.

HATEOAS Decision TreePublic or long-lived API?NoPlain JSON RESTYesState-driven actions?NoOpenAPI + versioningYesUse HATEOAS linksFirst-party SPA on same repo → skip hypermedia unless rules are complex
Decision framework for HATEOAS and Hypermedia APIs versus plain JSON REST

What hypermedia formats should you use for API responses?

REST does not mandate a single hypermedia syntax. Teams pick a convention and enforce it across every endpoint. Mixing HAL on one route and JSON:API on another destroys client trust faster than skipping HATEOAS entirely.

Common format options

  1. Custom links array: Lowest friction in Laravel. You define shape and validation. Best when you control all clients and publish an SDK.
  2. HAL (Hypertext Application Language): Uses _links and optional _embedded. Mature tooling and clear nesting for related resources.
  3. JSON:API: Standardised links, relationships, and sparse fieldsets. Strong ecosystem; see the JSON:API specification.
  4. Collection+JSON or Siren: Richer form descriptions for generic hypermedia clients. Rare in PHP business APIs but useful for exploratory admin tools.

Symfony 8.1 projects can normalise through the serializer component. Laravel teams often stay with API Resources unless they already standardise on JSON:API via a package. Either way, pick one envelope and document it beside your OpenAPI file.

{
  "id": 42,
  "status": "pending",
  "_links": {
    "self": { "href": "/api/v1/invoices/42" },
    "pay": { "href": "/api/v1/invoices/42/pay", "method": "POST" }
  }
}

That HAL-style snippet is readable in logs and diff-friendly in code review. For SDK design, expose helper methods like invoice.pay() that read rel keys internally. Client developers should not parse raw JSON by hand.

How do you test, secure, and document hypermedia APIs?

Hypermedia shifts complexity from client route maps to server link correctness. Your test suite and docs must catch missing or stale relations before release.

Write assertions on relation names, not full URLs. Paths change with prefix or version; semantics should not.

public function test_pending_invoice_exposes_pay_link(): void
{
    $invoice = Invoice::factory()->pending()->create();

    $response = $this->actingAs($this->admin)->getJson("/api/v1/invoices/{$invoice->id}");

    $response->assertOk()
        ->assertJsonPath('status', 'pending')
        ->assertJsonFragment(['rel' => 'pay', 'method' => 'POST']);
}

Pair this with policy tests from Laravel API best practices. A link must never appear when authorization would deny the action.

Documentation strategy

OpenAPI describes static paths well. Hypermedia describes dynamic availability. Publish both: an OpenAPI spec for resource shapes and a short hypermedia guide listing every rel name, expected method, and state prerequisites. Tools like Scribe help for Laravel—see API documentation with Scribe and Redoc and Swagger UI for static docs.

Authenticate link generation with the same guards you use for routes. Sanctum and Passport patterns from Passport versus Sanctum apply. A hypermedia response is not a shortcut around API security review.

Coupling: Plain JSON vs HATEOASPlain JSON RESTClient hard-codes URLsDuplicated state rulesBreaks on route renameHeavy mobile app updatesFast to build initiallyHATEOAS HypermediaClient follows rel namesServer owns transitionsURL changes stay opaqueOlder clients stay compatibleMore server logic upfrontvsHATEOAS and Hypermedia APIs reduce client coupling on public integrations
Plain JSON REST versus HATEOAS — maintenance trade-offs for Hypermedia APIs

Gateway and aggregation layers

API gateways can strip or rewrite links if they terminate TLS on a different hostname. Configure your gateway to preserve link hrefs or rewrite them consistently. Patterns from Kong API gateway setup and API gateway patterns apply. On trekking booking systems like Adventure Third Pole Trek, BFF endpoints sometimes flatten hypermedia for a Livewire UI while the public mobile API keeps full links.

For greenfield platforms under enterprise application development, consider an API-first workflow. Define relation names in the contract before you write controllers. That prevents six months of ad hoc JSON shapes.

Key Takeaways

  • HATEOAS and Hypermedia APIs put navigational links in responses so clients discover actions instead of hard-coding URLs.
  • Most internal Laravel 13 and Symfony 8.1 apps stay at Richardson Level 2 unless third-party or mobile clients need runtime discovery.
  • Implement links in API Resources with policy-aware conditionals—never expose transitions the server would reject.
  • Pick one hypermedia format (custom links, HAL, or JSON:API) and test relation names, not full href strings.
  • Combine hypermedia docs with OpenAPI for shapes and an SDK that wraps link following for integrators.
  • Audit gateway and CDN layers so rewritten hostnames do not break href values in production.

People Also Ask

Is HATEOAS required for a REST API?

No. Fielding's REST architectural style includes HATEOAS as a constraint, but industry usage often means Level 2 HTTP APIs with good documentation. Many successful public APIs never adopted hypermedia. You need HATEOAS when unknown clients must survive URL and workflow changes without redeploying.

What is the difference between HATEOAS and OpenAPI?

OpenAPI describes your API surface statically—paths, schemas, and operations. HATEOAS describes what is available dynamically for a specific resource instance. OpenAPI says an invoice can be paid; hypermedia says this invoice exposes a pay link right now because status is pending and the caller is authorised.

Does GraphQL replace HATEOAS?

GraphQL uses a schema and client-written queries instead of link following. It solves a different discovery problem. Some teams run GraphQL at the edge and REST hypermedia for partner integrations. Choose based on client diversity, not fashion.

Can you use HATEOAS with Laravel Sanctum authentication?

Yes. Sanctum tokens authenticate the request; your Resource classes decide which links appear for that user. Links and policies must agree. Never emit a POST link for an action the same token cannot perform.

Ship APIs that clients can evolve with

HATEOAS and Hypermedia APIs are not academic REST trivia. They are a practical tool when your API outlives its first client app and business rules gate which actions exist at each state. Start with Level 2 REST, add links where coupling hurts, and document every relation name you publish. If you are planning a public integration layer on PHP 8.3+ or Laravel 13, contact us for architecture review—or explore custom software development for booking, legal, and eCommerce platforms that need state-aware APIs built correctly from day one.

Frequently Asked Questions

HATEOAS means Hypermedia As The Engine Of Application State. The server returns resource data plus embedded links so clients discover valid next actions at runtime instead of hard-coding URLs.

Hypermedia is the broader concept: machine-readable links and forms inside API representations. HATEOAS is the REST-specific application of that idea, constraint six in Roy Fielding's REST definition. Engineers often use both terms together when discussing Richardson Level 3 REST. A hypermedia response includes both the resource state and the transitions the client may take, such as self, cancel, or pay links on a booking record.

No. Fielding's REST style includes HATEOAS, but most production APIs are Level 2 HTTP APIs with good documentation. Adopt hypermedia when unknown clients must survive URL, version, or workflow changes without redeploying.

The Richardson Maturity Model ranks REST APIs from Level 0 through Level 3. Level 2 means correct HTTP verbs and status codes on resource URLs, which is where most Laravel 13 and Symfony 8.1 APIs live today. HATEOAS and hypermedia APIs sit at Level 3, where the server drives client state by returning navigational controls alongside data. That extra maturity pays off when third parties integrate over years and you need versioning without breaking unknown consumers.

Laravel 13 has no dedicated HATEOAS layer. You compose it from API Resources, a centralised link builder trait that calls route() for consistent href generation, and conditional logic inside toArray() based on model state and policy checks. A pending invoice might expose pay and cancel links; a paid one omits them. Paginated collections need self, next, and prev relations at the envelope level. Validate link output in feature tests so hypermedia regressions do not silently expose wrong actions to clients.

Default to Level 2 REST for first-party SPAs or Livewire frontends on the same codebase where you own routes and types. Reach for HATEOAS when the API is public, long-lived, state-heavy, or consumed by mobile apps with slow upgrade cycles. Document workflows, legal case status machines, and booking state transitions benefit because the server exposes only valid actions. High-throughput read APIs and GraphQL BFF layers are weaker fits because link objects add payload bytes or use a different discovery model.

OpenAPI describes your API surface statically: paths, schemas, and operations available in general. HATEOAS describes what is available dynamically for a specific resource instance right now. OpenAPI says an invoice can be paid; hypermedia says this invoice exposes a pay link because status is pending and the caller is authorised. Publish both: OpenAPI for resource shapes and a hypermedia guide listing every rel name, expected HTTP method, and state prerequisites for each transition.

GraphQL uses a schema and client-written queries instead of link following in JSON responses. It solves a different discovery problem. Some teams run GraphQL at the edge for flexible frontends while keeping REST hypermedia for partner integrations that must survive URL changes without app store releases. Choose based on client diversity and coupling tolerance, not framework fashion. They are alternatives for runtime discovery, not drop-in replacements for each other.

REST does not mandate one syntax. Common options include a custom links array with rel, href, and method fields, which fits Laravel API Resources with minimal friction; HAL using _links and optional _embedded; JSON:API with standardised links and relationships; and Collection+JSON or Siren for richer form descriptions. Symfony 8.1 projects can normalise through the serializer component. Pick one envelope and enforce it across every endpoint. Mixing HAL on one route and JSON:API on another destroys client trust faster than skipping HATEOAS entirely.

Yes. Sanctum tokens authenticate the incoming request; your API Resource classes decide which links appear for that authenticated user. Links and Laravel policies must agree. Never emit a POST link for an action the same token would be denied if called directly. Passport follows the same principle. A hypermedia response is not a shortcut around API security review. Treat visible links as part of your authorisation surface, not just navigation hints.

Write feature tests that assert on relation names and HTTP methods, not full href strings, because paths change with API prefix or version while semantics should not. For example, assert a pending invoice response includes a pay relation with method POST, and that a paid invoice omits it. Pair link assertions with policy tests so a link never appears when authorization would deny the action. Hypermedia regressions are silent: clients do not crash, they just show wrong buttons or call endpoints that return 403.

Generate links inside API Resources using the same policy checks that guard your routes. A cancelled invoice should not expose a pay link even if the route exists. Authenticate link generation with Sanctum or Passport guards identical to controller middleware. Document that visible links represent permitted transitions for the current caller and resource state. On legal-tech client portals with document workflows, exposing only valid upload or sign links prevents clients from calling endpoints that will fail authorization and improves both UX and security boundaries.

rel is the link relation, a semantic label such as self, next, or cancel, defined in RFC 5988 Web Linking. href is the target URI for that relation. method is an optional hint such as POST, PATCH, or DELETE when GET alone is ambiguous for the transition. curie is a compact namespace prefix for custom relations, common in HAL payloads. Clients should store and follow rel names rather than hard-coding href paths, so server-side route changes do not break integrations that respect the contract.

API gateways that terminate TLS on a different hostname can strip or rewrite links if not configured carefully. href values generated with relative paths or absolute URLs pointing to an internal host may break once traffic passes through Kong or similar gateways. Configure your gateway to preserve link hrefs or rewrite them consistently to the public hostname clients actually use. Audit gateway and CDN layers during deployment because broken href values cause clients to follow dead links even when relation names and business logic are correct.

Payment gateways expect fixed callback URLs documented in their integration contracts, not runtime-discovered links from your order resource. Hypermedia helps your internal order state machine expose valid pay or retry transitions to your own clients. It does not replace gateway contract documentation. Combine both approaches: use hypermedia for your domain API and OpenAPI or vendor docs for external fixed endpoints. For payment POST links your API exposes, follow idempotency key patterns so retry-safe transitions do not double-charge when clients follow links more than once.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: