Skip to content

Automations (flows)

A flow is an automation you build in the app: a trigger, then a list of steps (blocks) that run against your workspace data. “When a deal is marked Won, create an onboarding project and email the account manager.” “Every Monday at 08:00 UTC, find overdue invoices and post them to our finance system.” Flows live under Automations in the navigation.

Don’t confuse a flow with an automation rule. An automation rule is a declarative derivation that keeps rows in one table in step with rows in another (see Tables & fields); it has no steps, no trigger and isn’t built in the flow editor. Everything on this page is about flows. (Some older identifiers and API fields still say automation; they mean flows.)

This guide is written for workspace admins, who build flows, and for members, who see what flows did.

If you only remember one thing:

A flow acts as a principal, and it can do no more than that principal can. A flow that runs as its own automation identity starts with no permissions at all. Until you attach a role to it, every step that reads or writes data is refused, and its runs are skipped for permission.


  • See flows and their run history. This needs flows:read, which members hold. They can see run status, timing and errors.
  • Build, edit, publish and delete flows. This needs flows:create, flows:update and flows:delete, which workspace admins hold.
  • Run a flow by hand, test it, or cancel a run. This needs flows:execute, which workspace admins hold. It covers the record page’s Run flow button. A member doesn’t see that button until a role grants them flows:execute.
  • See what each step received and returned. This needs flows:update or flows:execute. Holding only flows:read shows you the outline of a run, not its data.
  • Attach a role to a flow. This needs permissions:manage, which workspace admins hold. You can only attach a role whose grants are within your own permissions.

These are normal actions in the roles model, so a custom role can grant or withhold any of them.


Every flow has exactly one trigger. You choose it at the top of the builder.

Trigger In the builder Fires when
Record event When a record changes A record in the chosen table is created, updated or deleted, or an approval is requested or decided on one. An optional condition narrows it, e.g. “Stage changed to Won”.
Record action When someone runs it on a record Someone clicks Run flow on a record page of the chosen table. Each click starts one run with that record as its trigger record.
Schedule On a schedule A cron expression comes due. There are presets such as Every day at 09:00 UTC, or you can enter a custom 5-field cron.
Inbound webhook When a webhook URL is called An external system POSTs JSON to the flow’s secret URL. The body becomes {{payload.*}} in later steps.

Some things to know:

  • Schedules run in UTC. 0 9 * * * means 09:00 UTC all year. A schedule can fire at most 96 times a day, roughly once every 15 minutes.
  • Only the published version fires. Editing the draft has no effect on live runs until you publish (see Drafts, publishing and versions).
  • On a create, every field the record was created with counts as changed. A condition such as Revenue changed or Stage changed to Won matches a new record that has that value, so one trigger listening to created and updated covers “when it’s set or changes”. A field left empty on the create hasn’t changed.
  • A field that points into several tables can be tested by table. A condition on such a field offers points into and does not point into, so Regarding points into Accounts matches whichever account the record names, and an empty field points into no table; equals picks one record of one table. The same conditions filter a Find records step.
  • Flows can trigger flows, up to a point. A record written by a flow raises its own record event, and that event can start another flow. Chains stop at depth 3, which also stops a flow like “on update, update this record” from re-triggering itself forever.

Blocks run top to bottom. Every block’s output is available to the blocks after it.

Block What it does
Find records Looks up 1–100 records in a table with a filter and an optional sort. Finding nothing still succeeds, with count: 0.
Create record Creates a record. It can find or create: if a record already matches on up to 5 fields, the block uses that record (or updates it) instead of making a duplicate. In the built-in activities table it can also link the new activity to records.
Update record Updates the trigger record, a record an earlier step found or created, or any record by id. It can refuse to write if the record changed after the triggering event.
Delete record Deletes one of those same targets.
Send email Emails up to 10 workspace members. Recipients are members only, never arbitrary addresses.
HTTP request Calls an external HTTPS endpoint. It can capture the response (a non-2xx reply is then data you can branch on, not a failure) and HMAC-sign the request with a workspace secret. Requests to private and internal network addresses are blocked.
Run code Runs a small JavaScript transform(input) in a sandbox with no network, timers or modules. An object it returns becomes the step’s output, so return { total } is read as {{steps.<id>.total}}. Any other value (a number, text, a list) is kept under value: {{steps.<id>.value}}. Dry-run this block shows the output in this same shape.
Continue only if Stops the current list of blocks when its condition doesn’t match. At the top level, the run then ends as succeeded.
Branch If / else-if / else lanes (up to 5), each with its own blocks. The first lane whose condition matches runs.
Wait Pauses the run for a duration (up to 30 days) or until a date taken from the data.
Approval Pauses the run until a named person, or anyone holding a chosen role, approves or rejects it. A rejection is not a failure: it’s an output you branch on. Requests wait in My Approvals, and a timeout can approve or reject automatically.
Iterate Runs its blocks once for each item in a list, such as the records a Find records step returned, up to 100 items. Iterate blocks can’t be nested.

