docs
/
AI skills

Stage workflow with SLA timers and escalation

Put a kind of record through named stages (new, in review, done) with who works each stage, how many must sign off, what must be filled in before it moves on, a deadline per stage and who is added when it runs late; try it on one test record, then start it for every new record.

A workflow (workflow_definition) is a list of stages. Starting it for a record makes a task that sits on one stage at a time, names who it waits on (assignTo) and carries a deadline (dueDate) when the stage has one. When the deadline passes, the stage's next escalation tier adds people and reminds whoever hasn't acted. Each stage can also write its own state and status onto the record, so the record shows where it is.

Tickets already come with a workflow of this kind: see Customer support ticket flow. For approvals that grant access or publish, use Approvals: publishing review and access requests. For sending emails or making records when a record changes, use Automation when a record is added or updated.

Ask first

  • Which kind of record? Its collection (record type), e.g. application.
  • The stages, in order. A name for each and what it means for the record ("In review", status inprogress). The last ones are the outcomes: done, and if it can be turned down, rejected.
  • Who works each stage? Named people, a role, a group, the requester's manager, or a person named on the record. How many must sign off to move it on.
  • What must be filled in before a stage is left (a proof link, a signature, a date)?
  • The deadline per stage and who is added when it is missed, then if that is missed too.
  • Should it start for every new record, or only when someone starts it?
  • Should anyone be emailed when a task reaches a stage? By default nobody is.

Steps

1. Collection builder: let the record type use a workflow

