Docs

Flow JSON reference

Every field and step of an attention.run flow: selectors, targets, conditions, params, outputs and handlers, generated from the schema the hub validates with.

Last updated 15 October 2026

A flow is a JSON document: a slug, optional params and outputs, optional handlers, and a list of steps. Steps name elements of a library by their slugs; the runner finds them on the screen by their pixels. The hub, the attn CLI and the runner check flows against the same schema, which this page is generated from.

Add "$schema": "https://attention.run/schema/flow-v1.json" to a flow file and editors that read JSON Schema (VS Code among them) check it as you type.

{
  "$schema": "https://attention.run/schema/flow-v1.json",
  "flow": "acme/export-invoices",
  "name": "Export invoices",
  "library": "acme",
  "params": { "month": { "type": "string", "default": "2026-09" } },
  "outputs": { "exported": { "type": "number" } },
  "defaults": { "timeout": 10000 },
  "handlers": [
    {
      "id": "cookies",
      "when": { "visible": "global/cookie-accept" },
      "do": [{ "click": "global/cookie-accept" }],
      "max": 1
    }
  ],
  "steps": [
    { "call": "acme/sign-in" },
    { "click": "nav/invoices" },
    { "type": "{{params.month}}", "into": "invoices/month", "clear": true },
    { "scroll": { "dy": 400, "until": { "visible": "invoices/export" }, "max": 10 } },
    { "find": "invoices/row", "as": "rows", "all": true },
    { "set": { "exported": "{{vars.rows.count}}" } },
    { "if": { "var": "rows.count", "eq": 0 }, "then": [{ "pass": "nothing to export" }] },
    { "click": "invoices/export", "id": "export" },
    { "waitFor": { "visible": "toast/export-started" }, "timeout": 5000, "optional": true },
    { "assert": { "gone": "invoices/spinner" }, "timeout": 60000 }
  ]
}

The flow

FieldTypeRequiredLimitsWhat it is
$schemastringThe schema's URL, https://attention.run/schema/flow-v1.json, so editors check the file as you type. Ignored by the hub.
flowstringyesup to 120 charactersThe flow’s slug, such as fixture/sign-in: lowercase words and /. Pushing a source with a slug that exists saves a new version of that flow.
namestringup to 200 charactersA readable name.
descriptionstringup to 5000 charactersWhat the flow does, for people and for agents choosing a flow.
librarystringup to 60 charactersThe library its element slugs resolve in (a slug or an alias). Default: the default library.
params{ [name]: Param }Inputs, by name: given with each run (attn run --param, the API, a trigger, a call). See "Params and outputs".
outputs{ [name]: Output }What a run returns, by name: the var of the same name when the flow ends. See "Params and outputs".
defaults{ timeout? }timeout: the timeout of every step that doesn't set its own, in ms (default 10000).
handlersHandler[]Steps that run whenever a condition becomes true during the run, such as a cookie banner or a "session expired" dialog. See "Handlers".
stepsStep[]yesat least 1 itemThe steps, in order. At least one.

Steps

Each step is an object with one action key (click, if, …) and that action’s fields, plus any of the fields every step takes.

Fields of every step

FieldTypeRequiredLimitsWhat it is
idstringA stable name for reports, breakpoints and runs get; else the step is named by its path (steps[3]). Unique in the flow.
namestringup to 200 charactersA label shown in the editor and the report.
timeoutinteger0–600000How long the step may wait, in ms: for its element, its condition, or the clipboard. Default: the flow's defaults.timeout, else 10000. 0 decides on the current frame without waiting.
optionalbooleanA failure is recorded but doesn’t fail the run.
retry{ times, backoffMs? }{ times, backoffMs }: run the step again after a failure, up to times more times, backoffMs apart (default 500).
commentstringA note for people; ignored when the flow runs.
disabledbooleanSkip the step.

Actions

click

Waits for the target, then clicks its click point (the element’s, else the centre of the match).

FieldTypeRequiredLimitsWhat it is
clickTargetyesWhat to click.
button"left" | "right" | "middle"The mouse button (default left).
{ "click": "login/sign-in" }

doubleClick

Waits for the target, then double-clicks it.

FieldTypeRequiredLimitsWhat it is
doubleClickTargetyesWhat to double-click.
{ "doubleClick": "invoice/number" }

rightClick

Waits for the target, then right-clicks it.

