Skip to main content

RPC Resources

When the approval module is enabled, the framework registers the six RPC resources below. All of them are mounted under /api, using the standard envelope (resource, action, version, params, meta) documented in API. None of the operations are public: callers must be authenticated, and permissions are enforced wherever a RequiredPermission is listed. The generated Runtime API Index contains the exhaustive JSON field ledger for every request and response DTO.

Conventions used on this page:

  • Command-style operations (approval/flow, approval/instance, approval/my, approval/admin) declare params structs embedding api.P, so their fields decode from the request's params object.
  • CRUD read operations (approval/category, approval/delegation) declare search structs embedding crud.Sortable — a meta struct — so their filter fields decode from the request's meta object, next to meta.page / meta.size (page.Pageable) and meta.sort.
  • Paged responses use page.Page[T]: page, size, total, items.
  • Persisted models carry the standard audited-model columns (id, createdAt, createdBy, updatedAt, updatedBy) in responses; they are omitted from the field tables below.
  • Enum vocabularies (InstanceStatus, TaskStatus, node semantics) are defined in Instance Runtime and Flow Design.

approval/category

Flow category management (apv_flow_category). Reads are tenant-scoped: super-admin callers see all tenants, everyone else is confined to their own tenant and fails closed without one.

ActionPermissionInputOutput
find_treeapproval.category.queryCategorySearch (meta)nested FlowCategory[] (children populated)
createapproval.category.createCategoryParamscreated FlowCategory
updateapproval.category.updateCategoryParamsupdated FlowCategory
deleteapproval.category.deleteprimary-key params (params.id)success

There is no find_tree_options operation; build option lists from find_tree instead.

CategorySearch (query filters, decoded from meta):

FieldTypeMatchDescription
namestringcontainsfilter by category name fragment
isActiveboolequalsfilter by active flag; omit to match both
sortOrderSpec[]sort specifications (crud.Sortable)

CategoryParams (create/update, decoded from params):

FieldTypeRequiredDescription
idstringupdate onlyprimary key of the row to update
tenantIdstringYesowning tenant. On create, non-super-admin callers have this stamped from their own tenant (the submitted value is ignored); on update/delete the caller must be authorized for the row's tenant
codestringYescategory business code
namestringYesdisplay name
iconstringNodisplay icon identifier
parentIdstringNoparent category id; null makes it a root
sortOrderintNoordering weight among siblings
isActiveboolNoinactive categories stay queryable but hosts typically hide them from pickers
remarkstringNofree-text remark

FlowCategory (response model):

FieldTypeDescription
tenantIdstringowning tenant
codestringcategory business code
namestringdisplay name
iconstring | nulldisplay icon identifier
parentIdstring | nullparent category id
sortOrderintordering weight
isActiveboolactive flag
remarkstring | nullfree-text remark
childrenFlowCategory[]child categories; populated by find_tree, absent elsewhere

approval/delegation

Approval delegation management (apv_delegation). Ownership is enforced: non-super-admin callers only see, create, update, and delete delegations where they are the delegator — on create the delegatorId is stamped from the caller, and on update the original delegator is pinned so the record cannot be reassigned to another user.

ActionPermissionInputOutput
find_pageapproval.delegation.queryDelegationSearch + pageable metapage.Page[Delegation]
createapproval.delegation.createDelegationParamscreated Delegation
updateapproval.delegation.updateDelegationParamsupdated Delegation
deleteapproval.delegation.deleteprimary-key params (params.id)success

DelegationSearch (query filters, decoded from meta):

FieldTypeMatchDescription
delegatorIdstringequalsfilter by delegator (super-admin only — others are always scoped to themselves)
delegateeIdstringequalsfilter by delegatee
isActiveboolequalsfilter by active flag
sortOrderSpec[]sort specifications

DelegationParams (create/update, decoded from params):

FieldTypeRequiredDescription
idstringupdate onlyprimary key of the row to update
delegatorIdstringYesuser delegating their tasks. Non-super-admin callers have this stamped from the principal on create and pinned to the original value on update
delegateeIdstringYesuser receiving the delegated tasks
flowCategoryIdstringNorestrict the delegation to one flow category; null covers all categories
flowIdstringNorestrict the delegation to one flow; null covers all flows
startsAtDateTimeYesdelegation window start
endsAtDateTimeYesdelegation window end
isActiveboolNoinactive delegations are ignored by assignee resolution
reasonstringNofree-text reason shown in delegated tasks

Delegation (response model): same business fields as the params — delegatorId, delegateeId, flowCategoryId, flowId, startsAt, endsAt, isActive, reason — plus the audited-model columns. Tasks that arrive via delegation carry the delegator as a separate person snapshot (see NodeParticipant.delegator below).

approval/flow

Flow definition management: the mutable flow row, its immutable deployed versions, and the designer-facing graph reads.

ActionPermissionInputOutputAudit
createapproval.flow.createCreateFlowParamscreated FlowYes
deployapproval.flow.deployDeployFlowParamscreated FlowVersion (draft)Yes
publish_versionapproval.flow.publishPublishVersionParamssuccessYes
updateapproval.flow.updateUpdateFlowParamsupdated FlowYes
toggle_activeapproval.flow.updateToggleActiveParamssuccessYes
get_graphapproval.flow.queryGetGraphParamsFlowGraph
find_flowsapproval.flow.queryFindFlowsParamspage.Page[Flow]
find_versionsapproval.flow.queryFindVersionsParamsFlowVersionSummary[]
find_initiatorsapproval.flow.queryFindInitiatorsParamsFlowInitiator[]

CreateFlowParams (create):