Referencing data. Text fields in a block are templates. {{record.Name}} is a field of the trigger record, {{before.Stage}} is its value before an update, {{payload.order_id}} comes from a webhook body, and {{steps.<id>.…}} is an earlier step’s output. Inside an iterate block, {{item.…}} and {{itemIndex}} refer to the current item. The builder’s reference picker inserts these for you. In an HTTP request’s URL, headers and body, {{secrets.NAME}} reads a workspace secret. In a Run code step, a secret you name in the source is available as input.env.NAME. A path that doesn’t exist renders as empty and adds a warning to the run. In a condition, a rule whose value references a path that doesn’t exist never matches, and the step’s output lists the path under unresolvedReferences. A condition’s field is always a field of the trigger record. To compare against an earlier step, put the step reference in the value.

Writes follow the same rules as people do. Record steps go through the same checks as an edit in the app: the principal’s permissions and row scope, field restrictions, table constraints, stage guards and approval rules. If an approval rule intercepts a flow’s write, the run stops and is recorded as skipped (approval). To ask first, use an Approval block. Field values a flow can’t read are empty to it, so a flow never sees or copies hidden data.


Run as: the flow’s identity and its roles

Section titled “Run as: the flow’s identity and its roles”

Every run resolves a permission snapshot at the moment it starts, and again when it resumes after a wait. The Run as setting in the builder’s header decides whose snapshot that is:

  • Automation identity (the default). The flow acts as itself, a principal in its own right, like a member or an API token. This identity has no base role: its permissions are exactly the union of the roles attached to it. With no role attached it can do nothing. Each step that needs a permission is refused (for example “missing records:create”), and the run is skipped for permission. Skipped runs are not failures, so they don’t count towards auto-disable and send no failure email. Check Automation activity after you publish a new flow.
  • Initiating user. The flow acts with the permissions of the person whose change or click started it. This option is available for record-event and record-action flows. Schedules and public webhooks have no initiating user, so they must use the automation identity. If the person loses access later, their pending runs lose it too.
  1. Open Workspace settings → Roles. Create a role for the job if you need one, e.g. Lead intake with records:read and records:create on the tables the flow touches. Grant only what the flow needs.
  2. Click the role’s assignment count, or the Manage assignments icon.
  3. Under Flows, choose the flow in Attach to a flow and click Attach. The same panel lists every flow that holds the role. Each one links to its builder, and you can detach it from there.

The builder shows the result beside Run as. A flow on its automation identity lists the roles it holds and links to Manage roles. If it holds none, it shows a warning that it can’t act until a role is attached.


  • The builder edits a draft and autosaves it. Validation problems show in the publish bar as you work, and clicking one jumps to the block that caused it.
  • Publish checks the draft strictly and makes it the next immutable version. Only the published version runs. A run that’s paused on a wait or an approval resumes against exactly the version it started on.
  • Versions lists every published version. Restoring one copies it back into the draft for you to review and publish. It never changes the live version directly.
  • The enabled switch turns a flow off without unpublishing it.

There are two ways to test a flow, and they differ in one important way:

  • Dry-run this block (in a block’s settings) runs one Run code, Continue only if or Find records block on its own and shows its output. You can simulate the outputs of earlier steps. These block types don’t change anything, and blocks that do (record writes, email, HTTP) can’t be dry-run.
  • Test run (the panel under the canvas) runs the whole draft. For a record-triggered flow you pick a record and the event to replay. For a webhook flow you paste a payload.

