Configure Workflow Nodes
How do you turn an action into a working workflow step? Add the action, select its account, configure its fields in dependency order, and wire its inputs. Then resolve its output type. A node can be ready to run before its output type is known.
Work in the development version, where tests call providers for real. Before testing a read, choose the account and invoice record. Before a send or a write, choose a safe destination.
Find the exact action
Section titled “Find the exact action”In the app: choose Workflows → Invoice intake → Dev → flow → Add action. Search Actions, select the integration, then select the action.
With an assistant: list the workflows first.
cai workflow list --limit 100 --jsonSelect Invoice intake’s id from data.items as <workflowId>. If several match, ask which one. If data.hasMore is true and it is absent, open it in the app and take the ID after /workflows/ in its URL. This page pins the workflow once, so later CLI commands omit --workflow. Scoped MCP tools always require workflowId.
Pin the workflow, read its outline, and search for the action.
cai use <workflowId> --jsoncai workflow outline --jsoncai integration actions "read invoice" --jsonTake the intended data.flows[].id as <flowId>. From the action result’s data.items, select the exact nodeSlug as <slug> and key as <componentKey>. Narrow the query if data.hasMore is true.
Inspect the action, then add it.
cai integration action --integration <slug> --key <componentKey> --jsoncai node add --flow <flowId> --node <slug> --key <componentKey> --name "Read invoice" --jsonMCP uses integration_action_list with query, integration_action_get with integration and key, and node_add with flow, node, key, and name.
Note the added node’s data.nodeId as <nodeId>.
Select the account before its resources
Section titled “Select the account before its resources”In the app: choose Connection, or Connect / Add new connection to authorize an account.
With an assistant: list the connected accounts.
cai connection list --integration <slug> --jsonChoose the intended ACTIVE account from data.items, using its id as <connectionId>. If several active accounts match and no preference exists, ask which one. If none exists, start an authorization:
cai connection connect <slug> --no-open --jsonMCP uses connection_connect_url with nodeSlug.
Have the person authorize data.url. Then rerun the connection list and select the new ID. Bind it, reload it, and read the props back:
cai node set-connection <nodeId> --connection <connectionId> --jsoncai node reload <nodeId> --jsoncai node props <nodeId> --jsonMCP uses node_connection_set with connection, node_prop_reload, and node_prop_list.
Binding checks the saved status and the integration, not the provider’s authorization. Changing the connection resets remote-option values to defaults and clears the cached field catalog. Reselect the affected values after reloading.
Configure selectors before dependent fields
Section titled “Configure selectors before dependent fields”In the app: choose the invoice spreadsheet before its sheet and columns. In CLI data.props, inspect required, reloadProps, and option metadata. Setting a concrete value on a reload selector re-resolves the later fields, which can be added, removed, or left unchanged, and selections can be cleared. Reread the props after each selector write. node props only reads. node reload re-resolves. If a selector is unset, an explicit reload preserves the dependent fields instead of removing them.
Use the field’s name as <name> and search by the resource’s label as <q>:
cai node options <nodeId> --prop <name> --query '<q>' --jsonCopy a matching data.options entry’s value as <literal> and label as <label>, then write it and reread the props:
cai node set <nodeId> --prop <name> --value '<literal>' --label '<label>' --jsoncai node props <nodeId> --jsonMCP uses node_prop_options with query, and node_prop_set with value and a label array, even for one selection.
In a multi-select, labels must align with values. A response carries at most 100 options. For remote options, narrow the search when data.hasMore is true, or pass the returned data.context as JSON with --prev-context. Static lists ignore query, report hasMore: false, and hide entries beyond 100 through this operation. Repeat --for '<prop>=<value>' to preview an earlier selector without saving it. MCP uses parsed prevContext and a for array.
blocked_upstream means you must set blockedBy first. dynamic_upstream means an earlier expression prevents a concrete option list, so use a raw value or an expression. available means no selector blocks the lookup, and absent metadata means unknown. Configure concrete selectors and their dependent fields before switching to expressions, because an expression preserves the last concrete selection’s dependent schema instead of discovering a new one.
Enable fields and wire their values
Section titled “Enable fields and wire their values”In the app: enable Optional inputs, draw an edge from the producer, then select its value in the field’s expression editor. An edge makes a value available. It does not assign an action field.
node set and expr set enable an optional field automatically before writing to it. A write that fails can leave the field enabled. Disable a field only to delete its stored value. When the base field list is available, disabling a reload field also prunes dependents outside that base set.
cai node disable-prop <nodeId> --prop <name> --jsoncai node props <nodeId> --jsonMCP uses node_optional_prop_set with enabled: false.
Take the producer’s id from the outline’s selected flow’s nodes as <sourceNodeId>. Connect it, then read the field’s bindings:
cai node connect --flow <flowId> --from <sourceNodeId> --to <nodeId> --jsoncai expr context <nodeId> --prop <name> --jsonUse the intended data.inputs[].js binding as <src>:
cai expr set <nodeId> --prop <name> --js '<src>' --jsonMCP writes expressions with node_prop_set using js. Router and If / Else require branch connections.
Make downstream fields available
Section titled “Make downstream fields available”In the app: open Output → Action output format, select a supported format, or test representative data.
With an assistant: check readiness, then read the inputs a test needs.
cai node status <nodeId> --jsoncai run inputs <nodeId> --jsonStatus reports data.readyForRun, data.readyForDownstreamExpressions, and data.blockedBy. Readiness does not prove correct wiring. For an isolated test, build <json> from the input readback, keyed by every referenced data.flowInputs[].id, with safe invoice values. If you leave one out, the test fails with ISOLATED_FLOW_CONTEXT_REQUIRED. Explicit null, empty text, false, and zero count as supplied. Upstream nodes do not execute, so supply wired-input fixtures using data.nodeInputs[].id, then verify upstream expressions with the complete-flow test below.
cai run node <nodeId> --inputs '<json>' --jsonMCP uses run_inputs with nodeIdOrFlowId, then run_node with parsed inputs.
Inspect data.resolvedConfigs, data.output, and data.outputTypeAdoption, then reread status and destination context. Adoption belongs to that exact node, and scratch discovery cannot type it. no_observed_output means no reusable type was found.
If you prefer not to run the node, declare the shape instead. An unresolved or still-declared dynamic action output accepts a documented JSON object or non-empty array saved as invoice-response.json. Static outputs, flow calls, nodes without an output, and already-observed contracts reject a declaration.
cai node declare-output <nodeId> --sample-file invoice-response.json --jsonMCP uses node_output_type_declare with parsed sample, not a filename.
The shape stays unverified until a completed run of the unchanged node yields a reusable type. Adoption clears the declaration marker and replaces a declared type that does not match. A no_observed_output result preserves the declaration.
Use HTTP when the action does not fit
Section titled “Use HTTP when the action does not fit”A missing connection calls for authorization. Choose Send HTTP request when the catalog action cannot perform the API operation you need.
In the app: add that action and configure these fields:
| Field | Behavior |
|---|---|
Method (method) | GET, POST, PATCH, PUT, DELETE, HEAD. |
URL (url) | Required invoice API endpoint. |
Authentication (authType) | None by default; Basic Auth reveals username/password; Bearer Token reveals token. Have the person enter credentials in the app, never chat. |
Body Type (body_type) | None by default; JSON, Raw, or Form Data reveals body. Set the selector before its body. |
Timeout (in seconds) (timeout) | Empty means no HTTP-client timeout; set a limit such as 30. |
With an assistant: find it first:
cai integration actions "Send HTTP request" --jsonSelect its returned nodeSlug as <slug> and key as <componentKey>:
cai integration action --integration <slug> --key <componentKey> --jsoncai node add --flow <flowId> --node <slug> --key <componentKey> --name "Read invoice API" --jsonNote data.nodeId as <httpNodeId>, then set your invoice API’s GET endpoint as <literal>:
cai node set <httpNodeId> --prop method --value GET --label GET --jsoncai node set <httpNodeId> --prop url --value '<literal>' --jsoncai node set <httpNodeId> --prop timeout --value 30 --jsonSet authentication to match that API. A successful request returns only the body, without status or headers. A final non-2xx response fails the action. The output starts without a fixed type, so use the adoption procedure above.
Verify the complete flow
Section titled “Verify the complete flow”In the app: choose the flow in Dev → Test, then inspect Run history.
With an assistant: lay out the flow, validate it, and read back its inputs.
cai flow layout <flowId> --jsoncai workflow validate --jsoncai run inputs <flowId> --flow --jsonResolve validation errors first. Then build <json> from the returned data.flowInputs[].id keys, with safe values such as INV-104 and 120:
cai run flow <flowId> --target dev --inputs '<json>' --jsonMCP uses run_inputs with nodeIdOrFlowId and flow: true, then run_flow with flowId, target, and parsed inputs.
Check data.nodeExecutions for resolved inputs, statuses, and outputs, and confirm the invoice read or the intended external effect. Inspect data.outputTypeAdoptions, and reread the downstream context after an adoption. These changes and adopted types now belong to development.