FieldTypeRequiredDescription
tenantIdstringYesowning tenant; empty coalesces to "default", and the caller must be authorized for the resulting tenant
codestringYesunique flow business code; immutable after creation (start targets it)
namestringYesdisplay name
categoryIdstringYesowning FlowCategory id
iconstringNodisplay icon identifier
descriptionstringNofree-text description
labelsobject (string→string)Nohost-owned selection metadata; validated by the shared label rule — see the note below
bindingModestringYesstandalone (form data lives in approval tables) or business (links an existing business row)
businessBindingBusinessBindingConfigbusiness modewrite-back target description (below); rejected on standalone flows (ErrBindingUnexpected)
adminUserIdsstring[]Noflow administrators (used by transfer_admin empty-assignee handling and admin visibility)
isAllInitiationAllowedboolNotrue lets every user initiate; false restricts initiation to initiators
instanceTitleTemplatestringNoGo text/template for instance titles, e.g. {{.applicantName}}的请假申请; bindings: flowName, flowCode, instanceNo, formData, applicantId, applicantName (plus nested flow.name / flow.code, applicant.id / applicant.name). Empty falls back to flowName-instanceNo; parse failure raises ErrInvalidTitleTemplate
initiatorsCreateInitiatorParams[]Nowho may initiate when isAllInitiationAllowed is false (below)

CreateInitiatorParams entries:

FieldTypeRequiredDescription
kindstringYesuser, role, or department
idsstring[]Yesids of the selected users / roles / departments

BusinessBindingConfig:

FieldTypeRequiredDescription
tableNamestringYesbusiness table receiving approval state; validated as a SQL-safe identifier (ErrInvalidBusinessIdentifier) and checked to exist (ErrBindingSchemaInvalid)
keyColumnsstring[]Yescolumns locating the bound row; must exactly match a non-null primary or unique key (ErrBindingKeyNotUnique)
statusColumnstringYescolumn receiving the (mapped) instance status
instanceIdColumnstringYescolumn receiving the owning instance id; used as a compare-and-set fence so a stale instance cannot overwrite a newer approval round
startedAtColumnstringNocolumn receiving the instance start time
finishedAtColumnstringNocolumn receiving the instance finish time
statusMappingobject (InstanceStatus→string)Notranslates instance statuses into host vocabulary; missing entries fall back to the status string itself (ErrBindingStatusMappingInvalid for unknown keys or blank values)

Two binding fields naming the same column fail with ErrBindingColumnsConflict. Deployed versions snapshot their binding, so editing a flow's binding never affects instances already running under earlier versions. See Integration for the write-back lifecycle.

params.labels is host-owned selection metadata on the flow — equality-filterable in find_flows and my.find_available_flows (every submitted pair must match), surfaced in instance detail views, and never interpreted by the engine. Validation is the shared orm.ValidateLabels rule: alphanumeric keys with inner -/_ (no dots), ≤ 63 characters; values ≤ 256 characters, empty values legal. On update, labels are replaced wholesale — omitting them clears the flow's labels.

DeployFlowParams (deploy — creates a new draft version):

FieldTypeRequiredDescription
flowIdstringYesflow to deploy under
descriptionstringNoversion description shown in version lists
storageModestringNojson (default; form data stays in apv_instance.form_data) or table (a dedicated physical projection table is generated at publish)
flowDefinitionFlowDefinitionYesthe designer graph document — nodes, edges, and per-node data; validated at deploy (ErrInvalidFlowDesign). See Flow Design for the wire shape
formSchemaJSON documentNohost-owned form designer document, passed through opaque and stored verbatim; the flat field list the engine consumes is derived from it at deploy (see Form Schema and Derived Fields)

PublishVersionParams (publish_version — makes a draft the live version and archives the previous one):

FieldTypeRequiredDescription
versionIdstringYesdraft version to publish (ErrVersionNotDraft otherwise)

UpdateFlowParams (update — mutates the flow row only; deployed versions are immutable):

FieldTypeRequiredDescription
flowIdstringYesflow to update
namestringYesdisplay name
iconstringNodisplay icon identifier
descriptionstringNofree-text description
labelsobject (string→string)Noreplaced wholesale; omitting clears
bindingModestringYesbinding mode (see create); changing it only affects future deployments
businessBindingBusinessBindingConfigbusiness modewrite-back target (see create)
adminUserIdsstring[]Noflow administrators
isAllInitiationAllowedboolNoinitiation openness
instanceTitleTemplatestringYesinstance title template
initiatorsCreateInitiatorParams[]Noinitiator configuration; replaced wholesale

ToggleActiveParams (toggle_active):

FieldTypeRequiredDescription
flowIdstringYesflow to toggle
isActiveboolNotarget state; inactive flows refuse initiation (ErrFlowNotActive) while running instances continue

GetGraphParams (get_graph):

FieldTypeRequiredDescription
flowIdstringYesflow whose graph to load
tenantIdstringNooptional pre-filter; the actual cross-tenant gate is the caller's tenant authority
versionIdstringNoexplicit version to load — a designer resuming from the newest deployment, published or not; omitted resolves the latest published version

get_graph responds with a FlowGraph:

FieldTypeDescription
flowFlowthe mutable flow row (fields below)
versionFlowVersionthe resolved version, including flowSchema (the deployed FlowDefinition), formSchema (host document, verbatim), and formFields (the derived flat field list)
nodesFlowNode[]persisted node rows of that version — one row per node with all resolved node configuration (kind, execution type, approval method, pass rule, rollback / add-assignee / CC toggles, timeout config, branches). See Flow Design for each field's semantics
edgesFlowEdge[]persisted edge rows: key, sourceNodeId / sourceNodeKey, targetNodeId / targetNodeKey, sourceHandle (condition-branch anchor)

FindFlowsParams (find_flows):

FieldTypeRequiredDescription
tenantIdstringNotenant filter; non-super-admin callers are constrained to their own tenant regardless
categoryIdstringNofilter by category
keywordstringNocontains match against the flow name
isActiveboolNofilter by active flag
labelsobject (string→string)Nolabel equality filter — every submitted pair must match
bindingModestringNostandalone or business; filter by binding mode
pageintNopage number (1-based)
pageSizeintNopage size

find_flows responds with page.Page[Flow]. Flow (response model):

