Skip to content

Building an external task list

Your own application can be the task list: it lists the user tasks a person may work on, lets them claim, edit and complete them, and shows what happened to each one. Everything it needs is the v2 user-task API, which every deployment mode serves the same way (Embedded, Bundled and Cluster). The console’s Tasks area is built on it too.

A runnable example in Python (standard library only) is in tasklist/ in the examples download of the release (tinyconductor-0.1.0-examples.tar.gz).

A user task needs the user-task marker (tc:userTask) in its extension elements. Assignment, candidates, schedule and priority are optional:

<bpmn:userTask id="review-claim" name="Review claim">
<bpmn:extensionElements>
<tc:userTask/>
<tc:assignmentDefinition candidateUsers="= reviewers" candidateGroups="claims"/>
<tc:taskSchedule dueDate="= now() + duration(&quot;P2D&quot;)"/>
<tc:priorityDefinition priority="= if amount &gt; 1000 then 80 else 30"/>
<tc:formDefinition formId="claim-form"/>
</bpmn:extensionElements>
</bpmn:userTask>
  • Name. The task shows the element’s name (Review claim).

  • Priority is an integer from 0 to 100; higher is more important, and 50 is the default. It may be a literal or an expression. A literal outside the range is refused at deployment; an expression is evaluated when the task is activated, and a result that is not an integer from 0 to 100 raises an incident on the task.

  • A user task without the marker still deploys and runs as a native user task, with the same lifecycle and API. No job is created for it, so a job worker waiting for one would wait forever. The deployment answer names each such task in warnings, and the engine log carries the same warning:

    "warnings": [{
    "code": "USER_TASK_WITHOUT_MARKER",
    "message": "user task 'check-documents' of process 'unmarked-review' has no user-task implementation marker; it runs as a native user task, and no job is created for it",
    "resourceName": "unmarked-review.bpmn",
    "processDefinitionId": "unmarked-review",
    "elementId": "check-documents"
    }]

The engine decides per person; your application does not filter. A person sees a task when it is assigned to them, names them or one of their groups as a candidate, has no assignee and no candidates at all, or when a stored grant of task:read or task:admin names the task or its process. A task:admin held on every resource sees the whole tenant. A task a person may not see answers 404, exactly like a task that does not exist. The rules, and the actions each operation needs, are in Task authorization. A typical worker holds task:read, task:claim and task:complete (the built-in task-worker role), plus task:update to edit tasks.

POST /v2/user-tasks/search
{
"filter": {
"state": "CREATED",
"candidateGroup": "claims",
"priority": { "$gte": 50 },
"dueDate": { "$lt": "2026-10-01T00:00:00Z" },
"processInstanceVariables": [{ "name": "region", "value": "\"emea\"" }]
},
"sort": [{ "field": "priority", "order": "DESC" }, { "field": "dueDate" }],
"page": { "limit": 20 }
}
  • Filters: state, assignee, candidateUser, candidateGroup, name, elementId, priority, processDefinitionId, processDefinitionKey, processInstanceKey, elementInstanceKey, userTaskKey, tenantId, and the dates creationDate, completionDate, dueDate and followUpDate. Each takes a plain value or operators: $eq, $neq, $exists, $in, $notIn, $like (* and ? wildcards), and $gt, $gte, $lt, $lte for priority and dates. $or takes a list of filter objects.
  • Variable filters: localVariables (the task’s own scope) and processInstanceVariables match a variable’s JSON text, so a string value is written with its quotes.
  • Sort by priority, creationDate, dueDate, followUpDate, completionDate or name, ascending or descending; tasks without a value come last.
  • Paging: page.after takes the previous page’s endCursor, page.before the next page’s startCursor, and page.from an offset. page.totalItems counts every task the person may see that matches, up to 10,000; beyond that it says 10,000 and page.hasMoreTotalItems is true. In Bundled and Cluster mode every finished task can be paged to, however many there are; a search with a variable filter there considers at most 10,000 finished tasks that meet its other conditions, and with more it answers 400 asking for a narrower filter. In Embedded mode a search considers at most 10,000 tasks.

Each item carries the task’s name, priority, assignee, candidates, dates, formKey or externalFormReference, customHeaders and keys. GET /v2/user-tasks/{userTaskKey} reads one task in the same shape.

