Loopfour

Workflow definition format

Read the workflow definition format: the trigger, the steps and their types, how steps connect, and the portable export used for import.

A workflow is a JSON document: one trigger, an ordered list of steps, and a few optional fields. You send it to POST /workflows to create a workflow, and you get the same shape back from GET /workflows/:id. Studio builds the same document when you edit on the canvas.

Top-level fields

FieldTypeRequiredMeaning
namestring, 1 to 255 charactersYesShown in Studio and in run records
descriptionstringNoWhat the workflow is for
triggerobjectYesWhat starts a run. See Trigger
stepsarray, at least oneYesThe steps. See Steps
inputSchemaJSON Schema objectNoChecks the input of API and Studio runs (see below)
outputSchemaJSON Schema objectNoStored with the workflow
settingsobjectNoRun resources. See Settings

When inputSchema is set, POST /workflows/:id/run checks the required list and the top-level type of each property, and answers 400 with code VALIDATION_ERROR when the input does not match. It does not check nested properties, patterns or ranges.

Trigger

trigger.type picks the trigger. Each type has its own fields; the trigger pages list them.

typeStarts a run whenReference
apiYour system calls POST /workflows/:id/runAPI trigger
scheduleA cron expression or interval comes dueSchedule trigger
webhookAn event arrives from a connected provider or on a custom webhook URLWebhook trigger
email_eventAn email reaches the workflow's own inbound addressInbound email trigger
hubspot_event, airwallex_eventA HubSpot or Airwallex event arrives for a connected accountApp event triggers
slack_event, slack_slash_command, slack_interactiveSomething happens in a connected Slack workspaceSlack triggers

The schema also accepts salesforce_event, gmail_event and connection_event. They match events forwarded from those connected accounts and are not covered in these docs.

Whatever its trigger, an active workflow can also be started with POST /workflows/:id/run or with Run in Studio.

Steps

Each step has these fields:

FieldTypeRequiredMeaning
idstringYesUnique within the workflow. Later steps read its output as {{steps.<id>.<path>}}
typestringYesOne of the step types below
namestringYesShown in Studio and in the run record
actionstringFor type: "action"<block>.<operation>, for example quickbooks.createInvoice
configobjectYesThe step's settings. Values may contain variable references
errorHandlingobjectNo{ "strategy": "fail" | "continue" | "retry", "fallback": { … } }. See Errors, retries and timeouts
terminalbooleanNoWhen true, the run ends after this step
stagestringNoA stage title shown on the run page (top-level steps only)
outcomestringNoA plain-language outcome line shown for this step on the run page
inputSchema, outputSchemaJSON Schema objectNoWhen set, the step's resolved config and its output are checked against them; a mismatch fails the step
providerstringNoNames the provider the step needs, when it cannot be inferred from action
retryConfig, timeoutMsobject, numberNoAccepted, but not applied by the run engine today

Step types

typeWhat it doesReference
actionRuns one operation of a block: an integration action, or a core block such as PDF, Stagehand or API RequestIntegration actions, Block reference
conditionEvaluates a condition and runs the step ids in then or elseCondition block
loopRuns the listed steps once per element of an arrayLoop block
parallelRuns branches at the same timeParallel block
approvalPauses the run until a person approves or rejectsApproval block
waitPauses for a duration or until a timeWait block
transformMaps or filters dataTransform block
agentRuns an AI Agent stepAI Agent block
codeRuns JavaScript in a sandboxCode block
subworkflowRuns another workflowSub-Workflow block
data_tableReads or writes a Loopfour tableData Table block
workspace_filesReads or writes a workspace fileWorkspace Files block
slack, email, gmailSends or reads messages through those blocksSlack, Email

Integration steps

An integration step uses type: "action" and action: "<block>.<operation>". The block's fields go in config. To run against one account when the company has several for that provider, set config.connection to a connection id from GET /connections; without it the step uses the provider's default connection.

To find the valid blocks, operations and config fields, call GET /blocks and GET /blocks/:type. See Block and action discovery.

How steps run

  • Top-level steps run in array order.
  • A step listed in a Condition's then or else, a Loop's steps or a Parallel branch runs only from there, not again at the top level.
  • A step with terminal: true ends the run after it finishes.
  • The run's output is the output of the terminal step when one ended the run. Otherwise it is the output of the last step in steps, or an empty object when that step did not run.
  • When a step fails and its errorHandling.strategy is not continue, the run stops and its status becomes failed.

Settings

FieldTypeMeaning
settings.machinestandard, micro, small-1x, small-2x, medium-1x, medium-2x, large-1x or large-2xCompute size for each run. standard or no value uses the platform default
settings.maxDurationSecondsinteger, at least 5Wall-clock limit per run. The run is stopped when it is reached
settings.timeoutMs, settings.maxRetries, settings.concurrencyLimitnumberAccepted, but not applied by the run engine today

A Sub-Workflow step can override machine and maxDurationSeconds for the child run it starts.

Complete example

A bill check: bills over $10,000 wait for the controller before the record step runs.

{
  "name": "Check vendor bill",
  "description": "Sends large bills to the controller before recording them",
  "trigger": { "type": "api" },
  "inputSchema": {
    "type": "object",
    "required": ["billNumber", "vendor", "amount"],
    "properties": {
      "billNumber": { "type": "string" },
      "vendor": { "type": "string" },
      "amount": { "type": "number" }
    }
  },
  "steps": [
    {
      "id": "check_amount",
      "type": "condition",
      "name": "Over $10,000?",
      "config": {
        "conditions": { "left": "{{input.amount}}", "operator": "gt", "right": "10000" },
        "then": ["controller_approval"]
      }
    },
    {
      "id": "controller_approval",
      "type": "approval",
      "name": "Controller approves",
      "config": {
        "approvers": ["controller@example.com"],
        "reason": "Bill {{input.billNumber}} from {{input.vendor}} for {{input.amount}}",
        "timeout": "3d",
        "timeoutAction": "reject"
      }
    },
    {
      "id": "record",
      "type": "transform",
      "name": "Record the bill",
      "config": {
        "data": {
          "billNumber": "{{input.billNumber}}",
          "vendor": "{{input.vendor}}",
          "amount": "{{input.amount}}"
        }
      }
    }
  ]
}

When the amount is $10,000 or less, the condition is false and controller_approval is skipped. When the controller rejects, or the three days pass, the approval step fails and the run stops before record.

Check a definition before you save it

POST /workflows/validate (scope workflows:read) checks a definition and writes nothing. Send a workflow body or a portable file. The response has valid, plus errors and warnings, each with a code, a message and a JSON path.

Workflow status

StatusRuns start?Move to
draftNoactive (POST /workflows/:id/activate), archived
activeYespaused (POST /workflows/:id/pause), archived
pausedNoactive (activate again), archived
archivedNodraft (POST /workflows/:id/unarchive)

Activating is what Deploy does in Studio. It checks the steps, publishes the current definition as a new major version (v1.0, v2.0 and so on) when it changed, and sets up the trigger: the schedule for a schedule trigger, the address for an inbound email trigger. Archiving removes the schedule and the inbound address. See the Workflows API for versions and rollback.

Portable export format

To move a workflow between companies, use the portable format. It is the definition with "$schema": "loopfour.workflow-export.v1" added and the company-specific fields (id, status, version, timestamps, creator) removed. It may also carry canvasState, the Studio layout.

{
  "$schema": "loopfour.workflow-export.v1",
  "name": "Check vendor bill",
  "trigger": { "type": "api" },
  "steps": [ ... ],
  "inputSchema": { ... },
  "settings": {}
}

The file without $schema and canvasState is a valid POST /workflows body. Connections are not part of the file: after import, connect the systems the steps use in the new company, or the steps that need them fail at run time.

On this page