Source: https://docs.flowxo.com/ # Flow XO documentation Flow XO runs AI agents that talk to your customers, plus workflows that do exact, repeatable work for them: look up a row, update a record, call an API. ## Start here - [How Flow XO fits together](/start/concepts): organizations, workspaces, projects, Blueprints and publishing. - [Build a workflow](/workflows/build): your first workflow, step by step. ## Workflows - [Steps](/workflows/steps): what each step type does. - [Inputs, outputs and variables](/workflows/inputs-outputs): what goes in, what comes out, and what's kept along the way. - [Pick, type or calculate a value](/workflows/values): the value control every step uses. - [Google Sheets](/workflows/google-sheets): find rows, and insert, update or upsert them. - [Advanced JSON](/workflows/advanced-json): write a step's data as JSON. - [Test and fix a workflow](/workflows/testing): run it, read the run, fix what failed. ## Files - [Connect tools over S3](/knowledge/s3-access): reach your files from the AWS CLI, rclone or mountpoint-s3. ## Reference - [Formula reference](/reference/formulas): every operator, function and method, with checked examples. - [Step types](/reference/steps): the steps in the add-step dialog. - [Workflow limits](/reference/limits): the default limits on steps, retries, waits and run data. ## For coding agents These docs are also published as plain markdown. [llms.txt](/llms.txt) lists every page, and [llms-full.txt](/llms-full.txt) has all of them in one file. Add `.md` to any page's URL for its markdown. Something wrong or missing? Tell us at [support@flowxo.com](mailto:support@flowxo.com). --- Source: https://docs.flowxo.com/start/concepts # How Flow XO fits together ## Organization, workspace, project - **Organization:** your company's account. People, data region and spending sit here. - **Workspace:** a group of projects, with its own integrations, files and settings. Use one per team or client. - **Project:** one agent. Everything the agent needs lives in its project: its Blueprint, connections, integrations, knowledge, conversations and runs. Switch between them from the menu at the top of the sidebar. ## The Blueprint A project's **Blueprint** is everything that decides how its agent behaves: identity and instructions, model, greeting, tools, workflows, knowledge, variables and more. Open it from **Blueprint** in the project sidebar. A new project asks how to start. **Start with a support desk** gives you an agent with support instructions and handover rules. **Start from a blank agent** gives you an empty one. You can change either later. ## Draft and publish Edits to a Blueprint save to its **draft** as you make them. Customers keep talking to the published version until you choose **Publish**. Publishing applies the whole draft at once, workflows included. The workflow editor also keeps a recovery copy in your browser, so an edit that couldn't save yet isn't lost. That copy stays in your browser profile. It isn't encrypted or shared, so keep credentials out of workflow fields. ## Agents and workflows An **agent** talks. It reads messages, decides what to do, and answers in its own words. A **workflow** does. It runs the same steps the same way every time: find a row, write a record, call an API, wait for approval. Agents call workflows like tools. The agent supplies the inputs, the workflow returns outputs, and the agent explains the result. Use a workflow when a job has to be exact, or has to happen in a fixed order. ## Connections and integrations - **Connections** are the channels customers use to reach the agent, like the website Messenger. - **Integrations** connect outside services, like Google Sheets or an MCP server, so the agent and its workflows can use them. ## Runs Every workflow run is recorded. Open **Runs** in the project sidebar to see what each run received, what each step did, and why a step failed. See [Test and fix a workflow](/workflows/testing). --- Source: https://docs.flowxo.com/workflows/build # Build a workflow Workflows live in a project's Blueprint. Open **Blueprint → Workflows** to see them. ## Start one Choose **Create workflow** to start from nothing, or pick a recipe under **Start from a curated recipe**. A recipe is a ready-made workflow for a common job, like adding a row to a sheet or asking a person before writing. It asks for its settings, then builds the steps for you. Nothing is saved until the workflow has at least one step. ## The editor The editor has four sections: - **Build:** the steps and how they connect. - **Inputs & outputs:** what the agent sends in, what comes back, and the variables used along the way. See [Inputs, outputs and variables](/workflows/inputs-outputs). - **Workflow settings:** the display name people see in Blueprint. - **Test:** run the saved draft through the agent. It appears once the workflow is saved. See [Test and fix a workflow](/workflows/testing). The workflow's **Identifier** is shown under its name. Flow XO generates it, and it can't be changed. ## Add steps 1. Choose **Add a step**. The first step is where the workflow starts. 2. Pick a step type. The dialog lists **Common** steps first and **Advanced** ones after. See [Steps](/workflows/steps) for what each one does. 3. Fill in the step in the panel that opens. To add another, select a step and use its **+**. Dragging the **+** onto another step connects them. Each step's panel has up to four tabs: - **Configure:** what the step does. - **Settings:** its name, retries and timeout. - **Inputs:** the values it receives, for steps that take them. - **Outputs:** what later steps can read from it. A step with a problem shows **Issues affecting this step** at the top of its panel. ## Branches Steps run one at a time, top to bottom. A step can have several connections leading out of it: - **Branches are checked in order.** The first one whose rule matches runs. - **Otherwise** runs when no branch matches. It's always last. - A new branch never matches until you give it a rule. Select a connection to edit its rule. Rules are [formulas](/reference/formulas) that come out true or false. A [Classify](/workflows/steps#classify) step works differently: it gets one exit per class instead of rules. ## Saving Valid changes save to the Blueprint draft as you go. The status next to the workflow name says where things stand: - **Saved to Blueprint draft:** done. - **Not saved yet:** the workflow has no steps. - **Incomplete or invalid:** something needs fixing first. Your edits are kept in this browser in the meantime. **Undo** (Ctrl/Command+Z) and **Redo** (Ctrl/Command+Shift+Z) work until you leave the editor. **Versions** shows earlier saved versions. If someone else saves the same workflow while you're editing, you'll see "This workflow changed elsewhere". Your edits are kept. Compare the two and choose **Keep my changes** or **Use server version**. ## Give an agent access A workflow can sit in a Blueprint without any agent using it. In **Blueprint → Workflows**, turn on **Enable _workflow name_ for this agent** to let the agent call it. New workflows start with it on. For a recipe, clear **Enable for this agent** when you create it to start with it off. The agent can only call it once the Blueprint is published. Choose **Publish** at the top of the Blueprint. --- Source: https://docs.flowxo.com/workflows/steps # Steps Every step type in the add-step dialog. The [step reference](/reference/steps) has the same list in short. Every step has a **Settings** tab: - **Step name:** how the step appears on the diagram and in runs. - **Enable retry:** try again after a temporary error. Set **Maximum retries** and how long to wait before retrying. - **Step timeout:** how long the step may run, in seconds. Leave it empty for the default of 30 seconds. ## Invoke tool Runs an action: a built-in tool like **Current Time**, or one from an app connected to the project, like Google Sheets. 1. On **Configure**, choose **Select an available action** and search for the action. 2. On **Inputs**, fill in the action's fields. For each field, type a value or choose **Use a workflow input or step result**. Tools the agent can use but workflows can't yet are marked **Agent only**. **Edit input as JSON**, under the fields, shows the action's whole input as JSON. See [Advanced JSON](/workflows/advanced-json#edit-input-as-json). The **HTTP request** action has its own form: **Request method**, **Request URL**, **Request headers**, **URL parameters**, **Request body** and **Connection**, the saved login it uses to authenticate. For Google Sheets actions, see [Google Sheets](/workflows/google-sheets). ## Calculation Works out one value from earlier data with a [formula](/reference/formulas) and keeps it for later steps. Type `{{` or press **Ctrl+Space** to choose an input or earlier result. The panel shows what the step stores, like **Stores: Number**. Later steps read the value as `result`. ## Log Writes a message to the run's log, with optional data. Use it to record what happened at a point in the workflow. ## Set variables Saves values into workflow variables. Each **Assignment** has a **Variable name** and a **Value**. - Assignments run top to bottom, so a later one can read a variable an earlier one set. - Each variable can appear once per step. - Use **Move up** and **Move down** to reorder them. You can also save any step's output to a variable from that step's **Outputs** tab. See [Inputs, outputs and variables](/workflows/inputs-outputs#save-an-output-to-a-variable). ## Classify Asks AI to put text into one of the classes you define, then follows that class's exit. - **What to classify:** the text to read. Choose it with **Input/Variable** or **Calc**. - **Instructions (optional):** extra guidance for the choice. - **Classes:** at least two. Give each a **Name** and a **Description** of what belongs in it. Each class gets its own exit on the diagram. There are two more: - **Matched:** runs when the chosen class's exit isn't connected. - **No match:** runs when the text fits none of your classes. Only one exit runs. An exit with nothing connected ends that path. Later steps read the chosen class as `class`. ## Extract Asks AI to pull named fields out of text. - **Extract from:** the text to read, chosen with **Input/Variable** or **Calc**. - **Fields to extract:** for each field, a **Name**, a **Description** of how to find it, and a **Type**: **Text**, **Number**, **Yes/no**, **Date** or **One of a list**. For **One of a list**, enter the **Choices** separated by commas. - **Required:** if a required field can't be found, the step fails and says which. A missing optional field comes back empty. Each field becomes an output of the step. ## Transform Builds named outputs from earlier data. Each **Output** row has an **Output name** and a value: picked, typed or calculated. See [Pick, type or calculate a value](/workflows/values). Choose **Advanced JSON** to write the whole result as JSON instead, for nested data or lists. See [Advanced JSON](/workflows/advanced-json#transforms-advanced-json). ## Wait Pauses the run. Pick a **Wait kind**: - **Approval:** pause until someone approves or denies this step. Set the **Approval message** and how long to **Wait for approval up to**. - **Timer:** pause for a set time, in **Wait for**. - **Event:** pause until a named outside event arrives. Set the **Event name**. Waits can last up to 168 hours, unless support has changed your [limit](/reference/limits). A waiting run doesn't count toward its active run time. --- Source: https://docs.flowxo.com/workflows/inputs-outputs # Inputs, outputs and variables Open **Inputs & outputs** in the workflow editor. It has three tabs. ## Inputs Values the agent supplies when it calls the workflow. The agent sees each input's name and description, so describe what you expect. Choose **Add input**, then fill in: - **Field name:** letters, numbers, underscores or dollar signs. Steps read it as `input.`. - **Value type:** **Text**, **Number**, **Integer**, **Boolean**, **Object** or **List**. - **Required:** the agent must supply this value. - **Description:** what the value means and what format it takes. - **Default value:** used when the agent leaves the input out. It must match the type. - **Allowed values:** optional. Limits the input to a fixed list. **Use this field as** moves a field between tabs. **Input and result** makes the same field both an input and an output. ## Outputs What the workflow returns to the agent when it finishes. The agent reads these to explain the result. Required applies to inputs only. An output can come back empty, for example when the step that sets it didn't run on that path. ## Variables Working values the workflow keeps while it runs. The agent never sees them. - **Entire workflow:** every step can read and change it. - **Current step only:** only the running step uses this copy. Other steps see the workflow value. Steps read a variable by its name: `total`, not `input.total`. ## Renaming and removing Renaming a field offers to update every reference to it. Text you typed is left exactly as written. Removing a field lists the references you'll need to fix. Changing an input or output changes what the agent sees the next time the Blueprint is published. ## What each step produces A step's **Outputs** tab lists what later steps can read from it, with the type of each. Each output has a reference chip, like **Transform › Customer**. Copy it with the button beside it and paste it into another step's field. A step on a different branch may not have run, so its outputs can be empty. The editor warns when you use one. ## Save an output to a variable Each output on a step's **Outputs** tab has **Save _output_ to variable**: - **Choose a variable** of a matching type, or - **New variable…** to create one there. The variable is set only if the step succeeds, before the next step starts. --- Source: https://docs.flowxo.com/workflows/values # Pick, type or calculate a value Most step fields use the same control, with three ways to set a value: - **Input/Variable:** use a workflow input, a variable, or an earlier step's result. Choose it from the list. - **Specific value:** type it. What you type is used exactly as written, braces and all. - **Calc:** work it out with a [formula](/reference/formulas). ## Choosing a value **Input/Variable** lists what's available at that step: - **Workflow inputs:** `input.name`, `input.quantity` and so on. - **Variables:** by name. - **Earlier steps:** each step's outputs, under the step's name. **Entire output** is the step's whole result. If a step's fields depend on data that only exists when it runs, the list says so. You can still type the reference in **Calc**. ## Formulas In **Calc**, type `{{` or press **Ctrl+Space** to choose an input, variable or earlier result. It goes in without braces, ready to use in the formula: ```text input.quantity * input.price ``` In text fields and JSON, a reference goes inside `{{ }}`, and the braces can hold a formula: ```text Hello {{input.name}}, your total is {{input.quantity * input.price}}. ``` References show as chips, like **Look up row › Status**, so you can read them at a glance. See [Advanced JSON](/workflows/advanced-json#whole-value-or-part-of-a-sentence) for how a `{{ }}` behaves inside JSON. ## Warnings you might see - **"This workflow input is optional. A caller may leave it empty."** Supply a default, or handle the empty case. - **"This step may not run on every route, so its value can be empty."** The step is on a different branch. - **"This saved reference is no longer available."** The input, variable or step it pointed at no longer exists. Choose another value. ## Renames Renaming an input, variable or output updates every pick and formula that uses it. Text in a **Specific value** is never changed. If you typed `{{input.name}}` as a specific value, it stays exactly that. --- Source: https://docs.flowxo.com/workflows/google-sheets # Google Sheets Workflows can find rows in a worksheet and write them: insert a new row, update a matching one, or upsert. ## Before you start Connect a Google Sheets account in the project's **Integrations**. If more than one account is connected at the same level, the editor asks you to choose one there. ## Choose the spreadsheet Add an **Invoke tool** step and pick a Google Sheets action, like **Find worksheet rows** or **Write worksheet row**. Then: 1. Under **Choose the spreadsheet and worksheet**, use the folder button to pick the file from Google Drive. Or open **Enter an ID or URL manually**, paste the spreadsheet's URL or ID, and choose **Verify spreadsheet**. 2. Choose the worksheet by name. Flow XO can only open spreadsheets you pick from Drive and ones Flow XO created. A pasted URL works only for a file the connection can already open. Flow XO keeps track of the worksheet by its ID, so renaming the tab later doesn't break the step. ## Prepare the worksheet The first time you choose a worksheet, Flow XO prepares it so workflows can find and update the right rows. It adds hidden row tracking. Your columns and data don't change. If hidden tracking isn't available for a worksheet, Flow XO asks first, then adds a column named **FlowXO Row ID** after your last column. Your other columns stay as they are. A worksheet that isn't prepared shows **Worksheet not prepared** with a **Prepare worksheet** button. You can't test or publish until it's prepared. This also happens when someone types rows straight into the sheet: they're "rows added outside Flow XO". Preparing again tracks them too. Preparing needs every column in the first row to have a different name. Fix the headings, then choose **Check again**. ## Find rows **Find worksheet rows** answers **Which rows?** With no conditions, it returns rows in order. Add conditions to return only matches. Each condition has a **Column**, a **Comparison** and a **Value to match**. Choose **Add Conditions** for more. With two or more, **Rows must match** sets whether all of them or any of them must match. The comparisons are **Equals**, **Does not equal**, **Contains**, **Starts with**, **Ends with**, **Greater than**, **Greater than or equal to**, **Less than**, **Less than or equal to**, **Is empty** and **Is not empty**. Turn on **Case-sensitive matching** to tell `ada` from `Ada`. Under **Limit results**: - **Maximum rows:** leave it empty for 100 rows. It can be up to 500. - Up to 20 conditions per step. Each row comes back with a **Row ID**. Pass it to a later step to update that exact row. ## Write a row **Write worksheet row** writes one row. Pick a **Write mode**: - **Insert:** add a row at the end. Columns you leave untouched become empty cells. - **Update:** change one matching row. Columns you leave untouched keep their values. If no row matches, the step fails. - **Upsert:** update the matching row, or insert one if there isn't one. Running it again with the same value updates the same row. ### Finding the row to update For **Update** and **Upsert**, choose a **Match column** and a **Match value**. - The match value can't be empty. - Case is ignored unless you turn on **Match text case exactly**. - Spaces count. `Ada` doesn't match `Ada ` with a trailing space. - Numbers match their exact text: `1` matches `1`, but not `01`. - If more than one row matches, nothing is written and the step fails. In **Upsert**, the match value is what lands in the match column, even if a column mapping says otherwise. In **Update** mode, **Advanced options** has **Select Update by stable Row ID**, to find the row by its Row ID instead. ### Column values Each column in the worksheet is listed by its heading. For each one choose: - **Leave untouched:** don't change the cell. - **Write a value:** pick, type or calculate it. See [Pick, type or calculate a value](/workflows/values). - **Empty cell:** clear it. Write at least one column. If a picked value is missing when the step runs, nothing is written and the step fails with `value_missing`. When the columns aren't known until the workflow runs, open **Advanced options** and use **Advanced column values (JSON)**: one object whose keys are column headings and whose values are the cells. See [Advanced JSON](/workflows/advanced-json). ## Recipes Several recipes in **Blueprint → Workflows** build Sheets workflows for you, like **Update or add a contact row** and **Ask a person before writing the row**. They prepare the worksheet as part of setup. --- Source: https://docs.flowxo.com/workflows/advanced-json # Advanced JSON Three places in the workflow editor let you write a step's data as JSON instead of filling in a form: - **Edit input as JSON** on an **Invoke tool** step: the full input the action receives. - **Advanced JSON** on a **Transform** step: the value the step produces. - **Advanced column values (JSON)** on a **Write worksheet row** step: the cells to write. Use the form when it covers what you need. It checks each field as you go and shows references as chips. Use JSON for nested data, lists of objects, or fields the form doesn't show. ## The format The editor accepts standard JSON. That means: - Field names and text are in double quotes: `"name": "Ada"`. - Numbers, `true`, `false` and `null` have no quotes. - No comments and no trailing commas. A value from the workflow goes inside a quoted string as `{{ }}`. Type `{{` or press **Ctrl+Space** to choose one. Outside a string, the editor adds the quotes for you. ```json { "email": "{{input.email}}", "greeting": "Hello {{input.name}}" } ``` The braces can hold a whole formula, not only a reference. See the [formula reference](/reference/formulas). ## Whole value or part of a sentence Where a `{{ }}` sits decides what comes out: - **The whole string is one `{{ }}`.** The value keeps its type. `"{{input.quantity}}"` gives the number `3`, and `"{{input.tags}}"` gives a list. - **The `{{ }}` sits inside other text.** The value is written into the text. `"Order of {{input.quantity}}"` gives `"Order of 3"`. Even a single space around the braces makes it part of a sentence, so the value becomes text. ```json { "to": "{{input.email.trim()}}", "subject": "Your order of {{input.quantity}}", "quantity": "{{input.quantity}}", "tags": "{{input.tags}}", "priority": "normal" } ``` With the [sample data](/reference/formulas#sample-data), this gives: ```json { "to": "Ada@Example.com", "subject": "Your order of 3", "quantity": 3, "tags": [ "vip", "beta" ], "priority": "normal" } ``` ## Missing values If a whole-value `{{ }}` has nothing in it when the step runs, its field is left out. Inside text, a missing value is written as nothing. ```json { "name": "{{input.name}}", "nickname": "{{input.nickname}}", "note": "Nickname: {{input.nickname}}" } ``` With the [sample data](/reference/formulas#sample-data), this gives: ```json { "name": "Ada Lovelace", "note": "Nickname: " } ``` To always send the field, give it a fallback with `has()` and `? :`. Inside a JSON string, quotes in the formula need a backslash: ```json { "name": "{{input.name}}", "nickname": "{{has(input.nickname) ? input.nickname : \"\"}}" } ``` With the [sample data](/reference/formulas#sample-data), this gives: ```json { "name": "Ada Lovelace", "nickname": "" } ``` ## Edit input as JSON On an **Invoke tool** step, open the **Inputs** tab and choose **Edit input as JSON**. The box shows the complete input the action will receive, including fields the form doesn't have controls for. - **It must be a JSON object**, the same shape as the form: one field per action input. - **The form and the JSON are the same data.** A change in one shows in the other. Fields the form doesn't know about are kept when you edit with the form. - **Invalid JSON is never saved.** The box says "Enter a valid JSON object. The last valid value is unchanged." Fix it and the change saves. - **Closing the box while the JSON is invalid** asks whether to discard the text you typed. The last valid value stays. - **The action checks its own fields when the step runs.** If one is missing or the wrong type, the step fails with `invalid_tool_input` and the error names the field. [Test the workflow](/workflows/testing) to catch this before customers do. When an action has no guided fields, or the saved input uses a format the form can't show, the editor tells you to use Advanced JSON. ## Transform's Advanced JSON A new **Transform** step shows **Output rows**: one named output per row. Choose **Advanced JSON** to write the whole result as JSON instead. The result can be any JSON value: an object, a list, text in quotes, a number, `true`, `false` or `null`. Later steps read it as `result`. If it's an object, they can read its fields as `result.email`, `result.total` and so on. An object: ```json { "message": "Hello {{input.name}}", "count": "{{input.quantity}}" } ``` With the [sample data](/reference/formulas#sample-data), this gives: ```json { "message": "Hello Ada Lovelace", "count": 3 } ``` A single number: ```json "{{input.quantity * input.price}}" ``` With the [sample data](/reference/formulas#sample-data), this gives: ```json 7.5 ``` Text: ```json "Order for {{input.name}}" ``` With the [sample data](/reference/formulas#sample-data), this gives: ```json "Order for Ada Lovelace" ``` A list: ```json [ "{{input.email.trim().lowerAscii()}}", "{{input.tags}}" ] ``` With the [sample data](/reference/formulas#sample-data), this gives: ```json [ "ada@example.com", [ "vip", "beta" ] ] ``` Nested data, with formulas: ```json { "customer": { "name": "{{input.name}}", "email": "{{input.email.trim().lowerAscii()}}" }, "order": { "total": "{{input.quantity * input.price}}", "summary": "{{input.quantity}} × {{input.price}}" }, "vip": "{{\"vip\" in input.tags}}", "status": "{{nodes.lookup.output.row.Status}}" } ``` With the [sample data](/reference/formulas#sample-data), this gives: ```json { "customer": { "name": "Ada Lovelace", "email": "ada@example.com" }, "order": { "total": 7.5, "summary": "3 × 2.5" }, "vip": true, "status": "Active" } ``` ### Switching between rows and JSON - **Rows to JSON keeps everything.** If you switch a step that already has output rows, each row also stays available under its own name. Every row needs a unique name first. The editor asks for one with "Complete each output and give it a unique name before opening Advanced JSON." - **JSON to rows** is offered only when the JSON is a flat object: each field is plain text, a number, true or false, or one whole `{{ }}`. Otherwise you keep editing JSON, and the editor says "This JSON has values that output rows cannot show." - **Text typed in a row stays text.** A row set to **Specific value** that contains braces keeps them as typed in the JSON. Edit it in JSON to make it a formula. ### Older Transform steps Steps made before Transform had output rows show "This step uses older Transform settings." They keep working as saved. Choose **Switch to JSON** to edit them here. Check the result first, because it may change. ## Sheets column values On a **Write worksheet row** step, the column values can be JSON when the columns aren't known until the workflow runs. Write one object: each key is a column heading, each value is what goes in that cell. ```json { "Name": "{{input.name}}", "Quantity": "{{input.quantity}}", "Status": "New" } ``` With the [sample data](/reference/formulas#sample-data), this gives: ```json { "Name": "Ada Lovelace", "Quantity": 3, "Status": "New" } ``` Or set **Dynamic column values** to an input or formula that returns such an object. When the JSON is a flat object of known columns, **Use column rows** switches back to the column list. See [Google Sheets](/workflows/google-sheets#column-values). ## Validation The editor checks the JSON as you type: - **Not valid JSON.** Nothing is saved. Your text stays in the box with the error, and the last valid value stays saved. - **Valid JSON, but not the shape this box needs.** **Edit input as JSON** needs an object. A Transform's JSON can be any value. - **A reference to something that no longer exists** shows "This saved reference is no longer available" on the step's form. Problems inside a formula, such as dividing by zero, show up when the step runs. The step fails with an [error code](/reference/formulas#errors), and the run shows which step and why. --- Source: https://docs.flowxo.com/workflows/testing # Test and fix a workflow ## Run a test Open **Test** in the workflow editor. It appears once the workflow has been saved. 1. Fill in **Inputs for this test**, or choose **Generate sample data**. 2. Choose **Send to agent**. The test sends a message to your agent using the saved draft, asking it to run this workflow with those inputs. Each **Send** starts a fresh conversation. Nothing is published. To word the request yourself, open **Edit agent message**. A test is a real run. It can write to connected services like Google Sheets, so use test data. You can't send a test while the workflow has unsaved, invalid or conflicting changes. The workflow also has to be enabled for the agent. See [Give an agent access](/workflows/build#give-an-agent-access). ## Read the result After a test, **Last test conversation** says whether the agent called the workflow: - **"The agent requested this workflow."** Each call is listed as an invocation. Open it to see the run. - **"The agent finished this response without invoking this workflow."** The agent decided not to call it. Check its display name and input descriptions, or edit the agent message to ask for it directly. If the conversation hasn't finished loading, choose **Check test again**. ## Inspect a run **Runs of this workflow** lists every run, including runs of earlier saved versions. Open one to see: - the **Run inputs** the agent sent, - each step, marked completed, failed or not run, - what each step received and produced, with **View JSON** for the raw data, - the final output returned to the agent. A failed run puts the failed step first and says why. **Show steps not run** lists the steps the run never reached. If you've edited the workflow since the run, it says **Current draft has changed**. The run always shows the version that actually ran. Every run in the project is also in **Runs** in the project sidebar. ## What an error means A failed step names an error code. These are the codes a run can show, with what each means: | Code | What it means | | --- | --- | | `active_duration_exceeded` | This run used the time used of active time, exceeding the workspace limit of the limit. Waiting for approvals or timers doesn’t count. Shorten slow steps, reduce retries, or ask support about a higher limit. | | `cancelled` | This run was cancelled before it finished. | | `expression_error` | A rule or template in a step could not be evaluated. Open the step and correct it. | | `google_sheets.connection_not_permitted` | This step selects a different Google account from the project. Remove its explicit account to use the project’s effective account. | | `google_sheets.invalid_headers` | Add a header row with a unique, non-empty name for each column to use structured row tools. | | `google_sheets.worksheet_schema_conflict` | The worksheet’s headers or data conflict with the requested columns. Review the worksheet without overwriting existing cells. | | `handler_threw` | A step failed unexpectedly. | | `invalid_config` | A step's settings are not valid for its action. Open the step and correct them. | | `iteration_limit` | The run took more steps than any workflow can, and was stopped to protect it. | | `no_handler` | The runtime can’t perform this step’s action. | | `replace_limit` | A step kept replacing its own action and was stopped to protect the run. | | `retry_delay_failed` | The wait before a retry was interrupted, so the step was not retried. | | `runtime-workflow.expression.division_by_zero` | A calculation or branch rule divided by zero. The run stopped. | | `runtime-workflow.expression.invalid` | A calculation or branch rule couldn’t be read. Open the workflow and correct it. | | `runtime-workflow.expression.number_out_of_range` | A calculation or branch rule produced a number too large to store. | | `runtime-workflow.expression.type_mismatch` | A calculation or branch rule used incompatible values, such as text with a number or a missing value. | | `runtime-workflow.tool_input.value_missing` | A picked or calculated value was missing. Nothing was written. Supply the value, or choose Empty cell to clear the column. | | `state_too_large` | Variables and step results exceeded the limit. Keep only the fields the workflow needs. | | `TIMEOUT` | A step exceeded its timeout and stopped. Increase the timeout or shorten the work. | | `unknown_node` | The run reached a step that isn’t in its workflow. | | `unknown_operation` | This step’s operation isn’t available. | | `unknown_outcome` | A step returned a result the runtime does not recognize. | Errors from a formula are explained with examples in the [formula reference](/reference/formulas#errors). ## Common fixes - **A value is missing.** The step picked an input, variable or result that was empty on this run. Make the input required, give it a default, or handle the empty case with a formula. - **A step timed out.** Raise **Step timeout** in the step's **Settings**, up to the [limit](/reference/limits). - **A temporary error.** Turn on **Enable retry** in the step's **Settings**. - **The agent didn't call the workflow.** Give it a clear display name and describe its inputs, so the agent knows when to use it. Check it's enabled for the agent. --- Source: https://docs.flowxo.com/knowledge/s3-access # Connect tools over S3 Your Flow XO files are reachable from any S3-compatible tool: the AWS CLI, rclone, mountpoint-s3, or your own code. Generate an access key, then point the tool at Flow XO. ## What you need Open **Settings → Access keys** in a workspace or a project, then open **How to connect external tools**. It shows the three values every tool asks for: | Setting | Value | | -------- | ------------------------------------------------------------------------- | | Endpoint | `https://flowfs-s3.flowxo.com` | | Bucket | `flowfs-` followed by your organization ID. Copy it with **Copy bucket**. | | Region | `us-east-1`. AWS tools insist on one. Flow XO doesn't check it. | The examples below write the bucket as `flowfs-YOUR-ORG-ID`. Paste yours in its place. ## Generate an access key 1. In **Settings → Access keys**, choose **Generate access key**. 2. Name it after the tool that will use it, like "Backup script" or "My laptop". The name is optional and only you see it. 3. Choose **Generate**. 4. Copy the **Access key ID** and the **Secret access key**. **Copy as ~/.aws/credentials snippet** copies both in the format the AWS CLI reads. The secret is shown once. If you lose it, revoke the key and generate another. ## Workspace keys and project keys A key reaches only the files of the place it was generated in. - **Workspace key:** generated in workspace settings. It reaches that workspace's files, including its projects. - **Project key:** generated in a project's settings. It reaches that project's files and nothing outside it. A request for anything outside the key's reach is refused with `AccessDenied`, even with a valid signature. ## AWS CLI Add a profile to `~/.aws/credentials`: ```ini [flowxo] aws_access_key_id = YOUR-ACCESS-KEY-ID aws_secret_access_key = YOUR-SECRET-ACCESS-KEY ``` Then pass the endpoint with every command: ```bash # List files aws --profile flowxo --endpoint-url https://flowfs-s3.flowxo.com \ s3 ls s3://flowfs-YOUR-ORG-ID/ # Upload a file aws --profile flowxo --endpoint-url https://flowfs-s3.flowxo.com \ s3 cp ./report.pdf s3://flowfs-YOUR-ORG-ID/reports/report.pdf # Download a file aws --profile flowxo --endpoint-url https://flowfs-s3.flowxo.com \ s3 cp s3://flowfs-YOUR-ORG-ID/reports/report.pdf ./report.pdf # Copy a whole folder up aws --profile flowxo --endpoint-url https://flowfs-s3.flowxo.com \ s3 sync ./exports s3://flowfs-YOUR-ORG-ID/reports/exports/ ``` ## rclone Add a remote to `~/.config/rclone/rclone.conf`: ```ini [flowxo] type = s3 provider = Other env_auth = false access_key_id = YOUR-ACCESS-KEY-ID secret_access_key = YOUR-SECRET-ACCESS-KEY endpoint = https://flowfs-s3.flowxo.com region = us-east-1 acl = private ``` Then use `flowxo:` paths: ```bash rclone ls flowxo:flowfs-YOUR-ORG-ID/ rclone copy ./report.pdf flowxo:flowfs-YOUR-ORG-ID/reports/ rclone sync ./exports flowxo:flowfs-YOUR-ORG-ID/reports/exports/ ``` ## mountpoint-s3 Mount the bucket as a folder on Linux or macOS. This reads the `flowxo` profile from the AWS CLI section: ```bash mount-s3 flowfs-YOUR-ORG-ID /mnt/flowxo \ --endpoint-url https://flowfs-s3.flowxo.com \ --profile flowxo ``` mountpoint-s3 won't replace an existing file unless you add `--allow-overwrite`. ## Keep keys safe Anyone with a key ID and its secret can read and change every file the key reaches. Treat the secret like a password. Keys don't expire. To stop one working, choose **Revoke** next to it in **Settings → Access keys**. Tools using it stop working immediately, and this can't be undone. --- Source: https://docs.flowxo.com/reference/formulas # Formula reference Formulas work out a value from data the workflow already has. They're used in a **Calculation** step, in any value set to **Calc**, inside `{{ }}` in text and JSON, and in branch rules. Formulas use [CEL](https://cel.dev), with a few changes that make them forgiving: - A field that doesn't exist reads as empty instead of failing. - Empty text, `0`, empty lists and empty values count as false. - Every number is a decimal, so `3` and `2.5` mix freely. Type `{{` or press **Ctrl+Space** in a formula to choose an input, variable or earlier result. In a Calc value the reference goes in without braces, like `input.quantity`. ## Sample data Every example on this page runs against this data. The build checks each one against the real formula engine, so the results are exactly what a workflow gets. ```json { "input": { "name": "Ada Lovelace", "email": " Ada@Example.com ", "quantity": 3, "price": 2.5, "tags": [ "vip", "beta" ], "company": "" }, "nodes": { "lookup": { "output": { "found": true, "row": { "Status": "Active", "Plan": "Pro" } } } }, "total": 10 } ``` `input` holds the workflow's inputs. `nodes..output` holds an earlier step's results, and workflow variables are read by name, like `total`. ## Operators | Operator | Meaning | Example | Result | | --- | --- | --- | --- | | `+` | Add two numbers, or join two pieces of text | `input.quantity + 1` | `4` | | `-` | Subtract, or make a number negative | `total - input.quantity` | `7` | | `*` | Multiply | `input.quantity * input.price` | `7.5` | | `/` | Divide. The result can be a decimal | `10 / 4` | `2.5` | | `==` | Equal to | `input.name == "Ada Lovelace"` | `true` | | `!=` | Not equal to | `input.quantity != 3` | `false` | | `<` | Less than | `input.price < 3` | `true` | | `<=` | Less than or equal to | `input.price <= 2.5` | `true` | | `>` | Greater than | `input.quantity > 2` | `true` | | `>=` | Greater than or equal to | `input.quantity >= 5` | `false` | | `&&` | Both sides are true | `input.quantity > 2 && input.price < 3` | `true` | | `\|\|` | Either side is true | `input.quantity > 5 \|\| nodes.lookup.output.found` | `true` | | `!` | Not. Empty text, zero and empty values count as false | `!input.company` | `true` | | `? :` | If the first part is true, the second, otherwise the third | `input.company ? input.company : "No company"` | `"No company"` | | `in` | The list contains the value | `"vip" in input.tags` | `true` | | `.` | A field inside a value | `nodes.lookup.output.row.Status` | `"Active"` | | `[ ]` | An item in a list, counting from 0 | `input.tags[0]` | `"vip"` | Use brackets to group parts: `(input.quantity + 1) * input.price`. ## Functions | Function | What it gives | Example | Result | | --- | --- | --- | --- | | `has()` | Whether a field exists. Takes one field path | `has(input.email)` | `true` | | `size()` | Characters in text, items in a list, or fields in an object | `size(input.tags)` | `2` | | `string()` | Turn a value into text | `string(input.quantity) + " items"` | `"3 items"` | | `double()` | Turn text into a number | `double("2.5") * 2` | `5` | ## Methods Call a method on a value with a dot. | Method | What it gives | Example | Result | | --- | --- | --- | --- | | `.size()` | Characters in text, or items in a list | `input.name.size()` | `12` | | `.contains()` | Whether text contains other text. Case-sensitive | `input.name.contains("Love")` | `true` | | `.startsWith()` | Whether text starts with other text. Case-sensitive | `input.name.startsWith("Ada")` | `true` | | `.endsWith()` | Whether text ends with other text. Case-sensitive | `input.name.endsWith("lace")` | `true` | | `.lowerAscii()` | Text in lower case. Changes A to Z only | `input.email.trim().lowerAscii()` | `"ada@example.com"` | | `.upperAscii()` | Text in upper case. Changes a to z only | `input.name.upperAscii()` | `"ADA LOVELACE"` | | `.trim()` | Text without spaces at the start and end | `input.email.trim()` | `"Ada@Example.com"` | | `.join()` | A list of text joined into one piece of text, with an optional separator | `input.tags.join(", ")` | `"vip, beta"` | Methods can be chained: `input.email.trim().lowerAscii()`. ## Missing fields `input.missing` gives `null`. Check for a field with `has()`, or supply a fallback with `? :`. ## Errors A formula that can't run fails its step. The step's error names one of these codes. | Error code | What happened | Example that fails | | --- | --- | --- | | `runtime-workflow.expression.invalid` | The formula isn't valid: a typo, an unsupported operator or function, or it's too long | `input.quantity % 2` | | `runtime-workflow.expression.division_by_zero` | Something was divided by zero | `total / 0` | | `runtime-workflow.expression.type_mismatch` | An operator got values it can't use, like text times a number. Use string() or double() to convert first | `input.name + 1` | | `runtime-workflow.expression.number_out_of_range` | A number got too large to store | `1e308 * 10` | Text and numbers never convert by themselves. Use `string()` to turn a number into text, and `double()` to turn text into a number. ## Limits A formula can be up to 1,024 characters long. There are no loops, regular expressions or date functions. --- Source: https://docs.flowxo.com/reference/steps # Step types The steps in the workflow editor's add-step dialog, with the dialog's own descriptions. This list is generated from the editor, so it always matches what you see. Each name links to its guide in [Steps](/workflows/steps). ## Common | Step | What it does | | --- | --- | | [Invoke tool](/workflows/steps#invoke-tool) | Use a connected app or built-in tool to perform an action. | | [Calculation](/workflows/steps#calculation) | Calculate a value from earlier data and save it for later steps. | | [Log](/workflows/steps#log) | Write a message and optional data to the workflow log. | | [Set variables](/workflows/steps#set-variables) | Save a value for later steps in this workflow. | | [Classify](/workflows/steps#classify) | Choose one of your classes and follow its connected exit. | | [Extract](/workflows/steps#extract) | Extract named fields from text for later steps. | ## Advanced | Step | What it does | | --- | --- | | [Transform](/workflows/steps#transform) | Make named outputs from earlier data. Use Advanced JSON for complex shapes. | | [Wait](/workflows/steps#wait) | Pause until an approval, a time, or an outside event. | Steps saved before a type was replaced keep working. Agent judgment, for example, still runs in older workflows, but new workflows use [Classify](/workflows/steps#classify) and [Extract](/workflows/steps#extract) instead. --- Source: https://docs.flowxo.com/reference/limits # Workflow limits The limits every organization starts with. They come straight from Flow XO's limits catalog, so this page always shows the current defaults. | Limit | Default | Support can change it | | --- | --- | --- | | Steps in one workflow | 50 | Yes | | Connections between steps in one workflow | 100 | Yes | | Branches leaving one step | 8 | Yes | | Retries of a failing step | 10 | Yes | | Wait between two attempts of a step | 1 min | Yes | | Growth factor of the wait between attempts | 10× | No | | How long a step may run when it sets no timeout | 30 s | No | | Longest timeout a step may set | 2 min | Yes | | Longest an approval or timer step may wait | 7 days | Yes | | Active run time of one workflow run (waits excluded) | 5 min | Yes | | Run data one workflow run may hold (variables and step results) | 1 MB | Yes | Need more? Ask [support@flowxo.com](mailto:support@flowxo.com) about the ones marked Yes. Time spent in a [Wait](/workflows/steps#wait) step doesn't count toward a run's active time. Formulas have their own [limits](/reference/formulas#limits).