FieldTypeRequiredLimitsWhat it is
rightClickTargetyesWhat to right-click.
{ "rightClick": "files/report" }

hover

Waits for the target, then moves the pointer onto it.

FieldTypeRequiredLimitsWhat it is
hoverTargetyesWhere to move the pointer.
{ "hover": "nav/settings" }

drag

Presses the left button at from, moves to to and releases.

FieldTypeRequiredLimitsWhat it is
drag{ from, to }yesfrom and to: where the drag starts and ends.
durationMsinteger0–600000How long the move takes, in ms.
{ "drag": { "from": "board/card", "to": "board/done-column" } }

type

Types text. With into, clicks the target first. \n presses Enter.

FieldTypeRequiredLimitsWhat it is
typeTextyesThe text (with {{params.x}} and {{vars.x}} placeholders), or { "param": "name" } for a flow param.
intoTargetClick this first, to focus it.
clearbooleanSelect all and delete what the field holds before typing.
{ "type": { "param": "user" }, "into": "login/username" }

press

Presses a key or a chord.

FieldTypeRequiredLimitsWhat it is
pressstringyesA key or chord: Enter, Tab, Control+A, Meta+Shift+P. ControlOrMeta is Command on macOS targets and Control elsewhere.
{ "press": "Enter" }

navigate

Opens a URL. Chrome targets only.

FieldTypeRequiredLimitsWhat it is
navigatestringyesThe URL, with placeholders.
{ "navigate": "https://example.com/login" }

scroll

Scrolls by dx / dy pixels; with until, again and again until the condition holds.

FieldTypeRequiredLimitsWhat it is
scroll{ at?, dx?, dy?, until?, max? }yesat: where the wheel turns (default: the centre of the screen); dx, dy: pixels per scroll; until: a condition that ends it; max: the most scrolls with until.
{ "scroll": { "dy": 400, "until": { "visible": "invoices/export" }, "max": 10 } }

Waits and checks

waitFor

Waits until a condition holds, on frames from after the last action. Times out with TIMEOUT.

FieldTypeRequiredLimitsWhat it is
waitForConditionyesThe condition.
stableForinteger0–60000The condition must keep holding for this long, in ms, before the wait ends.
{ "waitFor": { "visible": "welcome/heading" }, "timeout": 5000 }

waitStable

Waits until nothing changes in region (default: the whole screen) for forMs.

FieldTypeRequiredLimitsWhat it is
waitStable{ region?, forMs }yesregion (a rect, optional) and forMs.
{ "waitStable": { "forMs": 500 } }

wait

Pauses for a fixed time, in ms. The validator warns about it: wait for what you expect to see instead.

FieldTypeRequiredLimitsWhat it is
waitintegeryes0–600000Milliseconds.
{ "wait": 1000 }

assert

Like waitFor, but a timeout fails the step with ASSERTION and the message.

FieldTypeRequiredLimitsWhat it is
assertConditionyesThe condition.
messagestringWhat the failure says.
{ "assert": { "gone": "login/sign-in" }, "message": "still on the sign-in page" }

Values

find

Waits for an element and stores what it found in a var: its position, size, score and state (the variant’s label). With all, every instance in reading order.

FieldTypeRequiredLimitsWhat it is
findSelectoryesThe element.
asstringyesThe var to store it in.
allbooleanStore every instance: { found, element, count, items }.
{ "find": { "element": "settings/toggle", "index": 0 }, "as": "toggle" }

read

Reads text into a var: the screen’s text in an area, by OCR on the runner ({ "text": area }), or the target’s clipboard (clipboard: the text copied after the last action started, so never what was there before the copy). The area is an element (the part its mask ignores, else all of it), { "element", "rect" } (a rect from its top-left; x and y may be negative), { "var", "rect"? } (a found item’s box, or a rect from it) or { "rect" } (screen pixels). A screen read waits until the area holds still.

FieldTypeRequiredLimitsWhat it is
read"clipboard" | { text }yesThe source: { "text": area } or clipboard.
asstringyesThe var to store the text in.
trimbooleanClipboard only: strip whitespace at both ends.
matchesstringScreen text only: a regular expression the text must match. The read waits for it, and with a group the var is the group’s text.
{
  "read": {
    "text": { "element": "invoice/number-label", "rect": { "x": 90, "y": 0, "width": 160, "height": 20 } }
  },
  "as": "invoiceNo",
  "matches": "INV-(\\d+)"
}

