Workflow definition
A workflow’s definition is a tree. The trigger is the root, and each step points to the next one.
The tree
There is no list of steps. Each step names the next one in nextAction. A branch, a loop and a step’s error path each hang their own chain off the step.
The example at the end of this page, by step name.
Three things to get right
config, notsettings- A step’s settings go in its
config.settingsis for the whole workflow only; on a step it is refused. - Two type words
- The trigger’s
typeis lowercase, such aswebhook. A step’s is uppercase:PIECE,CODE,BRANCHorLOOP. actionIdon the trigger- The trigger names its trigger in
actionId. There is notriggerId.
Any other key is refused with 400, which names it. Before you save, check the definition: each problem says what would be allowed.
GoRunner fills in a branch’s or loop’s app, a branch’s config.executionMode (FIRST_MATCH, the mode a run uses when none is written) and config.branches, and every valid, below, when you save and when you read. A read leaves the stored copy as it is, and sending back only those fill-ins changes nothing.
To change a draft, read it, change the tree, and send it back whole with Update a workflow’s draft. Send the definitionRev you read as expectedRev: if someone saved since, the answer is 409 and nothing is overwritten.
The definition
schemaVersion- Always
2. A definition saved without it reads as0, and is the same. trigger- The root: how the workflow starts.
settings- The workflow’s own settings. See below.
variables- Values any step reads as
{{$vars.NAME}}. Not secret: every read and export shows them. Secret ones are kept apart, in the variables. annotations- The canvas’s notes, kept as they are.
pinnedData- Old. Don’t write it.
The trigger
name- Its address, usually
trigger. No step may share it. typemanual,cron,webhook,event,error,run_finishedorpoll. A form useswebhook.pieceId,actionId- The app and its trigger, such as
webhookandcatch_webhook. Get an app lists them. config- The trigger’s settings, by field name: Get a trigger’s fields.
connectionId- For a trigger that registers itself with a service.
nextAction- The first step.
displayName- The label on the canvas.
pieceVersion,valid- Leave
pieceVersionout: GoRunner pins the version its workers run, and never changes it after.validis worked out by GoRunner:trueonce an app and its trigger are chosen. It checks nothing, and a value you send isn’t kept.
A step
name- Its address in expressions:
{{steps.total.result}}. Unique in the whole tree, and at most 255 characters. Renaming it breaks the expressions that use it. typePIECEruns an app’s action.CODEruns JavaScript.BRANCHandLOOPhold chains.pieceId,actionId- Such as
httpandsend_request.CODEiscode/run_code,BRANCHisbranch/evaluate,LOOPisloop/loop_on_items. Leave them out on aBRANCHorLOOPand GoRunner fills them in. config- The action’s settings, by field name: Get an action’s fields. Also how it handles errors, below.
connectionId- A connection’s ID, in the same workspace. Never put secrets in
config. Publishing needs it when the app needs a connection. skiptrueskips this step only. The next one still runs.nextAction- The next step. It doesn’t run when the error path does.
branchesBRANCHonly: its arms, in order. Leaveconfig.branchesout and GoRunner copies the arms there, when they have conditions. If you write it, list the same arms in the same order: a run checksconfig.branchesand runs the arm at the same place here.firstLoopActionLOOPonly: the first step run for each item.onErrorAction- The first error step.
displayName,pieceVersion,valid- As on the trigger, with
displayNameat most 255 characters. ABRANCH,LOOPorCODEstep is alwaysvalid.
Characters are counted, not bytes. A longer name or displayName is refused with 400 when you create, update or import a workflow or use a template, and checking the definition reports step_name_too_long. One already stored stays: you can read it, save it back and publish it, with a warning. A run that reaches that step can’t save its steps until you shorten it.
Error handling, in config
onErrorstopends the run, failed. This is the default.continuegoes on to the next step.errorPathrunsonErrorActioninstead; without one it stops.maxRetries- 0 to 5. 0 unless set.
timeoutSeconds- Seconds for each try, up to 600. 30 unless set, or what the app needs.
Only PIECE and CODE steps have them: a branch or loop that fails always stops the run. An error step reads the reason as {{steps.total.error}}, and the run ends Worked. See Handle errors.
Branches
Arms are tried in order, and the first that matches runs. Set config.executionMode to ALL_MATCH to run every arm that matches, in order. FIRST_MATCH is the default. Then the run goes on at the branch’s nextAction.
conditions- Groups of rules:
[[A, B], [C]]means (A and B) or C. An arm with no conditions never matches by itself: it is the else path, run only when no other arm matched. - A rule
{"field": "{{…}}", "operator": "num_gt", "value": 1000}.valueis left out for operators that take none.- Otherwise
- Saving gives a branch an arm named
Otherwisewith no conditions, last, if it has none. Without it, a run that matches nothing would do nothing.
Every operator (40)
exists, not_exists, is_empty, is_not_empty, text_eq, text_neq, text_contains, text_not_contains, text_starts_with, text_not_starts_with, text_ends_with, text_not_ends_with, text_is_empty, text_is_not_empty, regex_match, regex_not_match, num_eq, num_neq, num_gt, num_gte, num_lt, num_lte, date_eq, date_neq, date_before, date_before_or_eq, date_after, date_after_or_eq, bool_is_true, bool_is_false, list_contains, list_not_contains, list_is_empty, list_is_not_empty, list_length_eq, list_length_neq, list_length_gt, list_length_gte, list_length_lt, list_length_lte
Any other operator is refused with 400 when you create, update or import a workflow or use a template. One already stored stays: you can read it and save it back, publishing it is refused, and its step fails if it runs. An empty operator waits for publish.
To match one value against several cases, use the Switch app.
Loops
config.items is an expression that gives a list, such as {{trigger.body.items}}. Each step inside reads {{steps.each_item.item}}, with index and total beside it. batch_size hands over several items at once, max_iterations caps the loop, and timeoutSeconds caps one turn.
The workflow’s settings
timeoutSeconds- Caps the whole run. 5 minutes unless set, at most 1 hour. A synchronous webhook then answers
504. concurrencyLimit- Production runs at once, counting runs that are paused. 0 or left out means no limit.
whenFull- At the limit:
waitqueues the run, the default.refuseturns it away; an API or webhook caller gets429 CONCURRENCY_LIMIT. errorWorkflowId- A published workflow in the same workspace, run when this one fails.
maintenanceMode- The workflow’s Maintenance mode: after repeated identical failures, Keel drafts a fix. It waits in the draft for you.
autoPublishFixes- Saved, but not used yet: a fix always waits in the draft.
A complete example
The tree above. It passes the check, and it runs: when the items cost over 1,000, it loops over them. When total fails, only note_failure runs.
{
"schemaVersion": 2,
"trigger": {
"name": "trigger",
"type": "webhook",
"pieceId": "webhook",
"actionId": "catch_webhook",
"config": { "methods": ["POST"] },
"nextAction": {
"name": "total",
"type": "CODE",
"pieceId": "code",
"actionId": "run_code",
"config": {
"code": "return trigger.body.items.reduce((s, i) => s + i.price, 0);",
"onError": "errorPath",
"maxRetries": 2,
"timeoutSeconds": 60
},
"onErrorAction": {
"name": "note_failure",
"type": "PIECE",
"pieceId": "text",
"actionId": "transform",
"config": { "operation": "uppercase", "text": "{{steps.total.error}}" }
},
"nextAction": {
"name": "big_order",
"type": "BRANCH",
"pieceId": "branch",
"actionId": "evaluate",
"config": {},
"branches": [
{
"name": "Over 1,000",
"conditions": [[
{ "field": "{{steps.total.result}}", "operator": "num_gt", "value": 1000 }
]],
"firstAction": {
"name": "each_item",
"type": "LOOP",
"pieceId": "loop",
"actionId": "loop_on_items",
"config": { "items": "{{trigger.body.items}}" },
"firstLoopAction": {
"name": "item_name",
"type": "PIECE",
"pieceId": "text",
"actionId": "transform",
"config": { "operation": "uppercase", "text": "{{steps.each_item.item.name}}" }
}
}
},
{ "name": "Otherwise", "conditions": [] }
]
}
}
},
"settings": { "timeoutSeconds": 300, "concurrencyLimit": 5, "whenFull": "wait" },
"variables": { "CURRENCY": "EUR" }
}