Flows reference

Every node a flow can use, what expressions can read, and the limits every run works within. This page is generated from the flows engine itself (version 1.0.0), so it always describes the version that's running.

The flow document

  • versionAlways 1.
  • nameLower-case letters, digits and hyphens, unique in your account.
  • descriptionOptional.
  • entryHow the flow starts. An endpoint: { "type": "endpoint", "method": "POST", "path": "...", "public": false, "input": { JSON schema } }. An event flow: { "type": "event", "events": [...], "lists": [...], "when": "...", "ignoreUserMode": false } (see event flows). By hand: { "type": "manual" }.
  • nodesThe nodes: each has an id (letters, digits and underscores), a type, an optional label, and the fields its type takes.
  • edges["from", "to"], or ["from", "to", "port"] from a condition or switch. A flow can't have cycles.
  • helpersOptional: let and function declarations every expression can use.
  • layoutWhere the nodes sit in the editor. It never affects a run.

Nodes

A field of kind expression is written in the write rules language, as a string: "steps.game.data.deckId". A list is a path name or id, written plainly.

Reading and writing items

get-item

Read an item, with the list owner's privileges: guarded values included.

Gives: the item, { id, identityId, version, description, tags, readonly, createdAt, updatedAt, data }, or null.

FieldKindWhat it is
listlistThe list, by id or path name. (required)
itemexpressionThe item, by id or alias. (required; gives string)
requiredbooleanFail the flow with 404 if there is no such item, rather than giving null. (default false)

find-items

Find items by an index value. Only indexed fields can be searched, so it's always fast.

Gives: an array of items, each { id, identityId, version, description, tags, readonly, createdAt, updatedAt, data }.

FieldKindWhat it is
listlistThe list, by id or path name. (required)
indexstringThe index to search, by path name. It must be a filtering index. (required)
valueexpressionFind items whose indexed value equals this. (required)
limitintegerThe most items to return. (default 10; 1 to 25)
orderone ofOldest first (asc) or newest first (desc). (default "asc"; asc, desc)

create-item

Create an item. Variables are substituted and the list's JSON schema is checked, as for any write.

Gives: the created item, { id, identityId, version, description, tags, readonly, createdAt, updatedAt, data }.

FieldKindWhat it is
listlistThe list, by id or path name. (required)
dataexpressionThe item's data. (required)
identityIdexpressionThe identity that owns the new item, e.g. identity.id. Default: nobody. (gives string | null)
applyRulesbooleanCheck the list's write rules, as if the caller had made the write. By default a flow's writes bypass them: the flow's require nodes are its authorisation. (default false)

update-item

Update an item: replace its data, merge into it, or apply a JSON Patch.

Gives: the item as it is after the update, { id, identityId, version, description, tags, readonly, createdAt, updatedAt, data }.

FieldKindWhat it is
listlistThe list, by id or path name. (required)
itemexpressionThe item, by id or alias. (required; gives string)
dataexpressionThe item's new data, replacing all of it.
mergeexpressionA JSON merge patch: fields to set, and null for fields to remove. (gives object)
patchexpressionA JSON Patch: [{ op: 'remove', path: '/cards/0' }, ...] (gives array)
ifVersionexpressionRefuse the write (409) unless the item is at this version, e.g. steps.game.version. (gives string)
applyRulesbooleanCheck the list's write rules, as if the caller had made the write. By default a flow's writes bypass them: the flow's require nodes are its authorisation. (default false)

delete-item

Delete an item.

Gives: the item as it was before it was deleted, { id, identityId, version, description, tags, readonly, createdAt, updatedAt, data }.

FieldKindWhat it is
listlistThe list, by id or path name. (required)
itemexpressionThe item, by id or alias. (required; gives string)
applyRulesbooleanCheck the list's write rules, as if the caller had made the write. By default a flow's writes bypass them: the flow's require nodes are its authorisation. (default false)

Deciding

compute

Work out a value once, for later nodes to use as steps.<id>.

Gives: the value.

FieldKindWhat it is
valueexpressionThe value. (required)

condition

Go one way or the other: the edges out of it are labelled "true" and "false".

Gives: true or false. Outputs: true, false.

FieldKindWhat it is
whenexpressionWhich way to go. (required; gives boolean)

switch

Go the way of the first case that is true, or "default" if none is.

Gives: the name of the port taken.

FieldKindWhat it is
casescasesThe cases, in order: [{ "when": "<expression>", "port": "<name>" }]. Each port names the edges to follow. (required; 1 to 20)

require

Stop the flow unless something is true. Everything it's written is rolled back, and an endpoint responds with the status and message.

Gives: true (a require that fails stops the flow).