set

Sets vars. A value that is exactly one placeholder copies that value with its type; anything else becomes text.

FieldTypeRequiredLimitsWhat it is
set{ [name]: string }yesVar names and their values.
{ "set": { "greeting": "Hello {{params.user}}", "first": "{{vars.rows.items.0}}" } }

Control

if

Checks the condition once, now, and runs then or else.

FieldTypeRequiredLimitsWhat it is
ifConditionyesThe condition.
thenStep[]yesSteps when it holds.
elseStep[]Steps when it doesn’t.
{ "if": { "visible": "login/error" }, "then": [{ "fail": "Sign-in was refused" }] }

switch

Waits for the first case whose condition holds (in order, on the same frame) and runs its do. At the timeout, runs else, or fails with TIMEOUT without one.

FieldTypeRequiredLimitsWhat it is
switch{ when, do }[]yesat least 1 itemThe cases: { when, do }.
elseStep[]Steps when no case held by the timeout.
{
  "switch": [
    { "when": { "visible": "welcome/heading" }, "do": [{ "pass": "signed in" }] },
    { "when": { "visible": "verify/code" }, "do": [{ "call": "fixture/verify" }] }
  ]
}

repeat

Runs do a number of times, until a condition holds (checked after each pass) or while it holds (checked before). max caps until and while (default 100); going over fails the step.

FieldTypeRequiredLimitsWhat it is
repeat{ times?, until?, while?, max?, as? }yesOne of times, until, while; max; as, a var with the pass number (0, 1, …).
doStep[]yesThe steps to repeat.
{
  "repeat": { "until": { "gone": "list/spinner" }, "max": 20 },
  "do": [{ "scroll": { "dy": 300 } }]
}

forEach

Runs do once for each item of a list var, such as find … all’s items.

FieldTypeRequiredLimitsWhat it is
forEach{ in, as }yesin: the var holding the list; as: the var each item is in.
doStep[]yesThe steps.
{
  "forEach": { "in": "rows", "as": "row" },
  "do": [{ "click": { "element": "row/edit", "within": "row" } }]
}

try

Runs try; a step failure there runs catch (with vars.error: its code, message and path) instead of failing the run. finally runs after both.

FieldTypeRequiredLimitsWhat it is
tryStep[]yesThe steps to try.
catchStep[]Steps after a failure.
finallyStep[]Steps that always run after.
{ "try": [{ "click": "dialog/close", "timeout": 2000 }], "catch": [{ "press": "Escape" }] }

call

Runs another flow inline, on the same screen, with its own params, vars and handlers. Its pass returns here; its failure fails this step. At most 16 calls deep.

FieldTypeRequiredLimitsWhat it is
callstringyesup to 120 charactersThe called flow’s slug.
versionintegerat least 1A version of it (default: its latest version when the run started).
params{ [name]: Value }Its params. Strings take placeholders; exactly one placeholder keeps the value’s type.
asstringA var for its result: { status, outputs, message?, error? }.
{ "call": "fixture/sign-in", "params": { "user": "{{params.user}}" }, "as": "signin" }

pass

Ends the run as passed, with this message.

FieldTypeRequiredLimitsWhat it is
passstringyesThe run’s message.
{ "pass": "no invoices to export" }

fail

Fails the step with this message (FAILED), which fails the run unless something absorbs it (optional, try).

FieldTypeRequiredLimitsWhat it is
failstringyesThe error message.
{ "fail": "Sign-in was refused" }

Waiting and matching

Every step that acts on an element waits for it first, up to the step’s timeout. A wait after an action only looks at frames from after that action, so a dialog that was already on screen doesn’t end the wait for the next one.

An element that matches in more than one place fails with AMBIGUOUS, and the error lists the places: pick one with index, within or region, or cut a larger element.

Selectors

A selector names an element: its slug as a string, or an object with the slug and narrowing fields.

FieldTypeRequiredLimitsWhat it is
elementstringyesThe element’s slug or one of its aliases.
variantstringOnly this variant, by its label (a state such as on, checked) or id.
indexintegerat least 0The nth instance in reading order, from 0, for elements that appear several times.
withinstringOnly instances inside a found item in the vars (row, rows.items.2), such as the Edit icon in this row.
regionRectOnly instances inside this screen rect: { x, y, width, height }.