A test run executes the draft for real. It creates, updates and deletes real records, sends real email and makes real HTTP calls. The test panel lists the effects your draft has before you start it. Test runs are marked test in run history, but what they did isn’t undone.

A test run always uses your own permissions, whatever Run as says, so a tester never borrows the flow’s identity. This also means a test can pass when the live flow, on an automation identity with no role, would be skipped. Attach the role before you rely on a passing test.


Each time a published flow fires, it creates a flow run with its own status: queued, running, waiting (on a wait or an approval), succeeded, failed, skipped or cancelled.

  • Run history in the builder lists the flow’s recent runs. Automation activity (Automations → Activity) is the workspace-wide feed, filterable by flow. A record’s Automations tab shows the runs about that record.
  • Opening a run shows its step trace: each block, its status and timing, any template warnings and, with flows:update or flows:execute, what the block received and returned. Secret values are scrubbed from stored steps.
  • A run that’s queued or waiting can be cancelled from run history or the activity feed (this needs flows:execute).
  • A run’s skeleton (what ran, when and how it ended) is kept permanently. The bulky step inputs and outputs are pruned after 30 days by default.
  • The builder shows a health card with 14 days of successes, failures and skips.

When things go wrong. A step that fails for a transient reason is retried (up to 4 attempts) before the run fails. A deterministic error, such as invalid data or a blocked address, fails at once. The first failure after a healthy streak notifies everyone who can fix the flow (flows:update), including by email. After 20 consecutive failures the flow is switched off automatically and the same people are told. A run left waiting past its wake time also raises a notification. See Notifications.


These are the defaults. Your organization’s plan or a workspace admin may set different values.

Limit Default
Flows per workspace 50
Blocks per flow (a fixed ceiling of 50 always applies) 25
Runs started per workspace per UTC day (firings past it are dropped and noted on the flow) 1,000
Days a step’s input and output are kept 30

Every run also has fixed bounds: 200 step executions (iterations count), 5 minutes of total step time, 30 seconds per step, and a 30-day lifetime including waits. Conditions allow up to 10 rules per level and 5 groups.


A webhook-triggered flow is the one flow surface that can be called without signing in, so its URL is the credential.

  • Publishing mints the URL, in the form /api/hooks/flows/<workspaceId>/<flowId>/<token>, and shows it exactly once. The token is stored only as a hash, so copy it when it appears. Changing the flow to a different trigger and publishing again kills the old URL.
  • Choose how callers authenticate:
    • URL token only. Anyone holding the URL can start a run. Keep it as secret as a password.
    • URL token + API key. The caller must also send a workspace API token as a bearer credential. Use this when the caller can hold a secret header.
  • Every mismatch, whether an unknown flow, a wrong token or a deleted flow, returns the same 404, so the endpoint reveals nothing about which flows exist. A disabled flow returns 409.
  • Bodies must be JSON and at most 64 KB. Each hook is rate limited per minute and per day. Workspace admins can lower a flow’s daily limit from the card shown in its builder.

The endpoint is POST /hooks/flows/{workspaceId}/{flowId}/{token} in the API reference.


Goal: when a lead is created with an email address, create a follow-up activity and tell the sales lead.

  1. Automations → New flow, named Welcome new leads.
  2. Trigger: When a record changes. Table Leads, event Record created, condition Email is not empty.
  3. Run as: leave Automation identity.
  4. Add blocks:
    • Create record in Activities: Subject Intro call with {{record.Name}}, linked (regarding) to the trigger record.
    • Send email to the sales lead: subject New lead: {{record.Name}}, body {{record.Name}} ({{record.Email}}) just came in.
  5. Give it a role. In Workspace settings → Roles, create Lead intake with records:read, records:create and records:update. If Leads or Activities is a private table, widen the role’s record scopes so it reaches the records the flow touches (see record scopes). Under Flows, attach it to Welcome new leads. Back in the builder, the warning beside Run as is replaced by Lead intake.
  6. Test run: pick a recent lead and its created event, run it, and read the trace. This creates a real activity and sends a real email.
  7. Publish, and keep an eye on Automation activity for the first few live runs.
Open the app