Definition
Hypermedia is the idea that content should include embedded links to related content, just like web pages have clickable links to other pages. In the context of REST APIs, hypermedia means API responses don’t just return data - they also include URLs that tell clients what actions they can take next or what related resources they can access.
Think about browsing the web. You don’t need to memorize URLs or construct them manually. You click links, and the website guides you through available actions. Hypermedia brings this same discoverability to APIs. Instead of hardcoding URLs in your client application, the server sends links dynamically based on the current state. If an order can be canceled, the response includes a “cancel” link. If it’s already shipped, that link won’t appear.
The “hyper” in hypermedia refers to non-linear navigation - you can jump between related resources without following a fixed sequence. The “media” refers to the content format (JSON, XML, HTML) that carries both data and navigational links. This makes APIs self-documenting and allows clients to discover functionality without external documentation.
Example
GitHub API Hypermedia: When you fetch a repository via GET /repos/facebook/react, the response includes data about the repo plus hypermedia links: "forks_url": "https://api.github.com/repos/facebook/react/forks", "issues_url": "https://api.github.com/repos/facebook/react/issues{/number}", "pulls_url": "https://api.github.com/repos/facebook/react/pulls{/number}". You don’t need to construct these URLs manually - the API tells you where to find related resources.
PayPal Payment API: When you create a payment, PayPal’s HATEOAS response includes links: "self": "/v1/payments/payment/PAY-123", "approval_url": "https://paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-456", "execute": "/v1/payments/payment/PAY-123/execute". The client follows these links to complete the payment flow without hardcoding URLs.
AWS S3 with Bucket Listings: When you list objects in an S3 bucket, if there are more results, the response includes <NextContinuationToken> and constructs the URL for the next page. You don’t calculate pagination offsets - you follow the provided link.
Stripe API Expandable Resources: Stripe uses hypermedia for related resources. A charge object includes "customer": "cus_123" (just an ID), but also provides a way to expand it: the API documentation and responses guide you to GET /v1/customers/cus_123. The expandable pattern is a form of hypermedia that lets clients navigate relationships.
Netflix-style Content API: A movie listing might include hypermedia links: "play_url": "/play/movie/12345", "trailer_url": "/media/trailer/12345", "cast_url": "/movies/12345/cast", "reviews_url": "/movies/12345/reviews". The app follows these links instead of constructing URLs from templates.
Analogy
The Shopping Mall Directory: When you’re at a mall, you don’t need to know every store’s exact location beforehand. You look at the directory (hypermedia), which shows related stores and points you to their locations. If a store is closed (unavailable state), it’s not listed in the directory. Hypermedia in APIs works the same way - the server tells you what’s available and how to get there.
The Choose-Your-Own-Adventure Book: These books don’t force a linear path. Each page says “If you choose to enter the cave, turn to page 45. If you choose to cross the bridge, turn to page 78.” You don’t need to flip through the book randomly - the current page tells you valid next steps. Hypermedia provides the same guided navigation for APIs.
Restaurant Menu with QR Codes: Modern menus include QR codes that link to allergen information, nutritional facts, or wine pairings. You don’t need to know the URLs - you scan the code next to each item. The menu (response) embeds links to related information (hypermedia).
GPS Navigation: GPS doesn’t just show your current location - it shows available routes, nearby gas stations, and alternate paths. The interface guides you to related destinations without requiring you to memorize map coordinates. Hypermedia makes APIs navigable like GPS makes maps navigable.
Museum Audio Guide: As you move through a museum, the audio guide suggests related exhibits based on your current location: “If you enjoyed the Impressionist gallery, visit the Modern Art wing (Room 12).” You discover the museum’s layout through guided links, not by memorizing a floor plan. APIs with hypermedia work the same way.
Code Example
// Example 1: Order Resource with Hypermedia Links
// GET /api/orders/789
{
"id": 789,
"user_id": 123,
"status": "pending",
"total": 59.98,
"created_at": "2026-01-09T10:30:00Z",
// Hypermedia: Links to related resources and available actions
"links": {
"self": {
"href": "/api/orders/789",
"method": "GET"
},
"user": {
"href": "/api/users/123",
"method": "GET"
},
"items": {
"href": "/api/orders/789/items",
"method": "GET"
},
// Available actions based on current state
"pay": {
"href": "/api/orders/789/payment",
"method": "POST"
},
"cancel": {
"href": "/api/orders/789",
"method": "DELETE"
},
"update": {
"href": "/api/orders/789",
"method": "PATCH"
}
}
}
// Example 2: Order After Payment (state changed)
// GET /api/orders/789 (after payment)
{
"id": 789,
"user_id": 123,
"status": "paid",
"total": 59.98,
"created_at": "2026-01-09T10:30:00Z",
"paid_at": "2026-01-09T10:35:00Z",
// Hypermedia: Different links based on new state
"links": {
"self": {
"href": "/api/orders/789",
"method": "GET"
},
"user": {
"href": "/api/users/123",
"method": "GET"
},
"items": {
"href": "/api/orders/789/items",
"method": "GET"
},
"invoice": {
"href": "/api/orders/789/invoice.pdf",
"method": "GET"
},
// "pay" link removed (already paid)
// "cancel" link removed (can't cancel paid order)
// New action available:
"request_refund": {
"href": "/api/orders/789/refund",
"method": "POST"
}
}
}
// Example 3: HAL (Hypertext Application Language) Format
// Standard hypermedia format
{
"_links": {
"self": { "href": "/api/products/456" },
"category": { "href": "/api/categories/electronics" },
"manufacturer": { "href": "/api/manufacturers/apple" },
"reviews": { "href": "/api/products/456/reviews" }
},
"_embedded": {
"manufacturer": {
"name": "Apple Inc.",
"_links": {
"self": { "href": "/api/manufacturers/apple" }
}
}
},
"id": 456,
"name": "iPhone 16",
"price": 999.00,
"stock": 42
}
// Example 4: Collection with Pagination Hypermedia
{
"total": 1250,
"page": 1,
"per_page": 20,
"data": [
{ "id": 1, "name": "Product A" },
{ "id": 2, "name": "Product B" }
// ... 18 more items
],
"links": {
"self": "/api/products?page=1&per_page=20",
"first": "/api/products?page=1&per_page=20",
"next": "/api/products?page=2&per_page=20",
"last": "/api/products?page=63&per_page=20"
// "prev" not included because we're on first page
}
}
// Example 5: JSON:API Format (Standard Hypermedia Specification)
{
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "REST API Design",
"body": "..."
},
"relationships": {
"author": {
"links": {
"self": "/articles/1/relationships/author",
"related": "/articles/1/author"
},
"data": { "type": "people", "id": "9" }
},
"comments": {
"links": {
"self": "/articles/1/relationships/comments",
"related": "/articles/1/comments"
}
}
},
"links": {
"self": "/articles/1"
}
}
}
Diagram
graph TB
Start[GET /api/orders/789]
Start --> Response1{Order Response
status: pending}
Response1 --> L1["link: self
/api/orders/789"]
Response1 --> L2["link: user
/api/users/123"]
Response1 --> L3["link: items
/api/orders/789/items"]
Response1 --> L4["link: pay
/api/orders/789/payment"]
Response1 --> L5["link: cancel
/api/orders/789"]
L4 --> |Client follows pay link| Pay[POST /api/orders/789/payment]
Pay --> Response2{Order Response
status: paid}
Response2 --> L6["link: self
/api/orders/789"]
Response2 --> L7["link: invoice
/api/orders/789/invoice.pdf"]
Response2 --> L8["link: request_refund
/api/orders/789/refund"]
Response2 --> L9["link: items
/api/orders/789/items"]
style L4 fill:#90EE90
style L8 fill:#FFB6C1
style Response1 fill:#ADD8E6
style Response2 fill:#FFE4B5
Best Practices
- Include links for all related resources: User, items, related orders, etc.
- Provide action links based on state: Only include “cancel” if the order is cancellable
- Use standard hypermedia formats: HAL, JSON:API, or Siren for consistency
- Include self link: Every resource should link to itself
- Use link relations:
relattributes like “next”, “prev”, “self”, “related” clarify link purpose - Provide pagination links: “first”, “last”, “next”, “prev” for collections
- Include method information: Specify GET, POST, PUT, DELETE for each action link
- Make links absolute URLs: Avoid relative paths that require client-side construction
- Document link relations: Explain what each link relation means in your API docs
- Version hypermedia contracts: Ensure link structures remain stable across API versions
Common Mistakes
Returning data without links: Just returning {"id": 123, "name": "User"} forces clients to hardcode URL patterns.
Inconsistent link formats: Mixing strings ("user_url": "/users/123") with objects ("items": {"href": "/items"}) creates confusion.
Including links regardless of state: Showing “cancel” link even when the order is already shipped violates HATEOAS principles.
Relative URLs without context: Using /users/123 instead of https://api.example.com/users/123 requires clients to construct base URLs.
Missing link relations: Not specifying rel attributes makes it unclear what each link represents.
Over-nesting: Embedding entire related resources instead of providing links bloats responses.
Ignoring standards: Inventing custom hypermedia formats instead of using HAL, JSON:API, or Siren.
Static links in documentation: Hypermedia should be in responses, not just documentation.
Breaking link contracts: Changing link structures between API versions breaks clients that follow links.