SalesOS

Flows

Guided screen flows and autolaunched processes built without code — run from records, lists, pages, a link, workflows or events, and through the API.

A flow is a process an admin builds from elements under Settings → Automation → Flows (/dashboard/flows). A screen flow walks a person through screens; an autolaunched flow has no screens and runs from a workflow, an event or another flow. Flows are part of the Workflow Automation feature (Professional plan).

Flows run on the server. SalesOS renders each screen in the runner's language, validates every submission, and keeps the run (an interview) so a reload, another tab or another device resumes where the person stopped.

Elements

ElementWhat it does
SCREENInputs: text, textarea, number, currency, date, picklist (with dependent picklists), checkbox, lookup (record picker), choice (single or multiple); display and richtext show text. Each field can be required, have a default, a validation formula with its message, and visibleWhen conditions.
DECISIONOutcomes in order; the first whose conditions (or formula) hold wins, else the default (next).
ASSIGNMENTset, add, subtract, append, remove, clear on variables.
LOOPSteps through a collection into an item variable; done after the last item.
GET_RECORDSOne record by id, or records matching filters (lead, contact, account, opportunity, task, custom:<apiName>).
CREATE_RECORD / UPDATE_RECORD / DELETE_RECORDWrites through the entity services: validation rules, before-save hooks, field-level security, record locks, workflows, webhooks and the audit trail all apply.
CUSTOM_ACTIONCalls a custom action with mapped inputs; outputs go into variables.
FUNCTIONRuns a serverless function.
HTTP_CALLOUTCalls a named credential: credential, method, path and an optional JSON body (both templates with {!merge} fields). SalesOS adds the credential's authentication. responses map dot paths into the JSON reply (data.status), $status or $body (non-JSON replies) into variables. A non-2xx reply fails the step (CALLOUT_HTTP_ERROR), so its fault path can handle it.
PUBLISH_EVENTPublishes a custom event (eventApiName, payload mappings); the payload is checked against the event's schema. storeAs receives the event id. The flow's own event subscription never re-runs it for this event.
SEND_EMAILUp to 20 recipients (addresses or user ids). Fails with EMAIL_NOT_CONFIGURED when email is off.
SUBMIT_APPROVALSubmits a record to its approval process.
SUBFLOWRuns an active autolaunched flow inline (at most 3 levels).
ENDFinishes, optionally with a message.

Record, action, callout, event, email, approval and subflow elements can have a fault path (fault): when the step fails, the flow continues there and {!$Flow.FaultMessage} holds the reason. Without one, the interview stops and shows the error.

Variables, values and formulas

Variables are text, number, currency, boolean, date, datetime, record (with an object) or collection. Mark them isInput (filled from the launch or a parent flow) or isOutput (returned when the flow completes). A screen field stores its answer in a variable of the same name.

A value is one of:

  • text, with merge fields — "Follow up {!$Record.firstName}". A string that is exactly one merge keeps the value's type;
  • a number or boolean;
  • { "ref": "deal.amount" };
  • { "formula": "deal__amount * 0.9" } — the custom-field formula language (no code runs). In formulas, record fields are <variable>__<field>, the launching record's are Record__<field>, a collection's size is <variable>__count.

Built-in references: $Record (the record the flow was launched on, read as the runner), $User (id, name, email, role), $Label.<name> (a custom label in the runner's language), $Flow.interviewId, $Flow.FaultMessage, $Event (event-launched flows), and recordIds (list launches).

Run mode

By default record steps run as the person running the flow, with their record access and field security. An org admin can switch a flow to system mode: record steps then run as that admin, whoever runs the flow. The builder and the flow list label it, and it stops working if that person loses org admin access.

Runtime guarantees

  • Version pinning: definitions are versioned (DRAFT → ACTIVE → ARCHIVED); an interview keeps the version it started on.
  • Server validation: every submitted screen is checked again (types, options, dependent values, lookups the runner can see, required, validation formulas). A rejected screen answers 400 FLOW_SCREEN_INVALID with fieldErrors and the interview stays on that screen.
  • One submission per step: each request carries the interview's revision; a stale one (double click, second tab) is 409 FLOW_STALE_SCREEN and changes nothing.
  • Idempotent writes: a retried or resumed request never repeats a completed step, and a step whose outcome is unknown (the request was interrupted mid-call) fails to its fault path with FLOW_STEP_INTERRUPTED instead of running twice.
  • Back returns to the previous screen with its answers, only when nothing was written since it.
  • Loop guard: 500 elements per request, 200 runs of one element per request, 5,000 per interview (FLOW_STEP_LIMIT, FLOW_LOOP_LIMIT).
  • Interviews are kept 30 days.

Launching a flow

FromHow
Record pagesLaunch context record:<OBJECT> (record:LEAD, record:custom:project…) adds the flow to the page's Flows button.
List pageslist:<OBJECT> adds a bulk-bar action; the selected ids arrive in recordIds.
Page builderpage, rendered by the flow component.
A linkstandalone: /dashboard/flows/run/<apiName> (?recordId=&objectType= to launch on a record, ?interview= to resume).
WorkflowsA Run flow step (RUN_FLOW, { "flowApiName": "…", "inputs": { "var": "field" } }) runs an autolaunched flow with the trigger record as $Record, as the workflow's creator.
EventsAn autolaunched flow's eventTypes (any catalog event or custom event; a custom event's payload is {!$Event.<field>}). It runs as the flow's creator, never for writes the same flow made.

API

Running flows is public (tag Flows). API keys and OAuth tokens can run flows; their record steps need the matching READ_/WRITE_ scope, and custom action, function, email and approval steps need full access.

GET  /v1/flows?status=ACTIVE&context=record:LEAD
POST /v1/flows/{idOrApiName}/interviews     { "recordId": "…", "objectType": "LEAD", "inputs": {} }
GET  /v1/flows/interviews                   your paused interviews
GET  /v1/flows/interviews/{id}
POST /v1/flows/interviews/{id}/next         { "revision": 3, "values": { "subject": "Call back" } }
POST /v1/flows/interviews/{id}/back         { "revision": 4 }
POST /v1/flows/interviews/{id}/cancel

Each answer is the interview: status (PAUSED on a screen, COMPLETED, FAILED, CANCELLED), revision, screen (fields with their labels, options, current value and the visibleWhen conditions the client evaluates as the user types), message, error and outputs.

Building flows (/v1/flows/definitions…, /v1/flows/validate, /v1/flows/meta) needs an org owner or admin and is not part of the public contract.

Metadata

Flows move between orgs as FLOW components (flows/<apiName>.flow.json) with the definition of their active version; interviews never move. See metadata.

On this page