Skip to Content

API & Data

The REST APIs of flow-starter, the in-process FlowClient, the persistence model and the editor contracts, for reference. For how approvals and automation behave, see Approval Flows and Automation.

API Overview

Host applications embedding the engine in-process should integrate through the FlowClient facade (io.softa.starter.flow.api) rather than looping back over REST — actor / initiator / tenant identity is always resolved server-side from the context, never passed as call parameters. FlowAutoConfiguration registers the default InProcessFlowClient only when the application defines no FlowClient bean of its own. See the in-module integration guide under starters/flow-starter/docs/integration-guide.md in the Softa source repository.

Design APIs (flow editor)

Base path: /flow/designs — the dedicated surface for the graphical editor (the full editor API contract lives in the Softa source repository under starters/flow-starter/docs/frontend-editor-api.md). The generic model API at /FlowDesign/** remains available as the platform data plane and is intentionally not intercepted.

MethodPathDescription
POST/flow/designsCreate a draft
GET/flow/designs?keyword=&scenario=&pageNumber=&pageSize=Paged draft list (canvas excluded)
GET/flow/designs/{id}Load one draft (canvas + optimistic-lock version)
POST/flow/designs/{id}/saveAuto-save the canvas; stale versions are rejected
POST/flow/designs/{id}/deleteDelete a draft if it has never been published
POST/flow/designs/{id}/duplicateCopy a draft under a fresh name ("<name> (copy)" when newFlowName is omitted)
POST/flow/designs/validateValidate the posted document; diagnostics returned as a success payload
POST/flow/designs/{id}/publishPublish the saved draft as a new active revision
POST/flow/designs/{id}/restore?bundleId=Restore the draft canvas from a historical bundle snapshot
GET/flow/designs/{id}/statusPublish status: revision badge + dirty flag
GET/flow/designs/{id}/availableVariables?nodeId=Variables visible to a node (expression autocomplete)
POST/flow/designs/{id}/debugRunCompile and run the current draft as a debug bundle (not a sandbox)
GET/flow/designs/{id}/debugRunsDebug-run history, newest first

Bundle APIs (published revisions)

Base path: /flow/bundles

MethodPathDescription
GET/flow/bundles?flowId=Revision list of one flow (or, without flowId, the active bundle of every flow of the caller’s tenant)
GET/flow/bundles/{bundleId}?include=designOne revision’s summary, optionally with the canvas-complete design snapshot
POST/flow/bundles/{bundleId}/activateMake this revision the flow’s effective one (rollback = roll-forward)

Node Descriptor APIs

Base path: /flow/nodeDescriptors

MethodPathDescription
GET/flow/nodeDescriptorsList all node descriptors for the editor palette
GET/flow/nodeDescriptors?scenario=ApprovalFilter descriptors by FlowScenario

Instance APIs

Base path: /flow/instances — launch, reads and approval actions on runtime instances.

MethodPathDescription
POST/flow/instances/startStart an Automation flow whose trigger is Api, by flowId or a pinned bundleId; any other flow is refused
GET/flow/instances/{instanceId}?includeTrace=Get one runtime instance (trace excluded by default)
GET/flow/instances/{instanceId}/overlayPer-node run state for canvas painting
GET/flow/instances/{instanceId}/trace?sinceSequence=Incremental trace rows for polling
POST/flow/instances/searchPaged summaries of the caller’s own record approvals, newest first — the initiator filter is always the authenticated user, and automation runs their writes triggered are left out. See Instance search for the filters and what each row carries
POST/flow/instances/approveApprove a pending approval
POST/flow/instances/rejectReject a pending approval; the comment saying why is required
POST/flow/instances/batch/approveApprove several of the caller’s pending tasks (tasks: instance + step, at most 100), each in its own transaction. Returns FlowBatchActionResult — succeeded / skipped / failed counts and each task’s outcome. See Batch actions and handling steps
POST/flow/instances/batch/returnReturn several pending tasks to their initiators with one comment (required); a step that allows no return is skipped
POST/flow/instances/batch/rejectReject several pending tasks with one comment (required)
POST/flow/instances/transferTransfer a pending approval task
POST/flow/instances/delegateDelegate a pending approval task
POST/flow/instances/add-sign-beforeInsert a prerequisite signer
POST/flow/instances/add-sign-afterInsert a follow-up signer
POST/flow/instances/ccSend a CC notification
POST/flow/instances/cc/readMark a CC as read
POST/flow/instances/returnReturn a pending approval to the initiator, ending the instance as Returned. Only the step’s approver, on a step that allows returns (returnEnabled), with a comment saying what to change
POST/flow/instances/withdrawWithdraw an Approval instance: its initiator, while it waits on its approvers and the flow’s withdrawPolicy permits it (WithdrawPolicyGuard)
GET/flow/instances/{instanceId}/withdrawWhether the caller may withdraw the instance now — true exactly when a withdrawal would be accepted, by the same WithdrawPolicyGuard. Allowed to whoever may view the instance
POST/flow/instances/urgeRemind the approvers the approval waits on — its initiator only. Each waiting step is urged at most once per flow.approval.urge-interval-minutes (default 240), by the initiator or an administrator alike; each step urged gets its own URGE history entry, and when every step is still inside the interval the urge is refused. Returns FlowUrgeStatus, not the instance
GET/flow/instances/{instanceId}/urgeWhether the approval may be urged now, and if not, when (FlowUrgeStatus). Allowed to whoever may view the instance
POST/flow/instances/commentAdd a comment audit entry while the instance runs — its participants only: the initiator and the people it gives or gave a task to. An instance that has ended takes no more comments. Returns true, not the instance
GET/flow/instances/{instanceId}/nodes/{nodeId}/formPermissionsGet field-level form permissions
GET/flow/instances/by-record?modelName=&rowId=A business record’s approval history: its approval instances newest first, with the steps still waiting, the approvers yet to act and the approval actions, every user named (names are resolved only once the caller is allowed); a failed instance says where and why it stopped (failure). Allowed to whoever took part in its approvals or can read the record
GET/flow/instances/preview?modelName=&rowId=What submitting the record would do now, without submitting it: the Submit flow it would get and its approval steps, each with the approvers who would be asked or why it passes on its own; a refusal, no applicable flow or several are reported, a route decided only once the approval runs is reported as undecided. For a record in approval, the rest of its path (InProgress): the step it waits on now, then those after it. Nothing is started or written. Allowed to whoever can read the record (see Previewing a record’s approval)
GET/flow/instances/{instanceId}/recordThe business record a record’s approval runs on — any other instance is refused, since its record was named freely rather than submitted — for its initiator and the people it gives a task to: read past their own row permissions. Every field, dynamic ones included, but the sensitive ones the caller may see on no row; the field permissions of the waiting steps the caller approves are laid over that — what they list as Readonly / Editable / Required shows, sensitive or not, and what they mark Hidden does not. The initiator sees every field but those unseen sensitive ones; anyone else with no waiting step also misses what any approval step hides. Read as the model read API returns a record (relations and options as references, masked fields masked), with each one-to-many’s rows in full — an expense claim comes with its lines

Who may view an instance: its initiator and the people it gives a task to (approvers pending or done, CC recipients, transfer, delegation and add-sign targets) by default; anyone else through the standard role and data-scope configuration — they may read the FlowInstance row with their own permissions, or, for a record’s approval, read the record itself.

The defaults need no grant on the flow models, which no business role holds. The engine’s own bookkeeping — loading and saving instances, the approval ledger, the task and branch projections, delegation and auto-CC rules — runs past the caller’s data permissions: who may act is the action handlers’ call, who may see the access guard’s. The current user’s own lists — inbox counts, pending / completed / CC tasks and the inbox (/flow/approvalTasks), their approval history and sent CCs (/flow/approvalRecords), their delegations, and POST /flow/instances/search, with their FlowClient counterparts — carry @SkipPermissionCheck: pinned to the caller, row scope could only empty them. The per-instance reads (.../instance/{instanceId}, the instance detail endpoints) are not waived, because the guard judges everyone but the initiator and the task holders by their own permissions.

POST /flow/instances/search (the caller’s own approvals) and POST /flow/monitor/instances/search (across initiators, see Monitor) take the same FlowInstanceSearchRequest and return the same rows, newest first, paged by pageNumber (default 1) and pageSize (default 50). Filters combine with AND:

FilterMatches
flowId, status, modelName, rowIdThe instance’s own columns (rowId with modelName)
createdFrom / createdToWhen it started — for a record’s approval, when the record was submitted
initiatorIdThe monitor search only; the caller’s own search always pins it to them
subjectId, subjectDeptIdA record’s approval by the employee it is about, or by that employee’s department, as at submission
effectiveDateFrom / effectiveDateToA record’s approval by the date the record takes effect
recordCodeA record’s approval by the record’s own number (contains)
awaitingRevisionApprovals returned to their submitter that no later submission of the record replaced — records still waiting to be edited and submitted again

A row (FlowInstanceSummaryView) leaves out the heavy state columns and the trace — fetch the instance or its overlay for those. It carries the flow, title, initiator, status and the steps the instance waits on now (currentSteps, each with the approvers still to act), every user named, and for a record’s approval what its submission recorded (Approving Business Records): subject, subjectDept, effectiveDate, recordCode, and awaitingRevision. On the caller’s own approvals that wait on their approvers, withdrawable and urge say whether they may withdraw or urge them now. recordDeleted marks an approval whose record is gone — a draft deleted after a return or a withdrawal — and effectStatus says where an approved record’s change stands, for a model whose approval takes effect later (ModelApprovalHandler.effectStatuses).

Automation APIs

Base path: /flow/automation — trigger firing and transient runs; none of these reads or acts on an existing instance.

MethodPathDescription
POST/flow/automation/triggerFire a trigger event and start matching flows. Submit, Cron, Api and Subflow events are refused: those flows start by record submission, on their own schedule, by id, or from a parent flow
POST/flow/automation/onchangeRun COMPUTE flows transiently and return variable diffs
POST/flow/automation/validateRun VALIDATION flows transiently against candidate row data; returns each flow’s declared outputs keyed by flowId

Async-task and timer resumes have no REST endpoint: FlowAsyncTaskConsumer, FlowTimerConsumer and the due-timer sweep of the maintenance cron call FlowRuntimeEngine in-process.

actorId / initiatorId / tenantId are never sent by clients: every action request stamps them server-side from the login context (@JsonProperty(READ_ONLY)), and all query endpoints resolve the current actor from the context. The in-process FlowClient resolves the actor the same way and calls the same services as these endpoints.

Approval Projection APIs

Approval Tasks

Base path: /flow/approvalTasks

MethodPathDescription
GET/flow/approvalTasks/countsInbox badge counts for the current actor ({pendingApprovals, unreadCc} — same filter definitions as the paged /pending and /cc?read=false queries)
GET/flow/approvalTasks/pending?flowId=&instanceId=&nodeId=&pageNumber=&pageSize=Pending tasks for the current actor, newest first (paged: returns Page)
GET/flow/approvalTasks/completed?flowId=&instanceId=&nodeId=&pageNumber=&pageSize=Completed tasks for the current actor (paged)
GET/flow/approvalTasks/cc?read=&flowId=&instanceId=&nodeId=&pageNumber=&pageSize=CC tasks for the current actor (paged; read=false unread, read=true acknowledged, omit read for both)
GET/flow/approvalTasks/inbox?flowId=&instanceId=&nodeId=&includeCompletedApprovals=&pageNumber=&pageSize=Unified inbox view (pending approvals + CC; optional completed approvals)
GET/flow/approvalTasks/instance/{instanceId}All tasks for one runtime instance (participants / initiator only)

Each task (FlowApprovalTaskView) comes with its instance’s context — instanceTitle, modelName / rowId, instanceStatus, the initiator and submittedTime, and what a record’s approval recorded of its record (subject, subjectDept, effectiveDate, recordCode), every user, employee and department named — and with what its step’s configuration says about acting on it, read from the revision the instance runs on: handling (a handling step), returnAllowed (the step’s returnEnabled) and batchable (open to its approver now — pending, not blocked by an add-sign, the instance waiting — on a step that is not a handling step and makes no field required). See Batch actions and handling steps.

Approval Records

Base path: /flow/approvalRecords

MethodPathDescription
GET/flow/approvalRecords/instance/{instanceId}Approval history by runtime instance
GET/flow/approvalRecords/historyActor-scoped approval history
GET/flow/approvalRecords/cc/sentSender-side CC history

An entry (FlowApprovalRecord, FlowApprovalRecordView) records its action (ApprovalActionType: APPROVE, AUTO_APPROVE, REJECT, TRANSFER, DELEGATE, ADD_SIGN, CC, READ, RETURN, WITHDRAW, URGE, COMMENT, TERMINATE), its actorId and, for an action done on the actor’s behalf, operatorId — the administrator who reassigned the actor’s task (a TRANSFER entry from the approver to the new one); empty when the actor acted themselves.

CC Config

Base path: /flow/ccConfigs

MethodPathDescription
POST/flow/ccConfigsCreate a CC rule
GET/flow/ccConfigs?flowId=List CC rules by flowId
POST/flow/ccConfigs/{id}/deactivateDeactivate a CC rule

A rule (FlowCcConfig) CCs its recipients at its timing:

  • OnSubmit: when the flow starts.
  • OnApprove / OnReject: after an approver approves or rejects a step.
  • OnComplete: when the approval completes approved.

A rule without a nodeId is flow-level and fires at every such moment. A rule naming a step fires at OnApprove / OnReject of that step only. recipientType INITIATOR CCs the initiator; any other type reads recipientConfig as comma-separated user ids. An optional ccCondition is evaluated over the instance variables.

An automatic CC is recorded like a manual one: one CC entry per recipient in the approval history. The entry has no actor, since no person sent it. It sits on the node the rule fired at — the step acted on, or the start node at OnSubmit (and at OnComplete when every step passed on its own) — in that node’s current cycle. So the recipient:

  • has a CC task in the inbox, which they mark read;
  • is a participant of the instance: they open it and comment on it.

A recipient already CC’d on the same node and cycle, by a rule or by hand, is not CC’d again, so a step that several approvers approve CCs once. sendNotification additionally notifies the recipients CC’d that time.

Delegation

Base path: /flow/delegations

MethodPathDescription
POST/flow/delegationsCreate a delegation rule
GET/flow/delegations/myDelegations created by the caller (delegator resolved from the login context)
GET/flow/delegations/to-meActive delegations assigned to the caller (delegate resolved from the login context)
POST/flow/delegations/{id}/cancelCancel one of the caller’s delegation rules; another user’s is refused (403)

Monitor

Base path: /flow/monitor — the administrators’ console. No role is built in: each endpoint is granted like any other, and what the console reads or acts on is the caller’s own permissions on FlowInstance — their roles’ grant and data scope; taking part in an instance is not enough. A department scope (DEPT_SUBTREE, MANAGED_DEPARTMENTS) narrows by FlowInstance.departmentId, the subject’s department at submission — a plain department-id column the permission-starter scopes filter by the ids of the departments in range — so a role scoped to the departments it manages sees and administers their approvals; an automation run belongs to no department. Opening another user’s instance (detail / overlay / trace / approval history) follows the viewing rule under Instance APIs, as for any viewer who is not a participant.

MethodPathDescription
GET/flow/monitor/healthFlow runtime health snapshot (per-status instance counts, overdue timers)
POST/flow/monitor/instances/searchCross-initiator paged instance summaries, read with the caller’s own permissions: the FlowInstance rows their data scope reaches. Honors the request’s initiatorId filter when present; otherwise as Instance search
POST/flow/monitor/instances/urgeUrge an approval as an administrator rather than its initiator: whoever may read the instance’s FlowInstance row with their own permissions. The initiator’s interval applies — a step urged within it, by anyone, is not urged again. Returns FlowUrgeStatus
POST/flow/monitor/instances/terminateEnd each instance named (instanceIds, at most 100) that is running, waiting or failed, with one reason (required): its pending approvals close, it ends Cancelled — a record’s approval cancels its record — and the history records a TERMINATE entry with the reason, which the initiator is sent. Only instances the caller’s permissions read; each in its own transaction. Returns FlowBatchActionResult
POST/flow/monitor/approvers/reassign/previewHow many of an approver’s pending approval tasks (fromActorId → toActorId), on the instances the caller’s permissions read, a reassignment would move: FlowReassignPreview (tasks, instances)
POST/flow/monitor/approvers/reassignMove each of those tasks to the new approver, with a reason (required), each in its own transaction: recorded as the approver’s TRANSFER with the caller as operatorId; the new approver is given the task, the initiator is told who now holds it. Only the steps the instances wait on now move — a step not reached yet resolves its approvers when reached; CC tasks stay. Returns FlowBatchActionResult
POST/flow/monitor/delegationsCreate a delegation rule on an approver’s behalf (the body names the delegatorId and the delegateId): their tasks arriving from then on go to the delegate, and the approver sees the rule among theirs. It affects all of the approver’s tasks whatever the caller’s data scope, so it is a permission of its own
GET/flow/monitor/delegationsEvery delegation rule of the tenant, newest first
POST/flow/monitor/delegations/{id}/cancelDeactivate any delegation rule of the tenant

Event Logs

Base path: /flow/events

MethodPathDescription
GET/flow/events?flowId=&sourceModel=&sourceRowId=&instanceId=&success=&eventTimeFrom=&eventTimeTo=&pageNumber=&pageSize=Paged event log, newest first (filters combine with AND; list rows exclude the parameters payload)
GET/flow/events/{id}Single event including the raw trigger-parameters payload

Persistence Model

EntityDescription
FlowDesignDraft working copy
FlowBundlePublished compiled bundle
FlowInstancePersisted runtime instance state (trace and approval history are NOT embedded — see the two dedicated tables), with what a record’s approval recorded at submission: subjectId, departmentId, effectiveDate, recordCode, superseded. Approval instances are kept; finished Automation runs, with their trace, branches and the trigger event log, are purged after flow.automation.retention-days (default 30) by the flow_automation_purge cron
FlowExecutionTraceAppend-only execution trace rows (the in-memory state keeps only the current attempt’s delta)
FlowApprovalTaskPending/completed/CC task projections
FlowApprovalRecordApproval action audit ledger — the authoritative approval history, flushed by the instance store in the same transaction as the instance row; operatorId names an administrator who acted on the actor’s behalf
FlowCcConfigAutomatic CC rules
FlowDelegationDelegation rules
FlowEventTrigger event logs
FlowDebugHistoryDebug snapshots
FlowParallelBranchParallel branch tracking

Primary storage adapters in this module:

  • OrmFlowBundleRegistry
  • OrmFlowInstanceStore

OrmFlowBundleRegistry loads nothing at startup and caches per tenant:

  • Trigger matching reads an index of the tenant’s active bundles, loaded in that tenant’s own context on its first event (an empty result is kept too) and merged with the platform-shared bundles (tenantId = 0); a context without a tenant matches the shared ones only. Entries live for flow.trigger-cache-ttl-seconds (default 60). Publishing or activating a revision drops the tenant’s entry at once on the instance that did it; other instances pick the change up within the TTL. Change logs of models no flow watches are not turned into trigger events.
  • Definitions are cached by bundleId in a bounded LRU (a published revision never changes). A bundle of another tenant reads as not found, whether cached or not; platform-shared bundles are visible to every tenant.
  • The active revision of a flow is read from the database on every lookup, so starting by flowId follows a publish or rollback made on any instance.

Node Descriptor Contract

The editor palette is built from FlowNodeDescriptor records exposed through /flow/nodeDescriptors.

  • structural descriptors come from BuiltinNodeDescriptorProvider
  • executor-backed task descriptors come from TaskExecutorDescriptorProvider
  • descriptors include label, icon, sort order, config schema, default config, and allowed scenarios

TaskExecutorDescriptorProvider only exposes task nodes for executors that are actually registered as Spring beans, so conditional executors such as QUERY_AI and ASYNC_TASK only appear when their dependencies are present.

Frontend Graph Compatibility

Design graph DTOs remain aligned with xyflow/react style structures:

xyflow/reactflow-starter
Node.idFlowGraphNode.id
Node.typeFlowGraphNode.type
Node.positionFlowGraphNode.position
Node.width / heightFlowGraphNode.width / height
Node.dataFlowGraphNode.data
Edge.idFlowGraphEdge.id
Edge.source / targetFlowGraphEdge.source / target
Edge.sourceHandle / targetHandleFlowGraphEdge.sourceHandle / targetHandle
Edge.typeFlowGraphEdge.type
Edge.labelFlowGraphEdge.label
Edge.dataFlowGraphEdge.data
Viewport {x, y, zoom}FlowGraphViewport {x, y, zoom}
Last updated on