Workflow format

How a workflow's trigger and steps are written as JSON, for reading the program that Get a workflow returns.

Get a workflow returns a workflow's trigger and steps as program: a tree of JSON nodes, each naming what it is with a kind. You never need to write one: the editor and your AI tool do that. This page explains how to read one.

A program

{
  "kind": "Program",
  "version": 1,
  "inputs": [],
  "body": []
}

body is the list of statements, run in order. When the workflow starts from a trigger or a schedule, that's the first statement. inputs holds the inputs it declares, and version is the version of this format, currently 1.

An example

The Stripe example from these docs, as a program: a trigger, then two app actions that read the payment.

{
  "kind": "Program",
  "version": 1,
  "body": [
    {
      "kind": "LetStmt",
      "name": "payment",
      "init": {
        "kind": "NativeCallExpr",
        "name": "stripe_new_payment_trigger",
        "args": [{ "kind": "Literal", "value": "succeeded" }]
      }
    },
    {
      "kind": "LetStmt",
      "name": "logged",
      "init": {
        "kind": "NativeCallExpr",
        "name": "api.request",
        "args": [
          { "kind": "Literal", "value": "POST" },
          { "kind": "Literal", "value": "/actions/library/google-sheets/append-row" },
          {
            "kind": "Literal",
            "value": {
              "spreadsheet_url": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZ",
              "sheet_name": "Revenue",
              "row_values": [
                { "kind": "NowBuiltIn" },
                { "kind": "MemberExpr", "obj": { "kind": "VarRef", "name": "payment" }, "key": "description" },
                { "kind": "MemberExpr", "obj": { "kind": "VarRef", "name": "payment" }, "key": "amount" }
              ]
            }
          }
        ]
      }
    },
    {
      "kind": "LetStmt",
      "name": "posted",
      "init": {
        "kind": "NativeCallExpr",
        "name": "api.request",
        "args": [
          { "kind": "Literal", "value": "POST" },
          { "kind": "Literal", "value": "/actions/library/slack/send-message" },
          {
            "kind": "Literal",
            "value": {
              "text": {
                "kind": "BinaryExpr",
                "op": "+",
                "left": { "kind": "Literal", "value": "New payment: " },
                "right": { "kind": "MemberExpr", "obj": { "kind": "VarRef", "name": "payment" }, "key": "description" }
              }
            }
          }
        ]
      }
    }
  ]
}

Each step's result is bound to a name with a LetStmt: the trigger's event is payment, which later steps read with MemberExpr nodes. In the editor, those reads are the @payment.description references.

Statements

There are 14 kinds of statement:

kindFieldsIn the editor
LetStmtname, init?A step whose result has a name, such as an action or a calculation
AssignStmtname, valueChanging a value declared earlier
ExprStmtexprA step whose result isn't kept
BlockStmtbodyA group of statements
IfStmtcond, thenBranch, elseBranch?If, with Otherwise as elseBranch. An elseBranch that is itself an IfStmt makes a chain of cases.
ForOfStmtname, iterable, bodyRepeat for each item. name is the current item.
RepeatStmtcount, bodyRepeat N times
WhileStmtcond, bodyRepeat while
BreakStmt, ContinueStmtLeave a loop, or skip to its next pass
TryStmttryBranch, catchBranch, errorName?A try section; see below
StopStmtstatus, message?End the run early, with status success or failure
WhenStmttime, recurrence?, timezone?A schedule at a time of day; see below
FrequencyStmtvalue, unitA schedule every value units (seconds, minutes, hours or days)

Try sections

A TryStmt runs tryBranch. If a step inside it fails, the rest of tryBranch is skipped and catchBranch runs instead, with the failure bound under errorName (error unless set) as { "type": …, "message": … }. The type is action, http, timeout, variable or error. An empty catchBranch lets the run carry on after the failure. A StopStmt inside a try still ends the run. See Handle failures.

Schedules

A WhenStmt runs the workflow at time:

  • With recurrence, time is a time of day ("08:00") and recurrence is an iCalendar rule without the RRULE: prefix, such as "FREQ=WEEKLY;BYDAY=MO,WE,FR" or "FREQ=MONTHLY;BYMONTHDAY=1".
  • Without it, time is the next occurrence of a time of day, or an ISO date and time for a single run.
  • timezone is an IANA name such as "America/New_York". Without one, the schedule follows the workflow owner's time zone.

Expressions

Expressions produce values:

kindFieldsProduces
LiteralvalueA fixed value: text, number, boolean, null, list or object
VarRefnameA named value: a step's result, the current item of a loop, or an input
MemberExprobj, keyA field of an object, such as payment.amount
IndexExprarr, idxAn item of a list by position, from 0; -1 is the last. A position past the end gives null.
UnaryExprop, right- (negate), +, or ! (not)
BinaryExprop, left, right+ - * / % **, comparisons == != < <= > >=, and && ||. + joins text when either side is text.
NativeCallExprname, argsAn app action or a trigger; see Calls
Built-in functionits own fieldsSee Built-in functions

Calls

App actions

An app action is a NativeCallExpr named api.request with three arguments: the method, the action's path, and a payload of its inputs. The path names the app and the action: /actions/library/google-sheets/append-row is Google Sheets' Append row. The keys of the payload are the action's inputs, as listed on its integration page.

In the payload, a fixed value is written as plain JSON, and a value worked out at run time is an expression node in its place, like row_values and text above. Any object in the payload with a string kind is evaluated, at any depth.

Lodol's own Request approval and stored-value steps are calls too, to /actions/core/review/request-approval and /actions/core/variables/get, set, increment and delete.

Triggers

A trigger is a NativeCallExpr named with the trigger's ID, such as stripe_new_payment_trigger, in the first statement. Its args are its filters, in order. The LetStmt around it names the event.

human.* and web.* calls don't run

Workflows written against older versions of this page may contain human.* or web.* calls. They do nothing when a workflow runs. To wait for a person, use the Request approval step.

Built-in functions

A built-in function is a node whose kind ends in BuiltIn, with named fields that are themselves expressions. Each one is a built-in step in the editor.

On this page