Targets

Actions take a target: where the pointer goes.

FormWhat it is
"login/sign-in"An element (or a selector object): its click point.
{ "element": "login/username", "offset": { "dx": 120, "dy": 30 } }A point at an offset from the element’s top-left corner.
{ "x": 640, "y": 360 }Screen coordinates. No waiting: use them for what has no element.
{ "var": "row", "offset": { "dx": 8, "dy": 8 } }A found item in the vars: its centre, or an offset from its top-left. No waiting either.

Conditions

waitFor, assert, if, switch, repeat, scroll.until and handlers take a condition. The value tests of var and param are eq, ne, gt, gte, lt, lte, matches, exists (at most one).

ConditionWhat it meansExample
visibleThe element is on screen.{ "visible": "welcome/heading" }
goneThe element is not on screen.{ "gone": "list/spinner" }
screenThe library’s screen is up: every element its signature says shows is on screen, and none it says doesn’t.{ "screen": "checkout/payment" }
anyAt least one holds; waitFor reports which (which).{ "any": [{ "visible": "welcome/heading" }, { "visible": "login/error" }] }
allEvery one holds on the same frame.{ "all": [{ "visible": "a" }, { "gone": "b" }] }
notThe condition doesn’t hold.{ "not": { "visible": "toast/error" } }
stableNothing changed in region (default: the screen) for forMs.{ "stable": { "forMs": 300 } }
changedSome pixel changed in region (default: the screen) after the last action.{ "changed": {} }
varA var (a dotted path) compared with one test, or truthy without one. Decided from the run’s data, without looking at the screen.{ "var": "toggle.state", "eq": "on" }
paramA flow param, compared or truthy, like var.{ "param": "retry", "eq": true }

Placeholders

Strings in steps can name values with placeholders: {{params.user}} (a param), {{vars.row.cx}} (a var, with dotted paths into objects and lists such as {{vars.rows.items.0.state}}), and {{run.id}}, {{run.flow}}, {{run.version}}, {{run.target}}. Objects interpolate as JSON. There is no expression language: arithmetic and parsing belong in a script.

The validator checks that each placeholder names a declared param, a var some step sets, or a run field.

Params and outputs

Params are inputs: { "type": …, "default"?: …, "description"?: … }. Types are string, number, boolean, json (any JSON value). Text spells the other types, so --param count=3 fills a number param: "42" and "-1.5" are numbers, "true" and "false" booleans. A param without a default is required; a run with an unknown, missing or unconvertible param is refused.

Outputs are what a run returns: { "type"?: …, "default"?: …, "description"?: … } (no type: any JSON). An output is the var of the same name when the flow ends, so set, find, read and call … as write them. A run that passes must have every declared output, of its type; failed runs keep the ones that were set. At most 64 KB as JSON. Runs show them in attn run --wait, GET /runs/:id, webhooks and triggers ({{event.run.outputs.name}}).

Handlers

Handlers deal with what can appear at any time: { "id", "when", "do", "max"? }. Whenever when becomes true during the run, the next safe point (a wait, or between steps; never in the middle of an action) runs do, then the interrupted wait goes on. max is how many times it may run (default 3). A fail in a handler fails the run.

Results and errors

A run ends passed, failed (a step failed, or fail), error (the target or the setup broke) or cancelled. A failed step’s error has a code (TIMEOUT, AMBIGUOUS, NOT_FOUND, CAPABILITY, CONNECTOR, CANCELLED, ASSERTION, FAILED, INVALID), a message, the frame at the failure and the best candidate the matcher saw, such as the element at 0.71 against a threshold of 0.86.

Validation

Before a flow is saved (and before attn flows push sends it) it is checked: the schema, with an error per step and its path (steps[3].click); element slugs against the library (unknown ones are errors, proposed ones warnings); placeholders; params and outputs; calls (the flow exists, takes the params given, and no call leads back to the caller). Warnings: a wait, a type with no into before any click, steps after pass or fail.

The JSON Schema: https://attention.run/schema/flow-v1.json. How flows run: Concepts.

Request access

attention.run is in an invite-only preview. Tell us what you’d automate; we invite people in small batches.

What do you want to automate?

Pick all that apply.

What for?
Tell us more (optional)

We’ll only use this to reply about access. Privacy policy.