A new record only starts a workflow when its collection allows it. repo_find on collection with { "data.name": "<datatype>" }: read data.enableWorkflow and data.workflow (a workflow's sk, when the collection names one).

enableWorkflowNew recordsStarted on purpose (/workflow/fire)
truestart the workflowyes
missing, or no collectiondon't start ityes
falsedon't start itrefused

Set it with repo_update on the collection: { "data.enableWorkflow": true }. In Studio: the collection's Enable workflow in the Collection builder (/collection-builder).

GET /workflow/for/<datatype> says which workflow takes a record type and why. When the type has no collection it still reports the workflow as active, though new records won't start it (tested). Trust enableWorkflow.

Hands on: the collection's name and enableWorkflow. Check: repo_find_one on the collection shows enableWorkflow: true.

2. Workflow: write the stages

POST /workflow/definition with { "name": "<slug>", "title": "<shown name>", "collections": ["<datatype>"], "enabled": true, "autoFire": false, "stages": [ … ] }.

Set autoFire: false for now: the workflow can be started by hand on a test record, but new records don't start it until step 5. Always send enabled: a workflow saved without it runs, while Studio's form shows it switched off (tested). Close to a shipped pattern? POST /workflow/definition/from-template/<name> makes it (application-processing-pipeline, service-appointment-pipeline, renewal-pipeline), or returns the org's own copy if it has one. These come switched on, with no collections, and every stage emails its people (task-assignment, task-status-update and their -sms). PATCH its collections, stages and templates before anything starts it.

Each stage:

FieldWhat it does
id, name0, 1, 2… in order; the name people read
typestart (the first), intermediate, end or done (an outcome: the task finishes there)
modelState, modelStatusWhat the record shows while it is here (new, inprogress, completed; new, pending, done, rejected). Written onto the record because its type is in collections.
assignToWho works it: emails, or role names. Everyone listed may move it on.
assignRuleInstead of assignTo, who decides, per record: type roles, group, users, relative (supervisor, manager, department-head, location-manager of the requester) or field (an email at a path on the record, e.g. data.owner.email); required (how many must sign off) and mode (any, or all for everyone); fallbackRoles when nobody is found. Only these people, or an admin giving a reason, move it on.
rejectPolicyWith assignRule: any-rejects (one "no" sends it to the rejected outcome) or when-unreachable
inputsFields needed before it moves forward: { "name": "proof_link", "type": "string", "required": true } (string, number, boolean, date, date-time, time)
escalationsThe deadline tiers, below
notificationTemplateMessage templates sent to the stage's people when a task arrives, e.g. task-assignment. Empty, and the workflow's own notificationTemplate is used; neither, and nobody is told.

The deadline tiers, escalations[], in order:

Field
escalateAfter, unitsHow long: a number and minutes, hours or days
escalateRule or escalateToWho is added to the task when it fires (a rule shaped like assignRule, or emails). Leave both out to only remind the people already on it.
notificationTemplateThe reminder, e.g. task-reminder. Falls back to the stage's, then the workflow's. Goes only to those who haven't acted.

The first tier sets the deadline when the task reaches the stage. Each later tier counts from when the one before it fired, not from arrival: tiers of 4 hours and 1 day mean the second is due 1 day after the 4-hour mark (tested, in minutes).

A workflow may also archive finished tasks: autoArchive: { "enabled": true, "after": 7, "units": "days" }.

Hands on: the workflow's sk. Check: GET /workflow/definition/<sk> reads back every stage, and in Studio Workflow (/workflow) › Definitions shows it with its stages and SLA Escalation Tiers.

3. Workflow: try it on one test record

Make a test record of that type (name starting test-, people @test.local) and start the workflow on it: POST /workflow/fire with { "datatype": "<datatype>", "id": "<record sk>", "workflowId": "<sk>" }. Add "data": { "requesterEmail": "…" } to name who asked; otherwise it is whoever created the record. For the test, give every stage @test.local people.

Stop: a stage with a notificationTemplate, and every reminder tier, emails its people. Before starting it, list who each stage and tier would email, and check none are real people unless the person says so.

It answers { "task": {…}, "workflowId" }. On the task: stageId: 0, assignTo, dueDate, escalationTier: 0. On the record: the first stage's state and status, and workflow: { workflowId, taskId, status }.

Hands on: the taskId. Check: GET /workflow/task/<taskId>/sla answers dueDate, remainingMs, isBreached: false, escalationTier: 0.

4. Workflow: move it through, and watch the clock

ActionCallNotes
Next stagePOST /workflow/task/<taskId>/advanceinputs: { "proof_link": "…" } when the stage asks for them
A given stagePOST /workflow/task/<taskId>/move-to/<stageId>Back, or to an outcome (the rejected one is a "no")
FinishPOST /workflow/task/<taskId>/completeTo the first outcome stage
CancelPOST /workflow/task/<taskId>/cancelKept on the history
Hand onPOST /workflow/task/<taskId>/reassign with { "assignTo": ["…"] }Replaces everyone on it; tells nobody
NotePOST /workflow/task/<taskId>/note with { "note": "…" }
Push the deadlinePOST /workflow/task/<taskId>/snooze with { "minutes": 60 }
Escalate nowPOST /workflow/task/<taskId>/escalate-nowFires the next tier at once

What a move answers on an assignRule stage:

  • stageAction: { moved: false, required, approvedBy, waitingOn }: counted, not moved. It moves once required people have signed off. The same person twice counts once (tested).
  • 403 override-reason-required: the person isn't on the stage. An Owner or ConfigAdmin may still act for it with "overrideReason": "<why>", kept on the task, if the person gives one.
  • 422 inputs-required: "Proof link is needed before this moves on from Review." Send the inputs (tested).

Moving back asks for no inputs. A stage without assignRule moves on the first call from anyone on it.

On the clock: the scheduled job workflow-escalation looks for late tasks every 10 minutes. GET /workflow/escalation/breached (late now) and GET /workflow/escalation/upcoming?withinMinutes=60 (due soon) take workflowId=<sk>. When a tier fires: its people are added to assignTo, escalationTier goes up, escalationHistory records it, the reminder goes out, and the next tier's deadline is set. Staff can also run the check at once.

On the test task, make a short tier (1 minute), wait, and check it fired on GET /workflow/task/<taskId>/sla (or press escalate now), then move the task through every stage to an outcome.

Hands on: the test task at an outcome. Check: GET /workflow/task/<taskId> shows escalationTier, escalationHistory and each stage in history; the record shows the outcome's state and status, and workflow.status: done.

5. Workflow: start it for every new record

Set the real people and deadlines back if the test changed them.

Stop: from now on every new record of this type gets a task, and every stage and tier with a template emails its people. Show the person the stages, the people, the deadlines and who is emailed when. Wait for a yes.

PATCH /workflow/definition/<sk> with { "autoFire": true } (and "enabled": true). Records that already exist don't get a task; start any that need one with POST /workflow/fire, one at a time.

To pause it: { "enabled": false }. Nothing new starts, open tasks stay where they are.

Check: create one more test record of the type and see its task appear: GET /workflow/position/<datatype>/<record sk> answers its stage, deadline and history. Then delete the test records and their tasks.

6. Workflow: follow it

  • How long each stage takes: GET /workflow/analytics/<sk>/wait-times?windowDays=30: per stage mean, p50, p95 (milliseconds), the whole flow, how many are open per stage, and overdueCount.
  • When one will finish: GET /workflow/analytics/task/<taskId>/eta.
  • The list: GET /workflow/task?workflowId=<sk> (stageId, status, assignTo, q, includeArchived).
  • In Studio: Workflow (/workflow): Dashboard for the board, Analytics for the times.

When it's done

Tell the person:

  • the workflow's name, its stages in order, who works each and how many must sign off;
  • the fields each stage asks for;
  • each stage's deadline, who is added when it is missed and who is emailed;
  • what the test record did, and that it is gone;
  • whether it now starts for every new record.

Open Workflow (/workflow).

What it can't do yet

  • Business hours. Deadlines count clock time, nights and weekends included.
  • A deadline after the last tier. Once every tier has fired, the task has no due date and is no longer late (tested).
  • Escalate past the last tier. Escalate now still reports success and raises the tier, adding nobody (tested).
  • On-the-minute timing. Late tasks are found every 10 minutes.
  • Text reminders to email addresses. The -sms templates go only to phone numbers; with people named by email they reach nobody.
  • Recompute on restart. POST /workflow/task/restart puts a record's tasks back on the first stage without new people, a new deadline or a message.
  • Run automations from a stage move. Moving a task doesn't fire the record's updated automations.
  • Stop a second task. Starting the workflow again for the same record makes a second task.
  • Count working days in the analytics. Times are milliseconds of clock time.

Don'ts

  • Don't set autoFire: true before the test record has gone through every stage.
  • Don't put real people on the stages or tiers while testing.
  • Don't save a workflow without enabled.
  • Don't start the workflow twice for one record.
  • Don't send an overrideReason the person didn't give, or force a move for them.
  • Don't use this for access or publishing approvals; those go through Approvals.