22. Pattern: Draft Workflow Resource
The Draft Workflow Resource pattern manages multi-step processes by capturing and persisting state inside a dedicated “draft” resource instance before executing a final completion action.
Instead of requiring clients to submit massive, fully populated payloads in a single request or relying on fragile client-side session state, this pattern allows consumers to accumulate workflow data progressively across multiple HTTP calls (POST, PATCH, GET).
Once all necessary information is gathered, the client executes a functional completion endpoint (e.g., POST /loan-applications/{id}/submit) that validates the complete draft and provisions all underlying domain entities atomically.
22.1. Overview
Many enterprise business procedures—such as loan applications, user onboarding, or complex multi-step checkout flows—require gathering information in stages.
The Draft Workflow Resource pattern separates the data accumulation stage from the final business transaction stage:
- Initiate Workflow (
POST /workflow-drafts): The client creates a new workflow session instance. The server returns a unique draft identifier (draftId) and initializes a draft resource in a state such asDRAFTorIN_PROGRESS. - Progressive Accumulation (
PATCH /workflow-drafts/{id}): The client updates the draft step-by-step as users complete UI forms. The server validates incoming partial payloads against step-specific rules while allowing incomplete overall state to be saved. - Inspect Progress (
GET /workflow-drafts/{id}): The client or server can retrieve current draft progress, including completion percentages, missing required fields, or validation warnings. - Final Completion / Submission (
POST /workflow-drafts/{id}/submit): A functional action endpoint validates that all required workflow stages are complete, freezes the draft, and atomically creates the downstream production entities (e.g., creating aLoan, anUnderwritingCase, and aBorrowerProfile).
22.2. When to Use Draft Workflow Resource Pattern
Use Draft Workflow Resource when:
- A business process spans multiple screens, steps, or user sessions.
- e.g., Commercial mortgage applications, multi-party contract builder wizards, or complex insurance quote questionnaires.
- Users need to pause and resume progress.
- Persisting intermediate state on the server allows users to start an application on mobile, save progress, and finish later on desktop without losing data.
- Creation of final domain entities requires atomic, all-or-nothing execution.
- Downstream domain entities (e.g., active bank accounts or underwriting cases) should not exist in invalid or partially configured states.
- Multiple users or systems contribute data to the same process.
- e.g., A primary borrower fills out personal information, and an employer or co-borrower uploads income verification to the same draft instance later.
22.3. When NOT to Use Draft Workflow Resource Pattern
Avoid Draft Workflow Resource when:
- The resource can be created in a single HTTP request.
- If all required fields are known upfront, use standard
POST /resourcesCRUD creation.
- If all required fields are known upfront, use standard
- The workflow consists of independent, immediate CRUD resources.
- If adding a user or uploading an asset during a wizard has immediate independent utility regardless of whether the wizard completes, create those resources directly.
- The process is strictly linear and ephemeral.
- If steps occur in a single short-lived front-end session and no multi-device persistence is required, maintain state in client application memory until a final submission call.
22.4. What the Pattern Looks Like
Below are detailed HTTP interaction flows demonstrating a Commercial Loan / Mortgage Application wizard.
1. Initiate Loan Application Draft
The client creates a draft application session.
Request
POST /loan-applications HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json
{
"applicantId": "usr_99182",
"productType": "COMMERCIAL_MORTGAGE"
}
Response
HTTP/1.1 201 Created
Location: https://api.example.com/loan-applications/app_draft_4012
Content-Type: application/json
{
"id": "app_draft_4012",
"applicantId": "usr_99182",
"productType": "COMMERCIAL_MORTGAGE",
"status": "DRAFT",
"currentStep": "BORROWER_DETAILS",
"completionPercentage": 10,
"createdAt": "2026-08-04T10:00:00Z"
}
2. Step 1: Add Borrower & Employment Details
The client submits Step 1 information via PATCH.
Request
PATCH /loan-applications/app_draft_4012 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json
{
"borrower": {
"annualIncome": 185000,
"employerName": "Apex Technologies LLC",
"yearsEmployed": 5
}
}
Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "app_draft_4012",
"status": "DRAFT",
"currentStep": "PROPERTY_INFO",
"completionPercentage": 40,
"borrower": {
"annualIncome": 185000,
"employerName": "Apex Technologies LLC",
"yearsEmployed": 5
}
}
3. Step 2: Add Property & Appraisal Details
The client submits Step 2 details.
Request
PATCH /loan-applications/app_draft_4012 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json
{
"property": {
"address": "742 Evergreen Terrace, Springfield",
"estimatedValue": 750000,
"propertyType": "COMMERCIAL_RETAIL"
},
"requestedLoanAmount": 500000
}
Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "app_draft_4012",
"status": "DRAFT",
"currentStep": "REVIEW_AND_SUBMIT",
"completionPercentage": 100,
"missingRequiredFields": []
}
4. Final Action: Submit Application (POST /loan-applications/{id}/submit)
The client triggers submission. The API validates the draft, transitions status to SUBMITTED, and provisions the real underwriting entities.
Note: This example uses the Functional Resource pattern for the submit action.
Request
POST /loan-applications/app_draft_4012/submit HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi...
Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "app_draft_4012",
"status": "SUBMITTED",
"underwritingCaseId": "case_88391",
"submittedAt": "2026-08-04T10:45:00Z",
"message": "Loan application successfully submitted for underwriting."
}
22.5. Anti-Patterns to Avoid
1. Creating production entities prematurely in draft states
- Instantiating actual
Account,Loan, orBillingSubscriptionrecords with placeholder orisTemporary=trueflags during early wizard steps pollutes production databases and makes reporting, auditing, and cleanup extremely difficult. Hold all temporary data in the workflow draft resource until submission.
2. Over-validating during intermediate draft updates
- Rejecting a
PATCH /loan-applications/{id}request because step 3 fields are missing while the user is still on step 1 renders the draft pattern useless. Relax validation rules on intermediate draft updates, enforcing strict full-payload validation only when the final/submitaction is invoked. Alternatively, use a validation library that performs progressive validation of the fields provided after each step is completed.
3. Lack of draft expiration / TTL cleanup policies
- Allowing abandoned drafts to linger indefinitely clutters storage. Implement automatic cleanup or archiving for inactive drafts (e.g., auto-expire drafts after 30 days of inactivity).
4. Using POST collection calls for each wizard step
- Creating separate endpoints like
POST /loan-step-1,POST /loan-step-2, andPOST /loan-step-3fragments state into artificial endpoints. Use a single draft resource (/loan-applications/{id}) and update it using HTTPPATCH. This decouples the front-end user interface from the back-end storage, allowing the user interface to evolve over time without requiring corresponding changes to the API.
22.6. OpenAPI Example
A complete OpenAPI 3.0.3 specification illustrating the draft workflow lifecycle: creation, progressive partial updates, draft retrieval, and final submission.
openapi: 3.0.3
info:
title: Commercial Loan Application API - Draft Workflow Example
version: 1.0.0
servers:
- url: https://api.example.com
paths:
/loan-applications:
post:
summary: Initiate a new loan application draft
description: Creates a temporary workflow draft session to accumulate application data across multiple steps.
tags: [Loan Applications]
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- applicantId
- productType
properties:
applicantId:
type: string
example: usr_99182
productType:
type: string
example: COMMERCIAL_MORTGAGE
responses:
'201':
description: Draft application created
headers:
Location:
schema:
type: string
example: "https://api.example.com/loan-applications/app_draft_4012"
content:
application/json:
schema:
$ref: '#/components/schemas/LoanApplicationDraft'
/loan-applications/{applicationId}:
get:
summary: Retrieve loan application draft
description: Fetches current progress, stored draft fields, and completion details.
tags: [Loan Applications]
parameters:
- in: path
name: applicationId
required: true
schema:
type: string
example: app_draft_4012
responses:
'200':
description: Draft details
content:
application/json:
schema:
$ref: '#/components/schemas/LoanApplicationDraft'
patch:
summary: Update draft application step data
description: Progressively accumulates borrower, property, or loan term data into the draft.
tags: [Loan Applications]
parameters:
- in: path
name: applicationId
required: true
schema:
type: string
example: app_draft_4012
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LoanApplicationUpdate'
responses:
'200':
description: Draft updated successfully
content:
application/json:
schema:
$ref: '#/components/schemas/LoanApplicationDraft'
/loan-applications/{applicationId}/submit:
post:
summary: Submit completed loan application
description: Validates all required draft fields, transitions status to SUBMITTED, and creates underwriting case.
tags: [Loan Applications]
parameters:
- in: path
name: applicationId
required: true
schema:
type: string
example: app_draft_4012
responses:
'200':
description: Application submitted successfully
content:
application/json:
schema:
$ref: '#/components/schemas/SubmissionResult'
'422':
description: Unprocessable Entity - Draft is incomplete or failed validation rules
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetails'
components:
schemas:
LoanApplicationDraft:
type: object
properties:
id:
type: string
example: app_draft_4012
applicantId:
type: string
example: usr_99182
productType:
type: string
example: COMMERCIAL_MORTGAGE
status:
type: string
enum: [DRAFT, SUBMITTED, EXPIRED]
example: DRAFT
currentStep:
type: string
example: BORROWER_DETAILS
completionPercentage:
type: integer
example: 40
borrower:
type: object
properties:
annualIncome:
type: number
example: 185000
employerName:
type: string
example: Apex Technologies LLC
yearsEmployed:
type: integer
example: 5
property:
type: object
properties:
address:
type: string
example: "742 Evergreen Terrace"
estimatedValue:
type: number
example: 750000
propertyType:
type: string
example: COMMERCIAL_RETAIL
requestedLoanAmount:
type: number
example: 500000
createdAt:
type: string
format: date-time
LoanApplicationUpdate:
type: object
properties:
borrower:
type: object
property:
type: object
requestedLoanAmount:
type: number
SubmissionResult:
type: object
properties:
id:
type: string
example: app_draft_4012
status:
type: string
example: SUBMITTED
underwritingCaseId:
type: string
example: case_88391
submittedAt:
type: string
format: date-time
message:
type: string
example: "Loan application successfully submitted for underwriting."
ProblemDetails:
type: object
properties:
type:
type: string
example: "https://api.example.com/errors/incomplete-draft"
title:
type: string
example: "Unprocessable Entity"
status:
type: integer
example: 422
detail:
type: string
example: "Property details are required before submitting the loan application."
22.7. Visualizing Draft Workflow Resource Pattern (Mermaid Diagram)
sequenceDiagram
autonumber
actor Client as Client App (Wizard UI)
participant API as API Server
participant DraftDB as Workflow Draft Store
participant CoreDomain as Underwriting Core Engine
Note over Client,CoreDomain: Step 1: Initiate Workflow Draft
Client->>API: POST /loan-applications { applicantId, productType }
API->>DraftDB: Save initial draft record (status: DRAFT)
DraftDB-->>API: Created app_draft_4012
API-->>Client: 201 Created { id: "app_draft_4012", completionPercentage: 10 }
Note over Client,CoreDomain: Step 2: Progressive Accumulation (Borrower & Property Info)
Client->>API: PATCH /loan-applications/app_draft_4012 { borrower: { income: 185000 } }
API->>DraftDB: Update draft payload & calculate completion %
API-->>Client: 200 OK { completionPercentage: 40 }
Client->>API: PATCH /loan-applications/app_draft_4012 { property: { address: "..." } }
API->>DraftDB: Update draft payload
API-->>Client: 200 OK { completionPercentage: 100 }
Note over Client,CoreDomain: Step 3: Final Completion & Provisioning
Client->>API: POST /loan-applications/app_draft_4012/submit
API->>DraftDB: Retrieve full draft payload
API->>API: Validate complete payload against business rules
alt Validation Failed
API-->>Client: 422 Unprocessable Entity { missingRequiredFields: [...] }
else Validation Passed
API->>CoreDomain: Create UnderwritingCase & Loan records
CoreDomain-->>API: Success (case_88391)
API->>DraftDB: Update draft status to SUBMITTED
API-->>Client: 200 OK { status: "SUBMITTED", underwritingCaseId: "case_88391" }
end