CallAnswer
GET /v2/user-tasks/{key}/formThe linked deployed form (schema as JSON text), or 204 when the task links none. A task with an externalFormReference names a form your application resolves itself.
POST /v2/user-tasks/{key}/variables/searchThe variables of the task’s own scope (its input mappings).
POST /v2/user-tasks/{key}/effective-variables/searchEvery variable the task sees: its own, then each enclosing scope up to the process instance; the nearest one wins.
POST /v2/user-tasks/{key}/audit-logs/searchWho did what to the task: creation, assignments, updates, completion, with the acting user and SUCCESS or FAIL.

Variable values come back as JSON text; long values are cut and flagged isTruncated unless the request adds ?truncateValues=false.

CallEffect
POST /v2/user-tasks/{key}/assignment {"assignee": "alice", "allowOverride": false}Claim: refused with 409 when somebody already has the task. A person of a provider other than the default is named with its alias, such as alice@corp (Who a person is).
POST /v2/user-tasks/{key}/assignment {"assignee": "bob"}Assign: assigning to somebody else, or taking over a task that somebody else has, needs task:assign.
DELETE /v2/user-tasks/{key}/assigneeReturn the task to the pool. Returning somebody else’s task needs task:assign.
PATCH /v2/user-tasks/{key} {"changeset": {"priority": 90, "dueDate": "2026-10-01T12:00:00Z", "candidateGroups": ["seniors"]}, "action": "escalate"}Change the due and follow-up dates (the empty string clears one), the candidates and the priority (0 to 100, 400 otherwise).
POST /v2/user-tasks/{key}/completion {"variables": {"approved": true}, "action": "approve"}Complete; the variables go through the task’s output mappings. Completing somebody else’s task needs task:assign.

Every command answers 204 once the engine has applied it, so a read that follows sees the change. A command for a finished task is 404. An action names what your application meant (approve, reject, escalate, …); it is recorded on the task’s records and in the audit log. Send an Idempotency-Key header to make a retried command safe.

Task listeners run your code when a task is created, assigned, updated or completed, and hold the change until your code answers. They are job workers: a listener job of the listener’s type appears, and your worker completes it through POST /v2/jobs/{jobKey}/completion. With a result the worker can correct the task or deny the change:

{ "result": { "corrections": { "priority": 95, "candidateGroups": ["seniors"] } } }
{ "result": { "denied": true, "deniedReason": "four eyes: the requester cannot approve" } }

Corrections may set the assignee, due and follow-up dates, candidates and priority, and are stored. A denial of an assignment, update or completion leaves the task exactly as it was, with the reason on the task’s records.

The example runs against an Embedded engine from the registry.tinyfactory.ai/tinyblox/tinyconductor image, with one credential for the operator and one each for Alice and Bob:

Terminal window
docker run --rm -d --name tinyconductor-tasklist -p 127.0.0.1:8080:8080 \
-e CONDUCTOR_AUTH_CREDENTIALS_JSON='[
{"token":"operator-token","subject":"operator","tenantId":"default","kind":"user","actions":["*"]},
{"token":"alice-token","subject":"alice","tenantId":"default","kind":"user","actions":["task:read","task:claim","task:update","task:complete"]},
{"token":"bob-token","subject":"bob","tenantId":"default","kind":"user","actions":["task:read","task:claim","task:complete"]}
]' registry.tinyfactory.ai/tinyblox/tinyconductor:0.1.0 --mode embedded
python3 tinyconductor-0.1.0-examples/tasklist/tasklist.py
docker stop tinyconductor-tasklist

The tinyconductor program from a release download works the same way: set the same CONDUCTOR_AUTH_CREDENTIALS_JSON and run tinyconductor --mode embedded.

It deploys the two models (and prints the warning for the unmarked one), starts three claims, lists Alice’s and Bob’s tasks by priority, lets Alice claim the most urgent one while Bob’s claim is refused, reads the task’s variables, escalates it, approves it, and prints its audit log:

alice's task list (2 tasks):
[ 80] Review claim key 94575592174792714 candidates ['alice', 'bob'] assignee None
[ 30] Review claim key 94575592174784520 candidates ['alice'] assignee None
...
audit log:
CREATE by bpm-runtime SUCCESS
ASSIGN by alice SUCCESS
UPDATE by alice SUCCESS
COMPLETE by alice SUCCESS
OK: the external task list works end to end

The complete request and answer shapes are in the v2 API reference (Orchestration API v2, the User tasks section).