Pattern: Hypermedia-Driven Workflow
The Hypermedia-Driven Workflow pattern uses HAL-formatted (Hypertext Application Language) controls embedded directly in resource representations to guide clients dynamically through complex multi-step processes and state transitions.
Instead of hardcoding state machines, UI route paths, or action permission logic inside client applications, the API server drives application state (HATEOAS). By inspecting the hypermedia links (_links) served in each response, clients discover valid next actions, dynamically render appropriate pages or forms, and seamlessly support pausing and resuming workflows across devices and sessions.
23.1. Overview
In Pattern 22 (Draft Workflow Resource), we established how a dedicated resource instance stores state across a multi-step process before final execution. The Hypermedia-Driven Workflow pattern takes this concept a step further by removing the client’s need to assume what step or action comes next.
Rather than forcing front-end applications to maintain static state maps (e.g., if (status == 'DRAFT') showSubmitButton()), the API response explicitly instructs the client on what operations are possible right now by embedding Hypermedia links.
Note: Examples use HAL-based _links as they are the easiest to adopt into existing APIs as well as new APIs.
{
"id": "art_9901",
"title": "API Design Patterns for Enterprise REST",
"status": "DRAFT",
"_links": {
"self": { "href": "/articles/art_9901" },
"next-step": { "href": "/articles/art_9901/content-editor", "title": "Edit Content" },
"submit": { "href": "/articles/art_9901/submit", "method": "POST", "title": "Submit for Review" }
}
}
Pausing and Resuming Workflows
When a user pauses a workflow (e.g., closing a browser or switching devices), resuming is effortless:
- The client issues a
GETrequest to retrieve the workflow resource instance. - The client inspects the returned
_linksobject and current status metadata. - The client dynamically routes the user to the form or page referenced by the
next-stepor actionable link relations (e.g.,edit-revisionsorschedule). - Only valid, permitted action buttons or form controls are rendered based on the presence of corresponding relation links (
rel).
23.2. When to Use Hypermedia-Driven Workflow
Use Hypermedia-Driven Workflows when:
- Workflows have dynamic or role-dependent state transitions.
- e.g., A Content Management System (CMS) where authors see
submit, editors seeapprove/request-revision, and publishers seeschedule/publish.
- e.g., A Content Management System (CMS) where authors see
- Clients must pause and resume workflows at later dates.
- Users can exit an application midway through a multi-step process and resume on a different client without losing context or requiring hardcoded client-side step tracking.
- Workflow business rules change frequently.
- Adding a new intermediate approval step (e.g., legal review before publishing) requires no client code updates; the server simply injects the new link relation into the response.
- You want to decouple UI routing and permissions from the server state machine.
- The client UI becomes a generic execution engine that renders screens based on link presence rather than embedded business logic.
23.3. When NOT to Use Hypermedia-Driven Workflow
Avoid Hypermedia-Driven Workflows when:
- The workflow is strictly static and simple CRUD.
- If a resource only supports standard
GET,PUT,DELETEoperations without business state transitions, HAL links add unnecessary payload overhead.
- If a resource only supports standard
- Building low-latency, machine-to-machine microservices.
- Internal microservices that rely on compiled gRPC or tightly coupled SDK contracts may incur unnecessary CPU parsing overhead from hypermedia links.
- Clients cannot parse dynamic link relations.
- If client developers refuse to use link-based navigation and insist on hardcoding exact URI endpoints, hypermedia controls provide little value.
23.4. What the Pattern Looks Like
Below is a complete lifecycle traversal for a Content Management System (CMS) article resource transitioning through seven states: DRAFT → SUBMITTED → REVISION_REQUESTED → APPROVED → SCHEDULED → PUBLISHED → ARCHIVED.
1. State: DRAFT (Pausing & Resuming)
An author retrieves a saved draft article. The server indicates that the author can continue editing (next-step) or submit for review.
Request
GET /articles/art_9901 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi... (Author Token)
Response
HTTP/1.1 200 OK
Content-Type: application/hal+json
{
"id": "art_9901",
"title": "API Design Patterns for Enterprise REST",
"status": "DRAFT",
"author": "Jane Doe",
"_links": {
"self": { "href": "/articles/art_9901" },
"next-step": { "href": "/articles/art_9901/editor", "title": "Continue Editing Draft" },
"submit": { "href": "/articles/art_9901/submit", "title": "Submit for Review" }
}
}
2. State: SUBMITTED (Editor View)
The author calls the submit link. An editor fetches the article. Because the user is an editor and the status is SUBMITTED, the server exposes approve and request-revision links.
Request
GET /articles/art_9901 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi... (Editor Token)
Response
HTTP/1.1 200 OK
Content-Type: application/hal+json
{
"id": "art_9901",
"title": "API Design Patterns for Enterprise REST",
"status": "SUBMITTED",
"submittedAt": "2026-08-04T09:00:00Z",
"_links": {
"self": { "href": "/articles/art_9901" },
"approve": { "href": "/articles/art_9901/approve", "title": "Approve Article" },
"request-revision": { "href": "/articles/art_9901/request-revision", "title": "Request Revision" }
}
}
3. State: REVISION_REQUESTED (Resumed by Author)
The editor requests revisions. When the author logs back in days later, the client fetches the article, reads status: REVISION_REQUESTED, and follows next-step (/articles/art_9901/revisions) to render the revision feedback form.
Request
GET /articles/art_9901 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi... (Author Token)
Response
HTTP/1.1 200 OK
Content-Type: application/hal+json
{
"id": "art_9901",
"title": "API Design Patterns for Enterprise REST",
"status": "REVISION_REQUESTED",
"revisionNotes": "Please add code examples for HAL links in Section 3.",
"_links": {
"self": { "href": "/articles/art_9901" },
"next-step": { "href": "/articles/art_9901/revisions", "title": "Edit Requested Revisions" },
"resubmit": { "href": "/articles/art_9901/submit", "title": "Resubmit for Review" }
}
}
4. State: APPROVED & SCHEDULED (Publisher View)
Once approved, the publisher receives options to publish immediately or schedule publication.
Request
GET /articles/art_9901 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi... (Publisher Token)
Response
HTTP/1.1 200 OK
Content-Type: application/hal+json
{
"id": "art_9901",
"title": "API Design Patterns for Enterprise REST",
"status": "APPROVED",
"_links": {
"self": { "href": "/articles/art_9901" },
"publish": { "href": "/articles/art_9901/publish", "title": "Publish Now" },
"schedule": { "href": "/articles/art_9901/schedule", "title": "Schedule Publication" }
}
}
5. State: PUBLISHED & ARCHIVED
After the article goes live, editing links are removed. Only archive or unpublish actions remain.
Response (Published State)
HTTP/1.1 200 OK
Content-Type: application/hal+json
{
"id": "art_9901",
"title": "API Design Patterns for Enterprise REST",
"status": "PUBLISHED",
"publishedAt": "2026-08-04T12:00:00Z",
"_links": {
"self": { "href": "/articles/art_9901" },
"canonical": { "href": "https://example.com/blog/api-design-patterns" },
"archive": { "href": "/articles/art_9901/archive", "title": "Archive Article" }
}
}
23.5. Anti-Patterns to Avoid
1. Hardcoding transition URLs inside client applications
- Why it’s bad: Constructing transition URIs manually in JavaScript/mobile code (e.g.,
fetch('/articles/' + id + '/submit')) bypasses hypermedia, breaking client apps whenever server routing or business workflow rules evolve.
2. Exposing links for unauthorized user actions
- Returning an
approveorpublishlink to an author who lacks editing permissions causes unnecessary403 Forbiddenerrors when clicked. The server should dynamically filter_linksbased on the authenticated user’s permissions.
3. Storing workflow state in local browser storage
- Relying on
localStorageor cookies to remember where a user left off causes state synchronization bugs when users log in from a second device. Use the resource’s hypermedia state (GET /resources/{id}) as the single source of truth.
4. Non-standard or unpredictable link relation (rel) names
- Inventing inconsistent link names (e.g., mixing
nextStep,next_step,continue_url) makes UI client engine code fragile. Establish standardized link relation names across your domain API guidelines.
23.6. OpenAPI Example
A complete OpenAPI 3.0.3 specification illustrating how to document HAL _links schemas and dynamic state actions for a CMS article resource.
openapi: 3.0.3
info:
title: CMS Article API - Hypermedia-Driven Workflow Example
version: 1.0.0
servers:
- url: https://api.example.com
paths:
/articles/{articleId}:
get:
summary: Retrieve article with hypermedia workflow links
description: Returns article state along with HAL '_links' indicating valid next workflow actions based on status and user roles.
tags: [Articles]
parameters:
- in: path
name: articleId
required: true
schema:
type: string
example: art_9901
responses:
'200':
description: Article retrieved successfully with contextual hypermedia links
content:
application/hal+json:
schema:
$ref: '#/components/schemas/ArticleHAL'
/articles/{articleId}/submit:
post:
summary: Submit article for review
tags: [Article Workflow]
parameters:
- in: path
name: articleId
required: true
schema:
type: string
example: art_9901
responses:
'200':
description: Article submitted; returns updated state and HAL links
content:
application/hal+json:
schema:
$ref: '#/components/schemas/ArticleHAL'
/articles/{articleId}/request-revision:
post:
summary: Request revisions from author
tags: [Article Workflow]
parameters:
- in: path
name: articleId
required: true
schema:
type: string
example: art_9901
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- revisionNotes
properties:
revisionNotes:
type: string
example: "Please add code examples for HAL links in Section 3."
responses:
'200':
description: Revisions requested; returns updated HAL resource
content:
application/hal+json:
schema:
$ref: '#/components/schemas/ArticleHAL'
components:
schemas:
ArticleHAL:
type: object
properties:
id:
type: string
example: art_9901
title:
type: string
example: "API Design Patterns for Enterprise REST"
status:
type: string
enum: [DRAFT, SUBMITTED, APPROVED, REVISION_REQUESTED, SCHEDULED, PUBLISHED, ARCHIVED]
example: REVISION_REQUESTED
author:
type: string
example: "Jane Doe"
revisionNotes:
type: string
example: "Please add code examples for HAL links in Section 3."
_links:
type: object
description: Contextual HAL hypermedia links driven by current resource state and permissions.
additionalProperties:
$ref: '#/components/schemas/HALLink'
example:
self:
href: "/articles/art_9901"
next-step:
href: "/articles/art_9901/revisions"
title: "Edit Requested Revisions"
resubmit:
href: "/articles/art_9901/submit"
title: "Resubmit for Review"
HALLink:
type: object
required:
- href
properties:
href:
type: string
example: "/articles/art_9901/submit"
title:
type: string
example: "Submit for Review"
method:
type: string
example: "POST"
23.7. Visualizing Hypermedia-Driven Workflow (Mermaid Diagram)
sequenceDiagram
autonumber
actor Author as Author Client (UI)
actor Editor as Editor Client (UI)
participant API as API Server
participant Workflow as Workflow Engine & State Machine
Note over Author,Workflow: Phase 1: Author Pauses & Resumes Draft
Author->>API: GET /articles/art_9901
API->>Workflow: Check state & author permissions
Workflow-->>API: Status: DRAFT
API-->>Author: 200 OK (HAL Body with _links.next-step & _links.submit)
Note over Author: Client evaluates _links.next-step & renders Content Editor UI
Author->>API: POST /articles/art_9901/submit (Follows _links.submit)
API->>Workflow: Transition status DRAFT -> SUBMITTED
API-->>Author: 200 OK (Status: SUBMITTED, _links has self only)
Note over Editor,Workflow: Phase 2: Editor Reviews & Requests Revision
Editor->>API: GET /articles/art_9901
API->>Workflow: Check state & editor permissions
Workflow-->>API: Status: SUBMITTED
API-->>Editor: 200 OK (HAL Body with _links.approve & _links.request-revision)
Note over Editor: Client evaluates _links & renders Approve / Revision buttons
Editor->>API: POST /articles/art_9901/request-revision { notes: "Add HAL code" }
API->>Workflow: Transition status SUBMITTED -> REVISION_REQUESTED
API-->>Editor: 200 OK (Status: REVISION_REQUESTED)
Note over Author,Workflow: Phase 3: Author Resumes Workflow Later
Author->>API: GET /articles/art_9901 (Days later from mobile app)
API->>Workflow: Check state & author permissions
Workflow-->>API: Status: REVISION_REQUESTED
API-->>Author: 200 OK (HAL Body with _links.next-step: "/revisions")
Note over Author: Client reads _links.next-step & dynamically opens Revision Form UI