HATEOAS Error Handling — Hypermedia Error Responses with Remediation Links
In this tutorial, you will learn about HATEOAS Error Handling. We cover key concepts, practical examples, and best practices to help you master this topic.
HATEOAS error handling extends standard error responses with hypermedia controls, including links to remediation actions, documentation, and retry endpoints that guide clients toward resolution.
What You'll Learn
- RFC 7807 Problem Details for REST errors
- Adding remediation links to errors
- Guiding clients to fix errors
- Validation errors with field-level links
- Rate Limiting with retry links
- Error documentation via describedBy
Why It Matters
Standard error responses tell you something is wrong but not how to fix it. Hypermedia error responses include links to documentation, retry endpoints, and remediation actions. DodaTech's Durga Antivirus Pro returns errors with links to help clients automatically handle rate limits, validation failures, and authorization issues.
Real-World Use
A client tries to quarantine a device but doesn't have permission. The error response includes a link to the authentication endpoint, a link to request elevated permissions, and a link to the API documentation about authorization. The client can show these options to the user.
flowchart TB
A["Client Action"] --> B["Server Error"]
B --> C["Problem Details JSON"]
C --> D["type: /errors/insufficient-permissions"]
C --> E["title: Permission Denied"]
C --> F["detail: User lacks admin role"]
C --> G["_links: auth, request-access, docs"]
D --> H["Client shows remediation options"]
G --> H
H --> I["Follow /request-access"]
H --> J["Follow /auth to re-login"]
style B fill:#fecaca,stroke:#dc2626
style C fill:#fef3c7,stroke:#d97706
Code Examples
Example 1: RFC 7807 Problem Details with Links
{
"type": "https://errors.dodatech.com/insufficient-permissions",
"title": "Insufficient Permissions",
"status": 403,
"detail": "User 'john.doe@example.com' does not have the 'admin' role required to quarantine devices.",
"instance": "/devices/1/quarantine",
"timestamp": "2026-06-28T10:30:00Z",
"correlationId": "req-abc-123",
"_links": {
"describedBy": {
"href": "https://docs.dodatech.com/errors/insufficient-permissions",
"title": "Error Documentation"
},
"auth": {
"href": "/auth/login",
"title": "Re-authenticate"
},
"request-access": {
"href": "/access-requests/new?resource=/devices/1/quarantine",
"title": "Request Admin Access"
},
"help": {
"href": "/support/tickets/new?error=insufficient-permissions",
"title": "Contact Support"
}
}
}
Example 2: Server-Side Error Handler with Links
const express = require('express');
const app = express();
class HypermediaError extends Error {
constructor(type, title, status, detail, instance, links = {}) {
super(detail);
this.type = type;
this.title = title;
this.status = status;
this.detail = detail;
this.instance = instance;
this.links = links;
}
}
function problemResponse(err, req, res) {
const body = {
type: err.type || 'https://errors.dodatech.com/generic-error',
title: err.title || 'Error',
status: err.status || 500,
detail: err.detail || err.message,
instance: err.instance || req.originalUrl,
timestamp: new Date().toISOString(),
correlationId: req.correlationId,
};
if (err.links && Object.keys(err.links).length > 0) {
body._links = err.links;
}
return res.status(err.status || 500).json(body);
}
// Validation error with field-level remediation
function validationError(errors, instance) {
const fieldLinks = {};
for (const field of Object.keys(errors)) {
fieldLinks[`fix:${field}`] = {
href: `/docs/fields/${field}`,
title: `How to fix '${field}'`,
};
}
return new HypermediaError(
'https://errors.dodatech.com/validation-error',
'Validation Failed',
422,
errors.map(e => `${e.field}: ${e.message}`).join('; '),
instance,
{
describedBy: { href: 'https://docs.dodatech.com/errors/validation', title: 'Validation Docs' },
...fieldLinks,
},
);
}
// Rate limit error with retry link
function rateLimitError(retryAfter, instance) {
return new HypermediaError(
'https://errors.dodatech.com/rate-limited',
'Rate Limit Exceeded',
429,
`Too many requests. Retry after ${retryAfter} seconds.`,
instance,
{
retryAfter: { href: instance, title: `Retry after ${retryAfter}s` },
upgrade: { href: '/pricing', title: 'Upgrade for higher limits' },
describedBy: { href: 'https://docs.dodatech.com/errors/rate-limiting', title: 'Rate Limits' },
},
);
}
// Error handler middleware
app.use((err, req, res, next) => {
if (err instanceof HypermediaError) {
return problemResponse(err, req, res);
}
// Generic error
return problemResponse(
new HypermediaError(
'https://errors.dodatech.com/internal-error',
'Internal Server Error',
500,
'An unexpected error occurred',
req.originalUrl,
{
describedBy: { href: 'https://docs.dodatech.com/errors/internal', title: 'Error Info' },
},
),
req, res,
);
});
Example 3: Client Handling Hypermedia Errors
class HypermediaErrorClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
async request(url, options = {}) {
const res = await fetch(`${this.baseUrl}${url}`, options);
if (!res.ok) {
const error = await res.json();
await this.handleError(error);
}
return res.json();
}
async handleError(error) {
console.error(`Error: ${error.title} (${error.status})`);
console.error(`Details: ${error.detail}`);
console.error(`Correlation ID: ${error.correlationId}`);
const links = error._links || {};
// Follow remediation links based on error type
if (error.type?.includes('validation-error')) {
console.log('Validation errors found. Fix fields:');
for (const [rel, link] of Object.entries(links)) {
if (rel.startsWith('fix:')) {
const field = rel.replace('fix:', '');
console.log(` - Fix '${field}': ${link.href}`);
}
}
}
if (error.type?.includes('rate-limited')) {
if (links.retryAfter) {
// Parse Retry-After header or use link
const retrySeconds = this.parseRetryAfter(error);
console.log(`Rate limited. Retrying in ${retrySeconds}s...`);
await this.delay(retrySeconds * 1000);
// The client doesn't need to know the URL - it's in the link
return this.request(error.instance);
}
}
if (error.type?.includes('insufficient-permissions')) {
if (links['request-access']) {
console.log(`Request access: ${links['request-access'].href}`);
// Automatically navigate to access request
const requestResult = await this.fetch(links['request-access'].href);
console.log('Access request submitted:', requestResult);
}
}
if (error.type?.includes('not-found')) {
if (links.collection) {
console.log(`Resource not found. Browse collection: ${links.collection.href}`);
}
}
throw new Error(error.detail);
}
}
Common Mistakes
- Returning plain error without links — if you return a 403 with just "Forbidden", the client has no idea what to do. Add links for auth, access requests, and docs.
- Using absolute URLs for everything — use relative URLs for same-API links and absolute URLs only for external documentation links.
- Not including a correlation ID — without a correlation ID, clients can't reference errors in support tickets. Always include one.
- Ignoring the
typefield — the type URI is the machine-readable error identifier. Each error type should have a dereferenceable documentation page. - Not adding rate limit headers with links — return both Retry-After header and a retry link. Some clients read headers, others read body.
Practice Questions
- What is RFC 7807 Problem Details and how does it structure errors?
- How do remediation links help clients handle errors?
- What links should a validation error include?
- How does a rate-limited client use hypermedia links?
- When should you use relative vs absolute URLs in error links?
Challenge: Design a hypermedia error response for each HTTP status code (400, 401, 403, 404, 409, 422, 429, 500) with appropriate remediation links for a threat management API.
Mini Project
Build a hypermedia error handling system with: RFC 7807 Problem Details format, remediation links per error type (auth, validation, rate limit, not found, conflict), automatic link generation based on error context, client library for automated error recovery, and error documentation pages served from the API.
FAQ
What's Next
Learn HATEOAS API design
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro