Flows: definitions and state types

The flow model — a Step-Functions-style JSON state machine — its nine state types, choice rules, data-flow keys, and a complete example.

A flow is an automation defined as a state machine in the style of AWS Step Functions. You can draw it in the visual designer (Setup → Automation → Flows) or write the JSON directly through the API, CLI (kelta flows) or MCP (create_flow, update_flow). Both produce the same definition.

The flow record#

AttributeMeaning
name, description
flowTypeRECORD_TRIGGERED, SCHEDULED, NATS_TRIGGERED or AUTOLAUNCHED (manual / API / webhook) — see Triggers
triggerConfigJSON specific to the flow type (collection and events, cron, topic)
definitionthe state machine below
activeinactive flows never start
versionincremented on publish; see Runs, versions and retention
runAsUserIdthe user the flow acts as when no initiating user exists

Definition format#

{
  "StartAt": "CheckAmount",
  "States": {
    "CheckAmount": {
      "Type": "Choice",
      "Choices": [
        { "Variable": "$.record.data.amount", "NumericGreaterThan": 10000, "Next": "SubmitForApproval" }
      ],
      "Default": "MarkApproved"
    },
    "SubmitForApproval": {
      "Type": "Task",
      "Resource": "SUBMIT_FOR_APPROVAL",
      "Parameters": {},
      "End": true
    },
    "MarkApproved": {
      "Type": "Task",
      "Resource": "FIELD_UPDATE",
      "Parameters": { "updates": [ { "field": "status", "value": "APPROVED" } ] },
      "End": true
    }
  }
}

Every state has a Type and either Next (the following state) or End: true; Comment is free text. Execution starts at StartAt and runs until a terminal state.

State types#

TypePurposeKeys
TaskRun an action handlerResource (handler name), Parameters, InputPath, ResultPath, OutputPath, Retry, Catch
ChoiceBranch on the state dataChoices[] (rules with Next), Default
ParallelRun branches concurrently, collect their results in an arrayBranches[] (each a full {StartAt, States}), ResultPath, Retry, Catch
MapRun an iterator over each item of an arrayItemsPath, Iterator ({StartAt, States}), MaxConcurrency, FailOnPartial, ResultPath
WaitPauseSeconds, Timestamp, TimestampPath, or EventName
PassInject a static Result or reshape dataResult, InputPath, ResultPath, OutputPath
InvokeFlowRun another flow synchronously and merge its resultFlowId or FlowName, Input, ResultPath, Retry, Catch
SucceedTerminate successfully
FailTerminate with an errorError, Cause

The designer exposes the first eight visually; InvokeFlow is authored in JSON (or through the API/MCP) and is bounded to a nesting depth of 10.

Choice rules#

A rule names a Variable (JSONPath into the state data), one comparison, and Next:

Comparison
StringsStringEquals, StringNotEquals, StringGreaterThan, StringLessThan, StringGreaterThanEquals, StringLessThanEquals, StringMatches (regex)
NumbersNumericEquals, NumericNotEquals, NumericGreaterThan, NumericLessThan, NumericGreaterThanEquals, NumericLessThanEquals
Booleans / presenceBooleanEquals, IsPresent, IsNull
CompositionAnd: [rules], Or: [rules], Not: rule

Default names the state to go to when no rule matches; without it a non-match fails the run with States.NoChoiceMatched.

Data flow#

Every run carries a JSON state envelope; states read and write it through JSONPath:

  • InputPath selects what the state sees (default $, the whole state).
  • Parameters builds the handler’s config; "${$.input.customerId}" substitutes a value from the state.
  • ResultPath says where the state’s result is merged (default $, replacing the state; "$.lookup" nests it).
  • OutputPath selects what is passed on.

The envelope’s shape and the all-important $.input.<key> rule are on Flow data.

Map#

{
  "NotifyEach": {
    "Type": "Map",
    "ItemsPath": "$.lookup.records",
    "MaxConcurrency": 5,
    "FailOnPartial": true,
    "Iterator": {
      "StartAt": "Email",
      "States": { "Email": { "Type": "Task", "Resource": "EMAIL_ALERT", "Parameters": { "to": "${$.email}", "subject": "Hello" }, "End": true } }
    },
    "ResultPath": "$.notified",
    "End": true
  }
}

Each iteration sees the item as its state. FailOnPartial: true fails the run if any iteration fails; otherwise failures are collected in the result.

Invoke another flow#

{
  "Enrich": {
    "Type": "InvokeFlow",
    "FlowName": "enrich-customer",
    "Input": { "customerId": "${$.record.data.customer}" },
    "ResultPath": "$.enriched",
    "Next": "Continue"
  }
}

The sub-flow runs to completion in the same execution context and its final state lands at ResultPath.

Where to go next#

Edit this page on GitHub