A Flow is a conversation graph that belongs to a single Agent. It defines how the agent reacts to user input: which questions to ask, which AI behavior to run, which external API to call, which conditions to branch on.
Flows are agent-scoped. They are not reusable across agents — clone or recreate them if you need a similar shape elsewhere.
| Field | Type | Notes |
|---|---|---|
id | number | Use as flowId in nested routes. |
name | string | Display name. |
description | string? | |
status | enum | ACTIVE · DRAFT · PAUSED · ARCHIVED. |
runCount | number | Total executions. |
assistantId | string | Owning agent (always returned in detail). |
isDefault | boolean | One flow per agent is the default entry point. |
liveSnapshotId | string? | MongoDB ObjectId of the published snapshot. |
draftSnapshotId | string? | ObjectId of the working draft. |
createdAt | string | ISO timestamp. |
updatedAt | string | ISO timestamp. |
A flow's behavior lives in its graph: a set of nodes connected by edges.
| Type | Purpose |
|---|---|
START | Entry point. Every flow has exactly one. |
TRIGGER_INTENT | Branches the conversation when an Intent matches. |
SAY_AI | Sends a message. Optionally generated by the AI model. |
RESPONSE_AI | Captures the user's free-text answer guided by instructions. |
TOOLS_AI | Lets the AI decide which custom Tool to call (incl. table tools). |
API | Calls an external HTTP endpoint and stores the result. |
CONDITIONAL_ROUTING | Branches based on AI-evaluated conditions over the context. |
CREATE_RECORD_ACTIVITY | Logs a manual activity (note, call, meeting, email, WhatsApp) on a specific Object record. |
The
DYNAMIC_TABLES("Data Action") node is automation-only and is not available in flows. To read/write records inside a flow, use aTOOLS_AInode with table tools.
Each node has a stable nodeId (regex [a-zA-Z0-9_-]+_\d+), an alias, a position for the canvas, and a data payload whose shape depends on type.
Edges connect a node's handle (output port) to another node's targetEdge (input). The same edge object lives both inline on the node and as a global list — the API normalizes both on write.
Reference validation: on create/update, the API validates every cross-resource reference inside a node's
datapayload —intentIds(TRIGGER_INTENT),customToolIds/playbookIds/selectedRecordTypes[].id(TOOLS_AI),assistantId(RESPONSE_AI),aiModelId,knowledgeBaseIds, etc. If any referenced id does not exist or does not belong to your account, the request returns400 bad_requestwithdetails.issues[].code === "not_found"and no partial state is persisted. See Errors.
When you publish a flow, the draft snapshot is promoted to the live snapshot. The agent serves traffic from liveSnapshotId. Mutations via POST /nodes, PUT /nodes/{nodeId}, POST /edges, etc. write to the draft.
- Default Start flow. Every agent ships with a default flow named "Start" (in
DRAFT) containing a singleSTARTnode. It runs automatically at the beginning of every conversation — first-contact logic (greeting, capturing data, creating a record) belongs here, added after theSTARTnode. It's the flow withisDefault: true. - The
STARTnode is unique. Only the default Start flow may contain aSTARTnode; the API rejects aSTARTnode in any other flow with400. - Other flows are triggered. A non-default flow begins with a
TRIGGER_INTENTnode and is entered when it matches the user, configured by either:- Intents —
intentIdson theTRIGGER_INTENTnode; the flow fires when the user's message matches one of them. - Agentic routing —
agenticRouting, a natural-language instruction describing when to enter the flow; the agent routes based on it and the user's intention.
- Intents —
- Default-message rule. If a flow finishes a turn without producing a user-facing message (e.g. it only captured variables or ran a Dynamic Tables action), the agent sends a default AI-generated message so the user always receives a reply. This applies to every flow.
Text fields can reference variables as {VARIABLE_NAME}. The name must match exactly (case-sensitive). Interpolable fields include:
- API node:
url,headers[].value,parameters[].value,body. - Say AI:
message,prompt. - Response AI:
instructions. - Tools AI:
instructions. - Conditional Routing:
conditions[].expression. - Dynamic Tables:
rowId,search,rowDatavalues. - Create Record Activity:
rowId,content.
See Flow Variables.
Interpolation reads a variable; capturing writes one. The field that populates a variable depends on the node type:
| Node | Field | Item shape |
|---|---|---|
| Tools AI / Response AI / AI Capture | captureVariables | reference an existing variable |
| API | variables | { key, value, fullResponse } — key names an existing variable, value is a path into the JSON response |
Capture targets must reference a variable that already exists. Create it first (POST /agents/{agentId}/variables for flows, POST /workflows/{workflowId}/variables for automations). In captureVariables you may pass just { "name": "<var>" } (or { "id": <id> }) — the API resolves it and stores the canonical { id, name, description } so it is linked and rendered in the app. An unknown name/id (or an API variables[].key that doesn't exist) returns 400.
Captured values are matched by name, so an AI node and an API node can write to the same {name}. The two node families use different fields (captureVariables vs variables); the wrong field on a node is silently ignored.
For an API node, value is a property path into the parsed JSON response — simple property access, not JSONPath (no $, wildcards, or filters):
- Dot notation for nested objects:
data.user.email. [n]for array indices:data.items[0].id([0]is equivalent to.0; a leading dot is optional).- Empty
value, orfullResponse: true, captures the whole response body.
For the response {"data":{"items":[{"id":42,"price":9.99}],"total":1}}:
value | Captured |
|---|---|
data.total | 1 |
data.items[0].id | 42 |
data.items[0].price | 9.99 |
Path extraction only applies when the response body is JSON. If a step in the path is missing or null, the variable is left unset (no error). Captured objects/arrays are stored as a JSON string; primitives as their string value.
Logs a manual activity on a specific Object record during a conversation flow.
{
"nodeId": "node_create_activity_1",
"type": "CREATE_RECORD_ACTIVITY",
"position": { "positionX": 640, "positionY": 0 },
"data": {
"type": "CREATE_RECORD_ACTIVITY",
"recordTypeId": 5,
"rowId": "{contact_row_id}",
"activityType": "NOTE",
"content": "El usuario preguntó sobre: {user_question}"
}
}| Field | Required | Notes |
|---|---|---|
recordTypeId | ✅ | Numeric id of the Object's record type. Validated: must exist in your account. |
rowId | ✅ | MongoDB ObjectId of the record. Supports {varName} interpolation. |
activityType | ✅ | NOTE, EMAIL, PHONE_CALL, MEETING, or WHATSAPP. Validated at save time. |
content | ✅ | Activity body text. Supports {varName} interpolation. |
Variables in rowId and content must exist — the API returns 400 if any {varName} is unknown.
| Verb | Path | Purpose |
|---|---|---|
GET | /public/v1/agents/{agentId}/flows | List flows |
POST | /public/v1/agents/{agentId}/flows | Create |
GET | /public/v1/agents/{agentId}/flows/{flowId} | Detail (?includeNodes=true for nodes) |
PUT | /public/v1/agents/{agentId}/flows/{flowId} | Update metadata / status |
DELETE | /public/v1/agents/{agentId}/flows/{flowId} | Soft delete |
GET | /public/v1/agents/{agentId}/flows/{flowId}/graph | Full graph (nodes + edges) |
POST | /public/v1/agents/{agentId}/flows/{flowId}/nodes | Create node |
PUT | /public/v1/agents/{agentId}/flows/{flowId}/nodes/{nodeId} | Update node |
DELETE | /public/v1/agents/{agentId}/flows/{flowId}/nodes/{nodeId} | Delete node + incident edges |
POST | /public/v1/agents/{agentId}/flows/{flowId}/edges | Add/replace edge |
DELETE | /public/v1/agents/{agentId}/flows/{flowId}/edges | Remove edge |
frontline agents flows list --table
frontline agents flows create --name "Order Routing"
frontline agents flows nodes create --data '{"nodeId":"start_1","type":"START","position":{"positionX":0,"positionY":0}}'
frontline agents flows edges add --source start_1 --source-handle default --target say_1 --target-handle default