29. Pattern: Change Request Workflow
The Change Request Workflow pattern replaces the traditional direct-mutation CRUD model (PUT, PATCH) with a formalized proposal and approval lifecycle. Instead of immediately modifying a resource, clients submit a strongly-typed “change request” specifying the exact kind of modification they wish to make.
By utilizing a polymorphic discriminator (e.g., changeType), the API enforces distinct validation schemas for different types of changes. The proposed modification remains isolated in a pending state until an authorized entity reviews and approves it, at which point the system applies the changes to the underlying resource.
29.1. Overview
In standard CRUD REST APIs, issuing a PATCH /accounts/123 immediately updates the account. While efficient, this direct-mutation approach breaks down in regulated environments, enterprise systems, or scenarios requiring “maker-checker” (four-eyes) principles.
If a user wants to change their legal address or increase their credit limit, these actions often require human review, automated fraud checks, or strict audit logging. If you try to force this into a standard PATCH request, you end up with messy, implicit state machines (e.g., the PATCH succeeds, but the account is secretly locked pending review).
The Change Request Workflow solves this by treating the intent to change as a first-class resource:
- The client submits a
POSTto a nested collection (e.g.,/accounts/123/change-requests). - The change request includes a discriminator (e.g.,
changeType: "UpdateAddress"orchangeType: "IncreaseCreditLimit"). - The API uses the discriminator to validate the request against a specific schema tailored exactly to that modification.
- A reviewer (or automated system) evaluates the change request and issues an approval or rejection.
- Upon approval, the backend safely applies the mutation to the primary resource.
29.2. When to Use Change Request Workflow
Use the Change Request Workflow when:
- Enforcing “Maker-Checker” or Dual Control policies.
- A clerk proposes a change, and a manager must approve it before it takes effect (common in banking, HR, and access management).
- Modifications require asynchronous validation.
- If a change requires a background process (e.g., KYC/AML verification) that takes hours or days, a synchronous
PATCHis impossible.
- If a change requires a background process (e.g., KYC/AML verification) that takes hours or days, a synchronous
- Different modifications require vastly different validation rules.
- Changing a username requires checking uniqueness; changing a banking routing number requires algorithmic checksum validation. A polymorphic discriminator handles these cleanly.
- Strict audit trails are mandated.
- Regulators need to see not just what changed, but who proposed it, why, and who authorized it.
29.3. When NOT to Use Change Request Workflow
Avoid the Change Request Workflow when:
- The user is the sole owner of low-stakes data.
- Updating a profile biography, tweaking UI preferences, or renaming a personal playlist should use direct
PATCHorPUT.
- Updating a profile biography, tweaking UI preferences, or renaming a personal playlist should use direct
- The system requires real-time collaborative editing.
- Workflows requiring operational transformation or WebSockets (like editing a shared document) clash with rigid approval lifecycles.
- The approval process is completely automatic and instantaneous.
- If the server can validate and approve the change in milliseconds without external dependency, exposing the change request lifecycle to the client adds unnecessary friction.
29.4. What the Pattern Looks Like
Below are HTTP request and response flows demonstrating the submission of a polymorphic change request and its subsequent approval.
1. Submitting the Change Request (The “Maker”)
The client proposes a modification to an account. By specifying changeType: "CreditLimitIncrease", the API knows to validate the changeRequest against the credit limit schema.
Request
POST /accounts/acc_881/change-requests HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json
{
"changeType": "CreditLimitIncrease",
"reason": "Customer requested standard yearly increase.",
"changeRequest": {
"requestedLimit": 15000.00,
"currency": "USD"
}
}
Response
The server validates the specific schema, creates the change request in a pending state, and returns it. The core account resource remains untouched.
HTTP/1.1 201 Created
Location: https://api.example.com/accounts/acc_881/change-requests/cr_992
Content-Type: application/json
{
"id": "cr_992",
"accountId": "acc_881",
"status": "pending",
"changeType": "CreditLimitIncrease",
"submittedBy": "usr_maker_01",
"submittedAt": "2026-09-25T14:38:23Z",
"changeRequest": {
"requestedLimit": 15000.00,
"currency": "USD"
},
"links": {
"approve": "/accounts/acc_881/change-requests/cr_992/approve",
"reject": "/accounts/acc_881/change-requests/cr_992/reject"
}
}
2. Approving the Change Request (The “Checker”)
An authorized manager or automated system acts on the pending request.
Request
POST /accounts/acc_881/change-requests/cr_992/approve HTTP/1.1
Host: api.example.com
Authorization: Bearer manager_token...
Content-Type: application/json
{
"comments": "Approved based on account history."
}
Response
The system marks the request as approved and implicitly applies the 15000.00 limit to the parent acc_881 resource.
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "cr_992",
"status": "approved",
"resolvedBy": "usr_checker_99",
"resolvedAt": "2026-09-25T15:10:00Z"
}
29.5. Anti-Patterns to Avoid
- Using a generic
changesblob instead of a Discriminator:- Allowing users to submit
{"fieldsToChange": {"limit": 15000, "status": "active"}}bypasses strict schema validation. You cannot easily enforce that a user providing a new address also provides a valid zip code. Use achangeTypediscriminator mapped to concrete, enforceable schemas.
- Allowing users to submit
- Overloading
PATCHwith implicit workflows:- Returning a
202 Acceptedfrom aPATCHrequest and silently creating a workflow task in the background hides the domain model from the client. The client has no URI to check the status of their pending change.
- Returning a
- Applying changes before approval:
- Mutating the primary resource into a “locked” or “provisional” state breaks read consistency for other clients fetching the resource. The primary resource must reflect the last known approved state until the change request clears.
29.6. OpenAPI Example
A complete OpenAPI 3.0.3 specification illustrating the polymorphic ChangeRequest utilizing oneOf and a discriminator.
openapi: 3.0.3
info:
title: Account Management API - Change Request Workflow
version: 1.0.0
servers:
- url: https://api.example.com
paths:
/accounts/{accountId}/change-requests:
post:
summary: Propose a modification to an account
description: Submits a change request for review. Uses polymorphic schema validation based on changeType.
tags: [Change Requests]
parameters:
- in: path
name: accountId
required: true
schema:
type: string
example: "acc_881"
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ChangeRequestSubmit'
responses:
'201':
description: Change Request created and pending review.
content:
application/json:
schema:
$ref: '#/components/schemas/ChangeRequestResponse'
/accounts/{accountId}/change-requests/{requestId}/approve:
post:
summary: Approve a change request
description: Approves a pending change request and applies the mutation to the parent account.
tags: [Change Requests]
parameters:
- in: path
name: accountId
required: true
schema:
type: string
- in: path
name: requestId
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
properties:
comments:
type: string
responses:
'200':
description: Successfully approved.
components:
schemas:
ChangeRequestSubmit:
type: object
required:
- changeType
- changeRequest
discriminator:
propertyName: changeType
mapping:
UpdateAddress: '#/components/schemas/UpdateAddressChangeRequest'
CreditLimitIncrease: '#/components/schemas/CreditLimitIncreaseChangeRequest'
properties:
changeType:
type: string
reason:
type: string
changeRequest:
# In OpenAPI 3, the discriminator guides validation against the referenced schema
type: object
UpdateAddressChangeRequest:
type: object
required: [street, city, zipCode]
properties:
street:
type: string
city:
type: string
zipCode:
type: string
CreditLimitIncreaseChangeRequest:
type: object
required: [requestedLimit]
properties:
requestedLimit:
type: number
currency:
type: string
default: "USD"
ChangeRequestResponse:
type: object
properties:
id:
type: string
status:
type: string
enum: [pending, approved, rejected]
changeType:
type: string
submittedBy:
type: string
changeRequest:
type: object
29.7. Visualizing Change Request Workflow (Mermaid Diagram)
sequenceDiagram
autonumber
actor Maker as User (Maker)
actor Checker as Admin (Checker)
participant API as API Gateway
participant CR_DB as Change Request DB
participant Core_DB as Core Account DB
Note over Maker,Core_DB: Phase 1: Proposal (Validation & Storage)
Maker->>API: POST /change-requests (Type: CreditLimitIncrease)
API->>API: Validate change request details against CreditLimit schema
API->>CR_DB: Insert Status='pending'
API-->>Maker: 201 Created (Pending)
Note over Checker,Core_DB: Phase 2: Review & Execution
Checker->>API: GET /change-requests?status=pending
API-->>Checker: Returns pending requests
Checker->>API: POST /change-requests/cr_992/approve
API->>CR_DB: Update Status='approved'
API->>Core_DB: Apply requested mutation to Account resource
Core_DB-->>API: Success
API-->>Checker: 200 OK (Approved)