FieldKindWhat it is
whenexpressionWhat must be true for the flow to carry on. (required; gives boolean)
statusintegerThe HTTP status to fail with: 403 for "not allowed", 409 for "not now", and so on. (default 400; 400 to 499)
messagestringThe error message. (default "The flow refused the request")

Looping

for-each

Run a small graph once for each element of an array (at most 50). Inside it, each is the element and index its position.

Gives: an array: one result per element.

FieldKindWhat it is
itemsexpressionThe array to go through. (required; gives array)
bodygraphThe graph to run for each element: { "nodes": [...], "edges": [...] }. Its nodes can use each, index, and the steps before the for-each. (required)
resultexpressionWhat each iteration gives, evaluated after the body with the body's steps in scope. Default: an object of the body's step outputs.

Output

respond

Set the endpoint's response. It's sent once everything else has run and the writes are committed. Without one, an endpoint responds 204.

Gives: the response body. Only in endpoint flows. Not allowed inside a for-each.

FieldKindWhat it is
statusintegerThe HTTP status. Use a require node to fail. (default 200; 200 to 299)
bodyexpressionThe response body, as JSON.
headersheadersExtra response headers: x-* headers and cache-control.

emit

Send an event to webhooks you already have, signed and retried like any webhook delivery. Sent only if the run succeeds.

Gives: null.

FieldKindWhat it is
webhooksstringsThe webhook ids. (required; 1 to 5)
payloadexpressionThe event's data.

log

Write a value to the run's log, for debugging.

Gives: null.

FieldKindWhat it is
valueexpressionThe value to log. (required)

What expressions can read

Besides these, expressions can use every function in the write rules language, including lookup and exists, and the flow's helpers.

  • inputanyWhat the flow was started with. For an endpoint, the request body (or query, for GET). For an event, the event: { type, list, item, previous, actor }, the same as a webhook body.
  • stepsobjectThe outputs of the nodes that ran before this one, by node id: steps.game.data.status. A node that was skipped (its branch wasn't taken) is null.
  • flowobjectAbout this run: { name, runId, eventId, depth, dryRun }. eventId is the same for every retry of an event run, so it makes a good idempotency key.
  • identityobject | nullThe identity making the request, or null. A flow does its own authorisation with require nodes. For an event flow, the identity that made the change, if one did.
  • tokenobject | nullThe API token making the request: { id, tags }, or null for a public flow. For an event flow, the token that made the change, if one did.
  • nowtimestampThe time of the run. The same instant for every node, and the same as the timestamps on the items the run writes.
  • oldanyThe item's data before the event, or null. With new, it's what changed(), unchanged() and onlyChanged() compare. Only in event flows.
  • newanyThe item's data after the event, or null on delete. Only in event flows.
  • eachanyInside a for-each body: the current element.
  • indexnumberInside a for-each body: the position of the current element, from 0.

Limits

The same on every plan.

  • 100 nodesin a flow, counting each node in a for-each's body once
  • 128 KiBfor a flow document, as JSON
  • 3 for-each nodesnested inside each other
  • 4,096 charactersin one expression
  • 20 casesin a switch
  • 50 elementsper for-each, each time it runs
  • 50 reads and writesper run, lookups included
  • 25 writesper run: creates, updates and deletes
  • 25 itemsfrom one find-items
  • 10 emitsper run, each to at most 5 webhooks
  • 50 log entriesper run
  • 256 KiBfor a value a run writes, responds with, emits or logs
  • 100,000 unitsof evaluation budget per expression, and 1,000,000 for a whole run (the same units as write rules)
  • 5 secondsfor an endpoint flow, and 30 seconds for an event flow
  • 100 testsin a flow's stored tests, and 512 KiB

Why a run fails

An endpoint flow that fails responds with a FLOW_FAILED error, whose details.code is one of these, and whose details.node is the node it failed at. The run log shows the same.

  • REQUIRE_FAILEDA require node's condition was false. The run fails with the node's status and message.
  • NOT_FOUNDA get-item with required found nothing (404).
  • EVALUATION_ERRORAn expression failed, e.g. comparing a number with a string, or a condition that wasn't true or false (400).
  • LIMIT_EXCEEDEDThe run did too many reads, writes, iterations, emits or logs (422).
  • BUDGET_EXCEEDEDThe expressions ran out of evaluation budget (422).
  • TIMEOUTThe run took too long (504).
  • LIST_NOT_FOUNDA node named a list that doesn't exist (500: the flow needs fixing, not the request).
  • ITEM_NOT_FOUNDAn item a write needed isn't there (404).
  • ITEM_CONFLICTAn update-item's ifVersion didn't match the item's version (409).
  • VALIDATION_ERRORA write didn't match its list's JSON schema (400).

A write can also fail the way the same API request would, e.g. MAX_ITEMS_EXCEEDED or ALIAS_IN_USE, or with your list's write rules on a node with applyRules.

2026-09-27