In this tutorial we'll see how to use the Workflow REST API to model a workflow for a simple approvals process.
Let's imagine we want to model the process of approving some data. To do this, we first create a task definition that models the possible flows. We can then create a tasks each time we want to actually approve some data.
Note we'll only transition between states manually in this tutorial. To learn how to automate workflows, follow this tutorial on setting up a data quality control to check new pricing data for outliers.
Note: To complete this tutorial, you must have suitable access control permissions. This can most easily be achieved by assigning your LUSID user the built-in
lusid-administrator
role, which should already be the case if you are the domain owner. If you are informed you do not have a license to perform a certain operation, contact support.
Step 1: Creating a task definition to model the workflow
We must first create a task definition using the CreateTaskDefinition API, passing in:
An API access token as a Bearer token in the Authorization HTTP header. See how to obtain a token.
A
scope
andcode
that together uniquely identify the task definition.A
displayName
anddescription
for the task definition.A set of possible
states
; in our example we needPending
,Approved
,Denied
andNotRequired
.A
fieldSchema
defining a set of fields, for our example we needassignee
,dataToApprove
andreasonForDecision
as fields.An
initialState
ofPending
, and adataToApprove
field that is required to enter this state.A set of
triggers
to prompt state transitions. Each trigger should contain the following:A
name
for the trigger.The
type
of the trigger - for now onlyExternal
triggers are available.
A set of
transitions
to control movement from the initial state to all the other states. Each transition should contain the following:A
fromState
specifying the state the transition begins in.A
toState
specifying the state the transition ends in.The name of the
trigger
prompting the transition to occur.Optionally, a
guard
condition which must be met for the transition to succeed. Read more on using guards in workflows.
Note: To avoid infinite loops, there must always be at least one terminal state within the task definition; that is, a state from which no onward transitions are possible.
curl -X POST 'https://<your-domain>.lusid.com/workflow/api/taskdefinitions'
-H 'Authorization: Bearer <your-api-access-token>'
-H 'Content-Type: application/json-patch+json'
-d '{
"id": {
"scope": "approvals",
"code": "dataApproval"
},
"displayName": "Data Approval",
"description": "A task definition for a simple data approval task",
"states": [
{ "name": "Pending" },
{ "name": "Approved" },
{ "name": "Denied" },
{ "name": "NotRequired" }
],
"fieldSchema": [
{
"name": "assignee",
"type": "String"
},
{
"name": "dataToApprove",
"type": "String"
},
{
"name": "reasonForDecision",
"type": "String"
}
],
"initialState": {
"name": "Pending",
"requiredFields": [
"dataToApprove"
],
},
"triggers": [
{
"name": "grant",
"trigger": {
"type": "External"
}
},
{
"name": "reject",
"trigger": {
"type": "External"
}
},
{
"name": "cancel",
"trigger": {
"type": "External"
}
}
],
"transitions": [
{
"fromState": "Pending",
"toState": "Approved",
"trigger": "grant",
"guard": "fields['assignee'] exists"
},
{
"fromState": "Pending",
"toState": "Denied",
"trigger": "reject",
"guard": "fields['reasonForDecision'] exists and fields['assignee'] exists"
},
{
"fromState": "Pending",
"toState": "NotRequired",
"trigger": "cancel"
}
]
}'
Part of a response is shown below. Note the asAtVersionNumber
of the task definition, which increments if you update the task definition:
{
"id": {
"scope": "approvals",
"code": "dataApproval"
},
"version": {
"asAtCreated": "2023-05-15T11:08:15.1522320+00:00",
"userIdCreated": "00ujk6twb4jDcHGjN2p8",
"asAtModified": "2023-05-15T11:08:15.1522320+00:00",
"userIdModified": "00ujk6twb4jDcHGjN2p8",
"asAtVersionNumber": 1
},
"displayName": "Data Approval",
"description": "A task definition for a simple data approval task",
...
Step 2: Creating a task to approve some data
Now that we have our task definition, we can test our workflow using a new approval. To do so, we create a task from our task definition and include the required fields for the task to enter its initial state and set the workflow in motion.
To create a task we call the CreateTask API, passing in:
The
scope
andcode
of the task definition.Optionally, any
correlationIds
to track and group API calls.The
name
and an appropriatevalue
for the requireddataToApprove
field:
curl -X POST "https://<your-domain>.lusid.com/workflow/api/tasks"
-H "Authorization: Bearer <your-API-access-token>"
-H "Content-Type: application/json"
-d '{
"taskDefinitionId": {
"scope": "approvals",
"code": "dataApproval"
},
"correlationIds": ["track-this-in-the-logs"],
"fields": [
{
"name": "dataToApprove",
"value": "Data x for y client"
}
]
}'
Part of a response is shown below. Note the following:
The
asAtVersionNumber
of the task, which increments if you update the task (see step 3).The task is in its initial
state
ofPending
.LUSID automatically creates a unique
id
for the task, which you can use to retrieve or update the task at any time.
{
"id": "79315001-b4bf-40e6-aae1-24850119aa9f",
"correlationIds": [
"track-this-in-the-logs"
],
"taskDefinitionId": {
"scope": "approvals",
"code": "dataApproval"
},
"taskDefinitionVersion": {
"asAtModified": "2023-05-15T11:08:15.1522320+00:00"
},
"version": {
"asAtCreated": "2023-05-15T12:39:12.8015030+00:00",
"userIdCreated": "00uji4twb4jDcHGjN2p7",
"asAtModified": "2023-05-15T12:39:12.8015030+00:00",
"userIdModified": "00uji4twb4jDcHGjN2p7",
"asAtVersionNumber": 1
},
"state": "Pending",
"terminalState": false,
...
Step 3: Updating the task to grant approval manually
Our task now exists in the Pending
state. To transition it to another state, such as Approved
, we can call the UpdateTask API, passing in the following:
Within the request URL:
The unique
id
for the task we want to update.A
trigger
to prompt a state transition. For our example, we'll use thegrant
trigger to move to theApproved
state.
The
name
and an appropriatevalue
for theassignee
field, which is required to meet the guard on thegrant
trigger.Names and appropriate values for any other fields we want to propagate, since this is a PUT operation and resources not specified are set to null (and therefore lost). In our example, we'll include the
dataToApprove
field in order to pass the data to theApproved
state.
curl -X POST "https://<your-domain>.lusid.com/workflow/api/tasks/79315001-b4bf-40e6-aae1-24850119aa9f?trigger=grant"
-H "Authorization: Bearer <your-API-access-token>"
-H "Content-Type: application/json"
-d '{
"fields": [
{
"name": "assignee",
"value": "Joe Bloggs"
},
{
"name": "dataToApprove",
"value": "Data x for this y client"
}
]
}'
Part of a response is shown below. Note the following:
The
asAtVersionNumber
of the task has been incremented.The task is in its terminal
state
ofApproved
.
{
...
"taskDefinitionVersion": {
"asAtModified": "2023-05-16T08:12:42.2904480+00:00"
},
"version": {
"asAtCreated": "2023-05-16T13:57:37.9615180+00:00",
"userIdCreated": "00uji4twb4jDcHGjN2p7",
"asAtModified": "2023-05-16T13:58:08.3013150+00:00",
"userIdModified": "00uji4twb4jDcHGjN2p7",
"asAtVersionNumber": 2
},
"state": "Approved",
"terminalState": true,
...
Since the task is now in its terminal state, it is not possible to trigger more state transitions for this particular task. To do anything else with this workflow, we need to create a new task from our task definition using the CreateTask
API.