FieldTypeDescription
tenantIdstringowning tenant
categoryIdstringowning category
codestringunique flow business code (immutable)
namestringdisplay name
iconstring | nulldisplay icon identifier
descriptionstring | nullfree-text description
labelsobject | absenthost-owned selection metadata
bindingModestringstandalone or business
businessBindingBusinessBindingConfig | absentcurrent write-back configuration (mutable copy; versions snapshot their own)
adminUserIdsstring[]flow administrators
isAllInitiationAllowedboolinitiation openness
instanceTitleTemplatestringinstance title template
isActiveboolactive flag
currentVersionintlatest published version number; 0 before the first publish

FindVersionsParams / FindInitiatorsParams (find_versions, find_initiators):

FieldTypeRequiredDescription
flowIdstringYesflow to inspect
tenantIdstringNooptional pre-filter (cross-tenant gate is the caller's authority)

find_versions returns FlowVersionSummary entries — the version list without the graph documents (flowSchema / formSchema / formFields), which a list never renders. Fetch one version's full definition through get_graph with params.versionId.

FlowVersionSummary fieldTypeDescription
idstringversion id
flowIdstringowning flow
versionintmonotonically increasing version number
statusstringdraft, published, or archived
descriptionstring | nullversion description
storageModestringjson or table
publishedAtDateTime | nullpublish time
publishedBystring | nullpublisher user id
createdAtDateTimedeploy time
createdBystringdeployer user id

find_initiators returns FlowInitiator[]: each entry carries flowId, kind (user / role / department), and ids (the configured id list).

approval/instance

Instance lifecycle commands. Every state change is recorded in the action log; the operations marked audited additionally capture framework-level IP / UA / request-id audit entries.

ActionPermissionInputOutputAudit
startapproval.instance.startStartParamscreated InstanceYes
process_taskapproval.task.processProcessTaskParamssuccessYes
withdrawapproval.instance.withdrawWithdrawParamssuccessYes
resubmitapproval.instance.resubmitResubmitParamssuccessYes
add_ccapproval.instance.ccAddCCParamssuccessYes
mark_cc_readapproval.instance.ccMarkCCReadParamssuccess
add_assigneeapproval.task.add_assigneeAddAssigneeParamssuccessYes
remove_assigneeapproval.task.remove_assigneeRemoveAssigneeParamssuccessYes
urge_taskapproval.task.urgeUrgeTaskParamssuccessrate-limited: max 10 per 1m

process_task deliberately bundles approve / reject / transfer / rollback / handle under one permission (approval.task.process): the designer's node-level toggles (isTransferAllowed, isRollbackAllowed, …) already govern which actions a node offers at runtime.

StartParams (start):

FieldTypeRequiredDescription
tenantIdstringYestenant to start under; empty coalesces to "default", and the caller must be authorized for the flow's tenant
flowCodestringYesbusiness code of the flow to start; resolves the latest published version (ErrFlowNotFound / ErrFlowNotActive / ErrNoPublishedVersion)
businessRefstring (≤ 512)business modeopaque reference to the bound business row; required on business-bound flows unless a registered BusinessRefProvider supplies it (ErrBusinessRefRequired). Default shapes: single key verbatim, composite key as a JSON object
formDataobjectNoform values keyed by field key; validated against the published version's derived field list (40401 family), rejected above 64 KiB; unknown keys are rejected (approval_form_field_not_defined)

The applicant identity and the condition-routing globals are resolved server-side from the authenticated principal (PrincipalDepartmentResolver, InstanceGlobalsResolver) — they are never accepted from the request body, where an applicant could forge them to steer the flow.

start responds with the created Instance:

FieldTypeDescription
tenantIdstringowning tenant
flowId / flowCode / flowVersionIdstringthe flow and the immutable version snapshot the instance runs under
titlestringrendered from the flow's instanceTitleTemplate
instanceNostringhuman-readable instance number
applicantId / applicantNamestringapplicant snapshot taken at start
applicantDepartmentId / applicantDepartmentNamestring | nullapplicant department snapshot
statusstringrunning, approved, rejected, withdrawn, returned, or terminated
currentNodeIdstring | nullnode the instance currently sits on
finishedAtDateTime | nullset when the instance reaches a final status
businessRefstring | nullopaque business reference (business-bound flows)
formDataobjectsubmitted form data (post-validation)
globalsobjecthost-supplied global-variable snapshot taken at start; condition evaluation reads it, so routing stays deterministic
businessProjectionIdstring | absentdurable write-back state claimed at start (business-bound flows)

ProcessTaskParams (process_task):

FieldTypeRequiredDescription
taskIdstringYespending task to act on; the caller must be its assignee (ErrNotAssignee, ErrTaskNotPending)
actionstringYesapprove, reject, transfer, rollback, or handle (handle nodes finish with handle; same semantics as approve)
opinionstring (≤ 2000)conditionaldecision comment; required when the node sets isOpinionRequired (ErrOpinionRequired)
formDataobjectNoform updates written with the action, filtered by the node's field permissions
attachmentsstring[] (≤ 20 × ≤ 512)Noattachment references stored on the action log
transferToIdstringtransfertarget user; must be non-empty and different from the operator (ErrInvalidTransferTarget); allowed only when the node sets isTransferAllowed (ErrTransferNotAllowed)
targetNodeIdstringrollbackrollback destination node; must be one of the node's valid targets per its rollbackType and the instance's visit trail (ErrInvalidRollbackTarget, ErrRollbackNotAllowed). Valid targets are served in my.get_instance_detailmyTask.rollbackTargets

WithdrawParams (withdraw — applicant pulls a running instance back, or abandons a returned one):

FieldTypeRequiredDescription
instanceIdstringYesinstance to withdraw; caller must be the applicant (ErrNotApplicant), state must allow it (ErrWithdrawNotAllowed)
reasonstring (≤ 2000)Nowithdraw reason recorded in the action log

ResubmitParams (resubmit — restart a returned or withdrawn instance):

FieldTypeRequiredDescription
instanceIdstringYesinstance to resubmit (ErrResubmitNotAllowed outside returned / withdrawn)
formDataobjectNoform updates merged over the instance's existing form data; the merged payload is validated like start

AddCCParams / MarkCCReadParams (add_cc, mark_cc_read):

FieldTypeRequiredDescription
instanceIdstringYestarget instance
ccUserIdsstring[] (1–50)add_cc onlyusers to CC; the instance must be running on a node (ErrInstanceCompleted) and the caller must be an assignee of the current node (ErrNotAssignee); allowed only when the current node sets isManualCcAllowed (ErrManualCcNotAllowed)

mark_cc_read stamps the read receipt on all of the caller's unread CC records for the instance — a self-service read receipt, hence no audit.

AddAssigneeParams (add_assignee — dynamic assignee insertion):

FieldTypeRequiredDescription
taskIdstringYesthe caller's own pending task
userIdsstring[] (1–50)Yesusers to add
addTypestringYesbefore (new assignee first, original waits), after (new assignee after the original completes), or parallel (joins the current group). Must be one of the node's addAssigneeTypes (ErrAddAssigneeNotAllowed / ErrInvalidAddAssigneeType)

RemoveAssigneeParams (remove_assignee):

FieldTypeRequiredDescription
taskIdstringYespeer task to cancel; must be a still-actionable peer of the caller's own visit, not the last active assignee (ErrLastAssigneeRemoval), and the node must allow removal (ErrRemoveAssigneeNotAllowed). Eligible peers are served in myTask.removableAssignees

UrgeTaskParams (urge_task):

FieldTypeRequiredDescription
taskIdstringYespending task to urge
messagestring (≤ 500)Nourge message delivered with the notification

Urges honor the node's urgeCooldownMinutes per task (40601 when urged too frequently; non-positive config defaults to 30 minutes), and the operation carries an extra rate limit of 10 calls per minute per caller. The applicant may urge any pending assignee; the applicant and anyone who ever held a task on the instance — directly or as a delegator — may urge any pending task; CC-only viewers cannot.

approval/my

Self-service queries for the current user. The operations declare no RequiredPermission — any authenticated principal may call them; every query is keyed to the caller's identity server-side.

ActionInputOutput
find_available_flowsFindAvailableFlowsParamspage.Page[AvailableFlow]
get_start_formGetStartFormParamsStartForm
find_initiatedFindInitiatedParamspage.Page[InitiatedInstance]
find_pending_tasksFindPendingTasksParamspage.Page[PendingTask]
find_completed_tasksFindCompletedTasksParamspage.Page[CompletedTask]
find_cc_recordsFindCCRecordsParamspage.Page[CCRecord]
get_pending_countsGetPendingCountsParamsPendingCounts
get_instance_detailGetInstanceDetailParamsInstanceDetail

Request parameters (all decoded from params):

ActionFieldTypeRequiredDescription
find_available_flowstenantIdstringNotenant filter
keywordstringNocontains match against the flow name
labelsobjectNolabel equality filter — every pair must match
page / pageSizeintNopagination
get_start_formtenantIdstringYestenant of the flow
flowCodestringYesflow to load the start form for
find_initiatedtenantIdstringNotenant filter
statusstringNoinstance status filter (running / approved / rejected / withdrawn / returned / terminated)
keywordstringNocontains match against the instance title
page / pageSizeintNopagination
find_pending_taskstenantIdstringNotenant filter
page / pageSizeintNopagination
find_completed_taskstenantIdstringNotenant filter
page / pageSizeintNopagination
find_cc_recordstenantIdstringNotenant filter
isReadboolNoread-state filter
page / pageSizeintNopagination
get_pending_countstenantIdstringNotenant filter
get_instance_detailinstanceIdstringYesinstance to load; the caller must be a participant — applicant, assignee, delegator, or CC recipient (ErrAccessDenied)

Response DTOs (approval/my package):

AvailableFlow — one flow the caller may initiate:

FieldTypeDescription
flowId / flowCode / flowNamestringflow identity
flowIconstring | absentdisplay icon
descriptionstring | absentflow description
labelsobject | absenthost-owned selection metadata
categoryId / categoryNamestringowning category identity

StartForm — the pre-submission view of a flow. Loading it is gated exactly like starting the instance (active flow, initiation permission, published version), so a rendered form always implies a startable flow:

FieldTypeDescription
flowId / flowCode / flowNamestringflow identity for the initiation header
flowIconstring | absentdisplay icon
descriptionstring | absentflow description
versionIdstringpublished version the form belongs to
versionintpublished version number
formSchemaJSON document | absenthost form-designer document, verbatim

InitiatedInstance — one instance the caller submitted:

FieldTypeDescription
instanceId / instanceNo / titlestringinstance identity
flowNamestringflow display name
flowIconstring | absentflow icon
labelsobject | absentthe flow's host-owned selection metadata
statusstringinstance status
currentNodeNamestring | absentname of the node currently in progress
createdAtDateTimesubmission time
finishedAtDateTime | absentcompletion time

PendingTask — one task awaiting the caller's action:

FieldTypeDescription
taskIdstringtask to submit process_task against
instanceId / instanceTitle / instanceNostringowning instance identity
flowNamestringflow display identity
flowIconstring | absentflow icon
applicantUserInfoapplicant snapshot
nodeNamestringnode the task belongs to
createdAtDateTimetask creation time
deadlineDateTime | absenttimeout deadline when the node configures one
isTimeoutboolwhether the task is past its deadline

CompletedTask — one task the caller already processed: same identity fields as PendingTask (without createdAt) plus status (the outcome — exactly approved, rejected, handled, transferred, or rolled_back), instanceStatus (the instance's current status), labels (the flow's host-owned selection metadata), and finishedAt; without deadline / isTimeout.

CCRecord — one CC notification addressed to the caller:

FieldTypeDescription
ccRecordIdstringCC record id
instanceId / instanceTitle / instanceNostringowning instance identity
flowNamestringflow display identity
flowIconstring | absentflow icon
applicantUserInfoapplicant snapshot
nodeNamestring | absentnode that produced the CC; absent for instance-level CCs
isReadboolread receipt state
createdAtDateTimedelivery time

PendingCounts — badge counts: pendingTaskCount (tasks awaiting action) and unreadCcCount (unread CC records).

InstanceDetail — the self-service detail view. Each top-level field is one renderable concern:

FieldTypeDescription
instanceInstanceInforuntime state (below)
formSchemaJSON document | absentversion-pinned host form-designer document, verbatim — the schema the instance was submitted under
timelineTimelineEntry[]node-by-node account of the path actually taken (below)
flowGraphInstanceFlowGraphReact Flow–ready read-only graph annotated with progress (below)
availableActionsstring[]viewer-specific action hints (below)
fieldPermissionsobject (field→permission)viewer-scoped field interactivity: visible / editable / hidden / required, materialized for every top-level form field; the client applies it verbatim, and instance.formData is already stripped of fields the viewer may not see (see Node Field Permissions)
myTaskViewerTask | nullthe viewer's own actionable context (below)

InstanceInfo:

FieldTypeDescription
instanceId / instanceNo / titlestringinstance identity
flowId / flowCode / flowName / flowIconstringflow display identity, read from the mutable flow at query time
labelsobject | absentthe flow's host-owned selection metadata — display identity like flowName, not a version-pinned snapshot
applicantUserInfoapplicant snapshot
statusstringinstance status
currentNodeId / currentNodeNamestring | absentcurrently in-progress node
businessRefstring | absentopaque business reference (business-bound flows)
formDataobject | absentform data, stripped of fields the viewer may not see
createdAt / finishedAtDateTimelifecycle timestamps

ViewerTask — the pending task process_task should target plus the node-level configuration the client needs to build the action UI without re-deriving engine semantics. null when the viewer holds no pending task on this instance:

FieldTypeDescription
taskIdstringthe pending task
nodeIdstringits node
isOpinionRequiredboolmirrors the node config: approve / reject must carry a non-empty opinion when set
addAssigneeTypesstring[]positions the node allows for dynamic assignee addition (before / after / parallel); empty when adding is not allowed
rollbackTargets{nodeId, name}[]valid rollback destinations, resolved from the node's rollback config and the instance's visit trail exactly like the rollback command validates them; empty when rollback is not allowed
removableAssignees{taskId, assignee, status}[]peer tasks the viewer may remove (status is pending / waiting), resolved exactly like the remove-assignee command authorizes them: still-actionable peers of the viewer's own visit, excluding the viewer; empty when removal is disallowed

RollbackTarget — one valid rollback destination:

FieldTypeDescription
nodeIdstringtarget node id
namestringnode display name

RemovableAssignee — one peer task eligible for removal:

FieldTypeDescription
taskIdstringpeer task id
assigneeUserInfoassignee snapshot
statusstringtask status (pending / waiting)

availableActions is a query-layer UI hint. For the applicant it includes withdraw when the instance can transition to withdrawn, and resubmit when the instance is returned or withdrawn. For pending tasks it includes handle for handle nodes, otherwise approve, then reject, plus transfer, rollback, add_assignee, remove_assignee, or add_cc when the current node allows them. If the instance has any pending task, it also includes urge; the applicant and anyone who ever held a task on the instance — directly or as a delegator — are allowed to urge (CC-only viewers are excluded). Command handlers still perform their own validation.

approval/admin

Admin-level management and observability. For every list and metrics query, non-super-admin callers ignore a submitted tenantId override and are filtered to their own tenant; super-admin callers may pass tenantId to filter one tenant or omit it for cross-tenant visibility.

ActionPermissionInputOutputAudit
find_instancesapproval.instance.queryAdminFindInstancesParamspage.Page[Instance]
find_tasksapproval.task.queryAdminFindTasksParamspage.Page[Task]
get_instance_detailapproval.instance.detailAdminGetInstanceDetailParamsInstanceDetail
find_action_logsapproval.action_log.queryAdminFindActionLogsParamspage.Page[ActionLog]
get_metricsapproval.metrics.queryAdminGetMetricsParamsMetrics
find_business_projectionsapproval.binding.queryAdminFindBusinessProjectionsParamspage.Page[BusinessProjection]
terminate_instanceapproval.instance.terminateAdminTerminateInstanceParamssuccessYes
reassign_taskapproval.task.reassignAdminReassignTaskParamssuccessYes
retry_business_projectionapproval.binding.retryAdminRetryBusinessProjectionParamssuccessYes

Request parameters (all decoded from params):

ActionFieldTypeRequiredDescription
find_instancestenantIdstringNotenant filter (super-admin only, see above)
applicantIdstringNofilter by applicant
statusstringNoinstance status filter
flowIdstringNofilter by flow
keywordstringNocontains match against the instance title
page / pageSizeintNopagination
find_taskstenantIdstringNotenant filter
assigneeIdstringNofilter by assignee
instanceIdstringNofilter by owning instance
statusstringNotask status filter (waiting / pending / approved / rejected / handled / transferred / rolled_back / canceled / removed / skipped)
page / pageSizeintNopagination
get_instance_detailinstanceIdstringYesinstance to load
find_action_logsinstanceIdstringYesinstance whose audit trail to page through
tenantIdstringNotenant filter
page / pageSizeintNopagination
get_metricstenantIdstringNotenant scope (super-admin may omit for cross-tenant)
find_business_projectionstenantIdstringNotenant filter
statusstringNoprojection status filter: pending, processing, applied, failed
page / pageSizeintNopagination
terminate_instanceinstanceIdstringYesnon-final instance to force-terminate (running, returned, or withdrawn; ErrTerminateNotAllowed once the instance is already in a final status)
reasonstring (≤ 2000)Notermination reason recorded in the action log
reassign_tasktaskIdstringYespending task to reassign
newAssigneeIdstringYesreplacement assignee (ErrInvalidTransferTarget when invalid)
reasonstring (≤ 2000)Noreassignment reason
retry_business_projectionprojectionIdstringYesprojection to retry immediately (ErrBindingProjectionNotFound when missing)

Response DTOs (approval/admin package):

Instance — one instance in the admin list:

FieldTypeDescription
instanceId / instanceNo / titlestringinstance identity
tenantIdstringowning tenant
flowId / flowNamestringflow identity
applicantUserInfoapplicant snapshot
statusstringinstance status
currentNodeNamestring | absentnode currently in progress
createdAt / finishedAtDateTimelifecycle timestamps

Task — one task in the admin list:

FieldTypeDescription
taskIdstringtask id
instanceId / instanceTitlestringowning instance identity
flowNamestringflow display name
nodeNamestringnode the task belongs to
assigneeUserInfoassignee snapshot
statusstringtask status
createdAtDateTimecreation time
deadlineDateTime | absenttimeout deadline
finishedAtDateTime | absentcompletion time

InstanceDetail — the admin counterpart of my.get_instance_detail, without the viewer-specific fields (availableActions / fieldPermissions / myTask): instance (InstanceDetailInfo), formSchema (verbatim host document), timeline (TimelineEntry[]), and flowGraph (InstanceFlowGraph). InstanceDetailInfo matches my.InstanceInfo plus tenantId and flowVersionId, minus flowIcon; its formData is unfiltered.

InstanceDetailInfo — the admin instance detail payload:

FieldTypeDescription
instanceId / instanceNo / titlestringinstance identity
tenantIdstringowning tenant
flowId / flowCode / flowNamestringflow identity
flowVersionIdstringthe version snapshot the instance runs under
labelsobject | absentthe flow's host-owned selection metadata
applicantUserInfoapplicant snapshot
statusstringinstance status
currentNodeId / currentNodeNamestring | absentcurrently in-progress node
businessRefstring | absentopaque business reference (business-bound flows)
formDataobject | absentcurrent form data (unfiltered — admin view)
createdAt / finishedAtDateTimelifecycle timestamps

ActionLog — one audit entry. Person references are uniform UserInfo snapshots captured at action time:

FieldTypeDescription
logIdstringlog entry id
actionstringActionType string: submit, approve, handle, reject, transfer, withdraw, cancel, rollback, add_assignee, remove_assignee, execute, resubmit, reassign, terminate, add_cc
nodeIdstring | absentnode the action happened at; absent for instance-level actions
taskIdstring | absenttask the action targeted
operatorUserInfoacting user snapshot
transferToUserInfo | absenttransfer / reassignment recipient
rollbackToNodeIdstring | absentrollback destination
addedAssignees / removedAssigneesUserInfo[] | absentdynamic assignee changes
ccUsersUserInfo[] | absentmanually CC'd users
opinionstring | absentaction comment / reason
attachmentsstring[] | absentattachment references
createdAtDateTimeaction time

Metrics — aggregated engine health for dashboards and ops alerting:

FieldTypeDescription
tenantIdstringtenant scope of the snapshot; empty for a cross-tenant snapshot (super-admin only)
capturedAtDateTimewhen the metrics were materialized
instanceCountsobject (status→int)instance counts keyed by InstanceStatus string
taskCountsobject (status→int)task counts keyed by TaskStatus string
timeoutTaskCountintpending tasks past their deadline
avgCompletionSecondsfloataverage end-to-end duration (createdAtfinishedAt) over all finalized instances; -1 means "no completed instances yet"
pendingBindingFailuresintprojection targets whose latest write attempt failed and is scheduled for retry
businessProjectionCountsobject (status→int)durable projection rows by convergence status (pending / processing / applied / failed)
pendingBusinessProjectionsinteventual projections whose desired revision has not been applied yet

BusinessProjection — the operator-facing convergence state for one bound business record (see Integration for the write-back model):

FieldTypeDescription
projectionIdstringprojection row id
tenantIdstringowning tenant
flowId / flowVersionIdstringflow and version that own the desired state
ownerInstanceIdstringinstance whose lifecycle produced the desired state
appliedOwnerInstanceIdstring | absentinstance whose state was last successfully written to the business row
businessTablestringtarget business table
recordKeyJSON objectkey-column values locating the bound row
consistencystringbinding consistency mode from configuration (synchronous / eventual)
desiredStatusstringinstance status awaiting write-back
desiredStartedAtDateTimelifecycle timestamp awaiting write-back
desiredFinishedAtDateTime | absentlifecycle timestamp awaiting write-back
desiredRevision / appliedRevisionintmonotonic revisions; the projection has converged when they are equal
statusstringconvergence state: pending, processing, applied, failed
attemptCountintwrite attempts so far
nextAttemptAtDateTime | absentnext scheduled retry
leaseUntilDateTime | absentworker lease expiry while processing
lastErrorstring | absentlast write failure message
appliedAtDateTime | absentwhen the desired state was last applied
updatedAtDateTimelast state change

Shared Projection Types

The detail views (my.get_instance_detail, admin.get_instance_detail) share these types from the public approval package.

UserInfo — the uniform person snapshot used everywhere a person appears:

FieldTypeDescription
idstringuser id
namestringdisplay name at action time
departmentId / departmentNamestring | absentdepartment snapshot at action time

TimelineEntry — one step of the instance timeline: the chronological, node-by-node account of the path an instance actually took. Because condition branches are exclusive, the traversed path is always a single line; a node re-entered after a rollback produces a second entry. Entries end at the node currently in progress — unreached nodes are not predicted:

FieldTypeDescription
kindstringstart, approval, handle, cc, end for node visits; withdraw, terminate for instance-level milestones. The condition structural kind never appears; the end visit is kept as the timeline's closing marker
nodeIdstring | absentvisited node; absent on milestone entries
namestringnode display name (or milestone action name)
statusstringnode-visit status: active, passed, rejected, returned, canceled
executionTypestringnode execution type (manual / auto_pass / auto_reject)
approvalMethodstringsequential / parallel (approval nodes)
passRulestringall / any / ratio (approval nodes)
passRatiodecimal | absentratio threshold when passRule is ratio
participantsNodeParticipant[]one entry per task at approval / handle nodes (below)
ccRecipientsCCRecipient[]delivered carbon copies: user (UserInfo) plus readAt read receipt
activitiesActivity[]side actions at the node (below); milestone entries hold a single activity describing who closed the instance and why
startedAt / finishedAtDateTimevisit span; finishedAt absent while in progress

NodeParticipant — one assignee's involvement during a single visit:

FieldTypeDescription
taskIdstringtask identity (what task operations target)
userUserInfoassignee snapshot
delegatorUserInfo | absentpresent when the task arrived via delegation
statusstringtask status verbatim
deadlineDateTime | absenttask deadline
isTimeoutbooltask was decided or escalated by the timeout scanner
opinion / attachments / actionTimeoutcome details fused from the action log that finished the task
transferToUserInfo | absenttransfer recipient when the task was transferred

Activity — a side action recorded at a node: action carries the ActionType string (transfer, rollback, add_assignee, remove_assignee, add_cc, reassign, execute, submit, resubmit, withdraw, terminate) plus urge for urge records. operator is the acting user; opinion holds the action's free text (a transfer reason, a withdraw reason, an urge message); target names the counterpart of a directed action (the urged assignee); transferTo, rollbackToNodeId / rollbackToNodeName, addedAssignees, removedAssignees, ccUsers, and attachments carry the action-specific details, and createdAt the action time. Decisions themselves (approve / handle / reject) are not repeated as activities — they live on the participant that made them.

InstanceFlowGraph — a React Flow–ready, read-only projection of the instance's pinned flow definition annotated with runtime progress. nodes and edges map directly onto React Flow's shape, except the node kind stays in kind (React Flow's type belongs to the client):

FieldTypeDescription
nodes[].idstringReact Flow identity — the design-time node key that positions and edges reference
nodes[].nodeIdstringpersistent flow-node id — the value actionLog.nodeId / rollbackToNodeId carry and the process_task rollback API expects as targetNodeId
nodes[].kindstringnode kind (start / approval / handle / condition / cc / end)
nodes[].position{x, y}designer coordinates
nodes[].dataFlowGraphNodeDatanode label, approval semantics, progress status (pending / active / passed / rejected / returned / canceled), plus participants / ccRecipients / activities aggregated across the node's visits in traversal order, and the startedAt / finishedAt span
edges[]{id, source, target, sourceHandle}React Flow edges connecting nodes by their ids

Error Surface

The importable approval package exports four plain Go sentinels. They are recognized with errors.Is, but they are not result.Error values and do not carry an API code or HTTP status by themselves.

ErrorSource packageMeaning
approval.ErrCrossTenantAccessapprovalnon-super-admin caller attempted cross-tenant access
approval.ErrInvalidBusinessIdentifierapprovalbusiness table / field identifier failed the SQL-identifier whitelist
approval.ErrUnknownNodeKindapprovalNodeDefinition.ParseData saw an unsupported kind
approval.ErrNodeDataUnmarshalapprovalNodeDefinition.ParseData could not decode node data

Built-in approval resources return module-owned result.Error values through the normal API envelope. Those values live under internal packages, so host applications should treat the code/message pair below as the public wire surface rather than importing the internal Go symbols.

CodeCode constantError valuei18n message keyNotes
40001ErrCodeFlowNotFoundErrFlowNotFoundapproval_flow_not_foundflow lookup failed
40002ErrCodeFlowNotActiveErrFlowNotActiveapproval_flow_not_activeflow is disabled
40003ErrCodeNoPublishedVersionErrNoPublishedVersionapproval_no_published_versionflow has no published version
40004ErrCodeVersionNotDraftErrVersionNotDraftapproval_version_not_draftoperation requires a draft version
40005ErrCodeInvalidFlowDesignErrInvalidFlowDesignapproval_invalid_flow_designgraph or node design failed validation
40006ErrCodeFlowCodeExistsErrFlowCodeExistsapproval_flow_code_existsduplicate flow code
40007ErrCodeVersionNotFoundErrVersionNotFoundapproval_version_not_foundflow version lookup failed
40008ErrCodeInvalidBusinessIdentifierErrInvalidBusinessIdentifierapproval_invalid_business_identifierbusiness table / field identifier failed validation
40009ErrCodeInvalidTitleTemplateErrInvalidTitleTemplateapproval_invalid_title_templateinstance title template failed parsing
40010ErrCodeInvalidFormDesignErrInvalidFormDesignapproval_invalid_form_designform schema failed design-time validation
40011ErrCodeBindingIncompleteErrBindingIncompleteapproval_binding_incompletebusiness binding is missing required table / key / status / instance-id fields
40012ErrCodeInvalidBindingModeErrInvalidBindingModeapproval_invalid_binding_modeflow binding mode is out of enum
40013ErrCodeInvalidInitiatorKindErrInvalidInitiatorKindapproval_invalid_initiator_kindflow initiator kind is out of enum
40014ErrCodeInvalidStorageModeErrInvalidStorageModeapproval_invalid_storage_modedeploy requested a storage mode other than json or table
40015unused (former flow-binding lock); the code is never reassigned
40016ErrCodeBindingColumnsConflictErrBindingColumnsConflictapproval_binding_columns_conflicttwo business-binding fields name the same column
40017ErrCodeBindingUnexpectedErrBindingUnexpectedapproval_binding_unexpectedbusiness binding supplied on a standalone flow
40018ErrCodeBindingSchemaInvalidErrBindingSchemaInvalidapproval_binding_schema_invalidconfigured binding table or columns do not exist in the primary database
40019ErrCodeBindingKeyNotUniqueErrBindingKeyNotUniqueapproval_binding_key_not_uniquekey columns are not backed by one complete, non-null primary or unique key
40020ErrCodeBindingStatusMappingInvalidErrBindingStatusMappingInvalidapproval_binding_status_mapping_invalidstatus mapping names an unknown status or maps to a blank value
40021ErrCodeInvalidFlowLabelErrInvalidFlowLabelapproval_invalid_flow_labelflow label key would silently break JSON or host tooling
40022ErrCodeInitiatorsNotAllowedErrInitiatorsNotAllowedapproval_initiators_not_allowedisAllInitiationAllowed=true and initiators are mutually exclusive
40023ErrCodeInitiatorsRequiredErrInitiatorsRequiredapproval_initiators_requiredrestricted initiation requires at least one initiator rule with non-empty IDs
40101ErrCodeInstanceNotFoundErrInstanceNotFoundapproval_instance_not_foundinstance lookup failed
40102ErrCodeInstanceCompletedErrInstanceCompletedapproval_instance_completedinstance is already complete
40103ErrCodeNotAllowedInitiateErrNotAllowedInitiateapproval_not_allowed_initiatecaller cannot initiate this flow
40104ErrCodeWithdrawNotAllowedErrWithdrawNotAllowedapproval_withdraw_not_allowedwithdraw is not allowed in the current state
40105ErrCodeResubmitNotAllowedErrResubmitNotAllowedapproval_resubmit_not_allowedresubmit is not allowed in the current state
40106ErrCodeInvalidInstanceTransitionErrInvalidInstanceTransitionapproval_invalid_instance_transitioninstance state transition is invalid
40107ErrCodeBusinessRefRequiredErrBusinessRefRequiredapproval_business_ref_requiredbusiness-bound flow started without a business reference
40108ErrCodeBindingTargetBusyErrBindingTargetBusyapproval_binding_target_busythe business record is already claimed by a non-final approval instance
40109ErrCodeInvalidBusinessRefErrInvalidBusinessRefapproval_invalid_business_refthe business reference could not be resolved into the configured record key
40110ErrCodeBindingProjectionNotFoundErrBindingProjectionNotFoundapproval_binding_projection_not_foundprojection lookup failed (admin retry)
40201ErrCodeTaskNotFoundErrTaskNotFoundapproval_task_not_foundtask lookup failed
40202ErrCodeTaskNotPendingErrTaskNotPendingapproval_task_not_pendingtask is not pending
40203ErrCodeNotAssigneeErrNotAssigneeapproval_not_assigneecaller is not assigned to the task
40204ErrCodeInvalidTaskTransitionErrInvalidTaskTransitionapproval_invalid_task_transitiontask state transition is invalid
40205ErrCodeRollbackNotAllowedErrRollbackNotAllowedapproval_rollback_not_allowedrollback is disabled or not valid here
40206ErrCodeAddAssigneeNotAllowedErrAddAssigneeNotAllowedapproval_add_assignee_not_alloweddynamic assignee insertion is disabled
40207ErrCodeTransferNotAllowedErrTransferNotAllowedapproval_transfer_not_allowedtransfer is disabled
40208ErrCodeOpinionRequiredErrOpinionRequiredapproval_opinion_requiredrequired opinion is blank
40209ErrCodeManualCcNotAllowedErrManualCcNotAllowedapproval_manual_cc_not_allowedmanual CC is disabled
40210ErrCodeRemoveAssigneeNotAllowedErrRemoveAssigneeNotAllowedapproval_remove_assignee_not_alloweddynamic assignee removal is disabled
40211ErrCodeInvalidAddAssigneeTypeErrInvalidAddAssigneeTypeapproval_invalid_add_assignee_typeaddType is not one of before, after, parallel
40212ErrCodeNotApplicantErrNotApplicantapproval_not_applicantcaller is not the applicant
40213ErrCodeInvalidRollbackTargetErrInvalidRollbackTargetapproval_invalid_rollback_targetrollback target is not allowed
40214ErrCodeLastAssigneeRemovalErrLastAssigneeRemovalapproval_last_assignee_removalremoval would leave no active assignee
40215ErrCodeInvalidTransferTargetErrInvalidTransferTargetapproval_invalid_transfer_targettransfer or reassignment target is invalid
40216ErrCodeNoUsersSpecifiedErrNoUsersSpecifiedapproval_no_users_specifieduser-list operation received no target users
40301ErrCodeNoAssigneeErrNoAssigneeapproval_no_assigneeno assignee could be resolved
40302ErrCodeAssigneeResolveFailedErrAssigneeResolveFailedapproval_assignee_resolve_failedassignee resolver failed
40401ErrCodeFormValidationFailedErrFormValidationFailedapproval_form_validation_failedgeneral form validation failure
40401ErrCodeFormValidationFailedErrFormDataTooLargeapproval_form_data_too_largesame code; JSON-encoded formData exceeded 64 KiB
40401ErrCodeFormValidationFaileddynamic form validation result.Errapproval_form_field_not_defined, approval_form_field_required, approval_form_field_must_be_string, approval_form_field_must_be_number, approval_form_field_must_be_integer, approval_form_field_min_length, approval_form_field_max_length, approval_form_field_invalid_validation, approval_form_field_pattern_mismatch, approval_form_field_min_value, approval_form_field_max_value, approval_form_field_empty, approval_form_field_invalid_file_item, approval_form_field_must_be_file, approval_form_field_invalid_value, approval_form_field_must_be_row_list, approval_form_field_must_be_row_object, approval_form_field_min_rows, approval_form_field_max_rows, approval_form_field_table_cellfield-level validation messages are constructed dynamically
40601ErrCodeUrgeCooldowndynamic urge result.Errapproval_urge_too_frequentno static sentinel; message is rendered with minutes; non-positive urgeCooldownMinutes defaults to 30 minutes
40701ErrCodeAccessDeniedErrAccessDeniedapproval_access_deniedcaller lacks approval-domain access
40702ErrCodeTerminateNotAllowedErrTerminateNotAllowedapproval_terminate_not_allowedterminate is not allowed from the current instance state

Startup and tenant-resolution diagnostics such as ErrEventRouteNotTransactional and ErrTenantNotResolved live under internal/approval/...; they are not importable public Go API, but operators may see their wrapped messages when event routing or tenant principal details are misconfigured.


Next: Flow Design for the designer wire shapes behind deploy, or Instance Runtime for lifecycle semantics behind the instance actions.