Part 2 — FormManager (FM)
Table of Contents
FM Proxy Table
In addition to its own direct endpoints, FM acts as a transparent proxy for all workflow-related requests. The following WFM endpoints are accessed through FM — it forwards the requests to WFM, adding correlation headers and enforcing timeouts. Default proxy timeout is 60 seconds unless noted otherwise.
| FM Proxy Path | WFM Target | Method | Timeout |
|---|---|---|---|
/context | /context | POST | 60s |
/context/activity | /context/activity | POST | 60s |
/action/complete | /action/complete | POST | 60s |
/action/save | /action/save | POST | 60s |
/action/retry | /action/retry | POST | 60s |
/action/function | /action/function | POST | 60s |
/action/start | /process/start | POST | 60s |
/process/start | /process/start | POST | 60s |
/process/:processInstanceId | /process/:processInstanceId | GET | 60s |
/task/:taskId/run/:functionId | /task/:taskId/run/:functionId | POST | 60s |
/task/:taskId/commit | /task/:taskId/commit | POST | 60s |
/note/add | /note/add | POST | 60s |
/note/update | /note/update | POST | 60s |
/note/delete | /note/delete | POST | 60s |
/file/upload | /file/upload | POST | 300s |
/file/download | /file/download | POST | 300s |
/file/delete | /file/delete | POST | 300s |
/notifications/:appId | /notifications/:appId | GET | 60s |
/notify/send | /notify/send | POST | 60s |
/query/activeProcessIds | /query/activeProcessIds | POST | 60s |
/query/processIdsUpdatedBetween | /query/processIdsUpdatedBetween | POST | 60s |
/query/processInstanceContextAndActivity | /query/processInstanceContextAndActivity | POST | 60s |
/getConstantValue | /getConstantValue | POST | 60s |
/appclose | /appclose | POST | 60s |
/external/task/:taskId/action/:actionType | /external/task/:taskId/action/:actionType | POST | 60s |
/external/task/:taskId/assignToUser | /external/task/:taskId/assignToUser | POST | 60s |
/external/task/:taskId/assignToSwimlane | /external/task/:taskId/assignToSwimlane | POST | 60s |
/external/function/:functionId/execute | /external/function/:functionId/execute | POST | 60s |
/external/process/start | /external/process/start | POST | 60s |
/external/process/changeStep | /external/process/changeStep | POST | 60s |
/external/data/search/:collection | /external/data/search/:collection | POST | 60s |
/external/locks/:userId | /external/locks/:userId | DELETE | 60s |
/external/batchjob/retry | /external/batchjob/retry | POST | 60s |
For details on each of these WFM endpoints, see Part 3 — WorkflowManager.
FM Direct Endpoints
The following endpoints are served directly by FormManager and do not proxy to WorkflowManager.
User Management
POST /user/createUser
Creates a new workflow user in the system. The server generates a unique userId via UUID and stores the user record. A case-insensitive duplicate check is performed on fullName to ensure uniqueness across all users in the system.
Request Body:
{
firstName: string;
lastName: string;
fullName: string; // Must be unique (case-insensitive)
unit: { id: string; name: string };
roles: string[]; // At least one role required
email?: string;
swimlanes?: string[]; // Swimlane assignments for task routing
isSystemUser?: boolean; // Defaults to false
organizationId: string; // Auto-injected
appId: string; // Auto-injected
}
| Field | Required | Description |
|---|---|---|
fullName | Yes | Display name for the user. Must be unique across all users (case-insensitive check). |
unit | Yes | The organizational unit the user belongs to, containing an id and a name. |
roles | Yes | Array of role identifiers assigned to the user. At least one role must be provided. |
email | No | User's email address. |
swimlanes | No | List of swimlane names this user can participate in. Used for task routing — when a task is assigned to a swimlane, only users with that swimlane can pick it up. |
isSystemUser | No | Whether this is a system/service account rather than a human user. |
Response: IUser — the created user object including the server-generated userId.
Errors:
400:fullNameis missing →"Name is mandatory!"400:unitis missing →"Unit is mandatory!"400:rolesis empty →"User role is mandatory!"400: DuplicatefullName→"Username is already taken."
POST /user/updateUser
Updates an existing workflow user. The user is located by userId and the entire document is replaced with the provided fields. Unlike createUser, this endpoint does not perform uniqueness checks on fullName or validate required fields — it trusts the caller to provide a complete, valid user object.
Request Body: Same structure as createUser. The userId field is used as the lookup key.
Response: IUser — the updated user object.
Errors:
404: User not found byuserId→"User not found!"
DELETE /user/deleteUser
Permanently deletes a workflow user from the system. This is a hard delete — there is no soft-delete or recycle mechanism. The user record is removed entirely.
Request Body:
{ id: string } // The userId of the user to delete
Response: { message: "User deleted successfully" }
Errors:
404: No user found with the given ID →"User not found!"
GET /user/getUserById/:userId
Retrieves a single user by their unique identifier. Returns the full user object or null if no user exists with the given ID (no explicit 404 is thrown).
Path Params:
| Param | Type | Description |
|---|---|---|
userId | string | The unique identifier of the user. |
Response: IUser or null
GET /user/listWorkflowUsers
Returns all workflow users for the current organization and application. The organizationId and appId are automatically injected into the request by the FM client and used as filters to scope the result to the current tenant and app.
Response: IUser[] — array of all matching user objects.
Model Management
POST /model/insertModel
Upserts a workflow model into the database. If a model with the same ID already exists, it is updated; otherwise a new model is created. This endpoint is the primary way workflow definitions are persisted from the Studio editor.
For models of type "jobScheduler", an additional deploy task is automatically created in the batch job system with status "waiting", which will be picked up by the batch job processor for deployment.
Request Body:
{
model: IModelForWorkflow; // The full model object containing the workflow definition
live: boolean; // true = production deployment, false = draft/preview
}
| Field | Required | Description |
|---|---|---|
model | Yes | The workflow model object containing modelID, type, appId, modelBody, and other metadata. |
live | Yes | Indicates whether this is a live production deployment or a draft save. |
Response: string — the upsert operation result.
GET /model/getModel/:id
Fetches a workflow model by its unique identifier. This endpoint implements HTTP conditional caching via ETags to optimize bandwidth. If the client sends an If-None-Match header matching the current model's ETag, a 304 Not Modified response is returned with no body.
The ETag is a composite value built from the model ID, version, update date, and byte length, ensuring it changes whenever the model content is modified.
Path Params:
| Param | Type | Description |
|---|---|---|
id | string | The unique model identifier. |
Response Headers:
| Header | Value | Description |
|---|---|---|
Cache-Control | public, max-age=28800 | 8-hour browser cache lifetime. |
ETag | "<modelID>-<version>-<updateDate>-<byteLength>" | Composite ETag for conditional requests. |
Vary | Accept-Encoding | Indicates response varies by encoding. |
Response: The model body content (structure varies by model type).
Errors:
404(fm3027): Model not found in the database.
Health & Monitoring
GET /healthcheck
Returns the health status of the FormManager service. Reports the service name, package version, last modification date, and the current server time. If version information cannot be read (e.g., missing package.json), the error is captured in the response body rather than thrown — this ensures the health check endpoint itself always remains available.
Response:
{
name: "formManager";
version: string; // e.g., "1.2.3"
modify: Date; // Last modification date of the package
time: Date; // Current server time
error?: string; // Present only if version info could not be read
}
GET /liveness
Minimal liveness probe. Returns only the current server timestamp, confirming the FM process is alive and can handle HTTP requests. Unlike /healthcheck, this endpoint does not check any dependencies (database, filesystem, etc.), making it suitable for Kubernetes liveness probes that should only verify process responsiveness.
Response: { time: Date }
GET /azure/serviceLog/:service
Streams real-time Azure App Service logs via Server-Sent Events (SSE). Acts as a filtered proxy to Azure's Kudu log streaming API. Incoming log lines are filtered by a [CI: <clientId>] pattern — only lines matching the caller's lpLogClientId are forwarded to the client. Azure timestamps are stripped from the output for cleaner display.
A heartbeat mechanism sends empty space characters every ~15 seconds to keep the SSE connection alive during quiet periods when no log lines match.
Path Params:
| Param | Type | Description |
|---|---|---|
service | "fm" | "wfm" | Which service's logs to stream. |
Required Headers:
| Header | Description |
|---|---|
lplogclientid | The client identifier used to filter log lines. |
studiotoken | Authentication token for the request. |
Response Headers:
| Header | Value |
|---|---|
Content-Type | text/event-stream |
Cache-Control | no-cache |
Connection | keep-alive |
Response: A continuous SSE stream of filtered log lines.
Errors:
400: Missinglplogclientidorserviceparameter.500: Missing Azure deployment environment variables.
Settings & Cache
GET /settings/:file
Serves UI settings files (JavaScript) for a given application. The appId is resolved from the request's Referer header through a 3-step fallback chain:
- Reads
appIddirectly from the referer URL's query parameters. - If not found, reads
processIdfrom referer query params and looks up theappIdfrom the model database. - If still not found, reads the
qquery param and extractsappIdfrom an encoded composite ID string.
Path Params:
| Param | Type | Description |
|---|---|---|
file | UISettingsFileType | The settings file identifier. |
Response: JavaScript content served with Content-Type: application/javascript; charset=UTF-8.
Errors:
404(fm3027):appIdcould not be resolved from the referer, or the settings model was not found.
GET /external/cache/:cacheType/:cacheTarget/evict
Invalidates cache entries across the system. Inserts a cache invalidation record into the database, which FM and WFM instances poll or react to for clearing their respective caches. This is a coordination-based eviction mechanism — the invalidation is not immediate but propagates through the shared record.
Path Params:
| Param | Type | Description |
|---|---|---|
cacheType | string | The type of cache to invalidate. |
cacheTarget | "fm" | "wfm" | "all" | Which service(s) should respond to the invalidation. |
Allowed cacheType values per target:
| Target | Allowed Cache Types |
|---|---|
fm | app, all, swagger, endpoint |
wfm | app, all, service |
all | app, all, swagger, service |
Query Params:
| Param | Type | Required | Description |
|---|---|---|---|
id | string | No | Scope invalidation to a specific cache key. Only valid for service and swagger cache types. If omitted, all entries of the given type are invalidated. |
Response: { status: "OK" }
Errors:
400: InvalidcacheTargetvalue, orcacheTypenot allowed for the given target.
Notifications (FM)
POST /notify
Upserts a notification record in the database. Uses a partial update mechanism — the notification object is flattened into dot-notation keys so that only the provided fields are updated while preserving any existing fields in the document. This allows incremental notification updates without overwriting the entire record.
The notification is keyed by processInstance.processInstanceId, meaning each process instance has exactly one notification record that is continuously updated as the process progresses.
Request Body:
{
notificationObject: {
eventType: NotificationEventTypes;
informList: Array<{
key?: string;
label?: string;
header: string;
display?: boolean;
}>;
processInstance: IProcessInstance; // Must contain processInstanceId
onUs: boolean;
task?: ITask; // Present when onUs = true
}
}
Response: { message: "notified" }
Errors:
400(wm3019): MissingprocessInstance.processInstanceId.