This document is written for the engineer building the frontend UI for the Flow module. It covers every screen, all API endpoints with exact request/response shapes, and the business rules the UI must enforce.
- What Is Flow?
- Core Concepts
- Screens to Build
- API Reference
- Business Rules the UI Must Enforce
- Data Shapes (Response Objects)
- UI Interaction Flows
Flow is a generic pipeline engine. Any object in the platform (a CRM opportunity, a support ticket, a provisioning request, etc.) can be enrolled in a pipeline and moved through ordered stages — without modifying the original object's table.
Think of it as a Kanban board engine that can be attached to anything.
| Term | What it is |
|---|---|
| Pipeline | A named board. Has ordered stages and optional custom fields. Can be a template or a live instance. |
| Stage | A column on the board. Has position, color, SLA, win/lost flag, and an optional checklist. |
| Item | A card on the board. Links any platform object to one stage within a pipeline. |
| Column | A custom field definition for a pipeline (e.g. "Contract Value", "Priority"). |
| Item Value | The actual value of a custom column for a specific item (EAV). |
| Stage Required Columns | Columns that must have a value before an item can enter a particular stage. |
| Stage History | Immutable audit log — every stage transition an item has gone through. |
| Automation | A rule that fires a named platform event when a trigger condition is met (stage entered/exited, item created, SLA breached). |
| Item Watcher | A user subscribed to updates on an item. |
- Show all pipelines for the current account.
- Columns: Name, Object Type, Status (active/inactive), Template badge, Created At.
- Actions: Create, Edit, Delete, Clone from Template.
- Filter by:
name,object_type,is_active,is_template.
Inside a pipeline, a settings view with tabs:
- Drag-and-drop to reorder stages (PATCH
position). - Create / edit / delete stages.
- Stage fields: Name, Color (hex picker), Probability (0–100 slider), SLA Days, Is Won toggle, Is Lost toggle.
- Checklist editor: add/remove/reorder checklist items per stage (key + label + required toggle). Stored as JSON array on the stage.
- At most one stage can be
is_won = trueand oneis_lost = true— enforce this in the UI.
- Create / edit / delete custom columns for the pipeline.
- Column fields: Name (machine key), Label (display), Field Type (see types below), Default Value, Is Required, Position.
- For
selectandmulti_selecttypes: an options list editor (add/remove string options). - Drag-and-drop to reorder columns.
Column field types:
field_type |
Input to render |
|---|---|
text |
Text input |
number |
Number input |
date |
Date picker |
boolean |
Toggle / checkbox |
select |
Dropdown (single) from options array |
multi_select |
Multi-select from options array |
json |
Textarea / code editor |
- For each stage: a multi-select of columns that must be filled before an item can enter.
- Show as a matrix or per-stage accordion.
- Uses
POST /flow/stage-required-columnsandDELETE /flow/stage-required-columns/{id}.
- List automations for the pipeline.
- Fields: Trigger type, Stage (optional — "any stage" or a specific stage), Event Name, Active toggle.
- Trigger types:
stage_entered,stage_exited,item_created,sla_breached. - Payload Template: optional JSON editor for the event payload.
Main working view. Shows items grouped by stage as columns.
- Load stages:
GET /flow/stages?flow_pipeline_id={uuid} - Load items:
GET /flow/items?flow_pipeline_id={uuid} - Each card shows: linked object reference (
object_type+object_id), assigned user,last_stage_changed_at, SLA breach indicator.
SLA breach indicator: if the stage has sla_days set and last_stage_changed_at is older than sla_days days, show a visual warning (red border, clock icon, etc.).
Moving a card (stage transition):
- User drags card from one stage column to another.
- Before moving: call validation (see Stage Move Flow).
- On success: PATCH the item with the new
flow_stage_id. - On 422 error: show which required columns are missing values and open the item detail panel.
Opens when clicking a card. Contains:
- Header: Object reference, assigned user selector, current stage badge.
- Custom Fields section: Show all columns for the pipeline. Each field is editable inline. Save via
ItemValuesupsert. - Checklist section: If the current stage has a
checklistarray, render each item as a checkbox. Read state fromitem.checklist_state. On toggle: PATCH the item with updatedchecklist_state. - Stage History tab: Timeline of all stage transitions (from → to, who moved it, when).
- Watchers tab: List of watchers. Add / remove watcher.
- Separate list of pipelines where
is_template = true. - "Clone to account" action: triggers
PipelinesService::cloneFromTemplate()(no dedicated endpoint yet — this may need a custom action or be handled by a backend action endpoint).
- Base path:
/flow(all endpoints are under this prefix) - All IDs are UUIDs. The API returns and accepts UUIDs, never integer IDs.
- Filtering: Pass filter fields as query parameters on list endpoints.
- Example:
GET /flow/items?flow_pipeline_id=<uuid>&flow_stage_id=<uuid>
- Example:
- Pagination: Add
paginate=truequery param to get paginated results.- Optional:
per_page=20&page=1
- Optional:
- Soft deletes: Deleted records are excluded from results automatically.
- Authentication: Standard platform Bearer token — include
Authorization: Bearer {token}on all requests.
GET /flow/pipelines
Query filters: name, object_type, is_template, is_system, is_active,
iam_account_id, iam_user_id, created_at_start, created_at_end
GET /flow/pipelines/{uuid}
POST /flow/pipelines
Content-Type: application/json
{
"name": "Sales Pipeline",
"description": "Main sales process",
"object_type": "crm_opportunity",
"is_template": false,
"is_system": false,
"is_active": true
}
iam_account_id and iam_user_id are set automatically from the session.
PATCH /flow/pipelines/{uuid}
Content-Type: application/json
{
"name": "Updated Name",
"is_active": false
}
DELETE /flow/pipelines/{uuid}
GET /flow/stages?flow_pipeline_id={uuid}
Query filters: flow_pipeline_id, name, is_won, is_lost, is_active
Sort by position to get them in order.
GET /flow/stages/{uuid}
POST /flow/stages
Content-Type: application/json
{
"flow_pipeline_id": "<pipeline-uuid>",
"name": "Qualified",
"color": "#60a5fa",
"position": 1,
"probability": 30,
"sla_days": 14,
"is_won": false,
"is_lost": false,
"is_active": true,
"checklist": [
{ "key": "budget_confirmed", "label": "Budget confirmed", "required": true },
{ "key": "decision_maker_identified", "label": "Decision maker identified", "required": false }
]
}
checklist is optional. When provided it must be a JSON array of objects with key, label, and required.
PATCH /flow/stages/{uuid}
Content-Type: application/json
{
"position": 2,
"sla_days": 7
}
DELETE /flow/stages/{uuid}
GET /flow/items?flow_pipeline_id={uuid}
Query filters: flow_pipeline_id, flow_stage_id, object_type, object_id,
assigned_iam_user_id, iam_account_id, iam_user_id
GET /flow/items/{uuid}
POST /flow/items
Content-Type: application/json
{
"flow_pipeline_id": "<pipeline-uuid>",
"flow_stage_id": "<first-stage-uuid>",
"object_type": "crm_opportunity",
"object_id": 42,
"assigned_iam_user_id": "<user-uuid>",
"position": 0
}
The backend automatically records the initial stage history entry and fires item_created automations.
PATCH /flow/items/{uuid}
Content-Type: application/json
{
"flow_stage_id": "<target-stage-uuid>"
}
This is the stage move operation. The backend:
- Validates all required columns for the target stage have values.
- Resets
checklist_stateto null (new stage's checklist starts fresh). - Sets
last_stage_changed_atto now. - Records a history entry.
- Fires
stage_exitedandstage_enteredautomations. - Notifies watchers.
On validation failure (missing required columns):
- HTTP
422 Unprocessable Entity - Response body contains which column IDs are missing.
- The UI must show the user which fields to fill in before the move is allowed.
PATCH /flow/items/{uuid}
Content-Type: application/json
{
"checklist_state": {
"budget_confirmed": {
"completed": true,
"completed_by": "<user-uuid>",
"completed_at": "2026-05-04T10:00:00Z"
},
"decision_maker_identified": {
"completed": false,
"completed_by": null,
"completed_at": null
}
}
}
Send the full updated checklist_state object. The checklist definition (keys and labels) comes from the item's current stage.checklist.
PATCH /flow/items/{uuid}
Content-Type: application/json
{
"assigned_iam_user_id": "<user-uuid>"
}
DELETE /flow/items/{uuid}
GET /flow/columns?flow_pipeline_id={uuid}
POST /flow/columns
Content-Type: application/json
{
"flow_pipeline_id": "<pipeline-uuid>",
"name": "contract_value",
"label": "Contract Value",
"field_type": "number",
"default_value": null,
"is_required": false,
"position": 1,
"is_active": true
}
For select / multi_select types, include options:
{
"flow_pipeline_id": "<pipeline-uuid>",
"name": "priority",
"label": "Priority",
"field_type": "select",
"options": ["High", "Medium", "Low"],
"is_required": false,
"position": 3,
"is_active": true
}PATCH /flow/columns/{uuid}
Content-Type: application/json
{
"label": "Deal Value",
"position": 0
}
DELETE /flow/columns/{uuid}
Item values store the custom column data for an item (EAV pattern). All values are stored as strings regardless of the column's field_type — the UI is responsible for casting when rendering.
GET /flow/item-values?flow_item_id={uuid}
POST /flow/item-values
Content-Type: application/json
{
"flow_item_id": "<item-uuid>",
"flow_column_id": "<column-uuid>",
"value": "42500"
}
If a value already exists for this (item, column) pair it is updated. There is no separate PATCH needed — always POST to set a value.
DELETE /flow/item-values/{uuid}
Value encoding by field type:
field_type |
How to encode value |
|---|---|
text |
Plain string: "Acme Corp" |
number |
Numeric string: "42500" |
date |
ISO 8601 string: "2026-07-01" |
boolean |
"true" or "false" |
select |
One of the strings in column options: "High" |
multi_select |
JSON array string: '["High","Urgent"]' |
json |
Any valid JSON string |
GET /flow/stage-required-columns?flow_stage_id={uuid}
POST /flow/stage-required-columns
Content-Type: application/json
{
"flow_stage_id": "<stage-uuid>",
"flow_column_id": "<column-uuid>"
}
DELETE /flow/stage-required-columns/{uuid}
Read-only from the UI perspective. The backend writes all history entries automatically.
GET /flow/stage-history?flow_item_id={uuid}
Response fields: id, flow_item_id, flow_pipeline_id, from_stage_id (null for first entry), to_stage_id, moved_by_iam_user_id, moved_at
Render as a timeline sorted by moved_at ascending. The first entry has from_stage_id = null (initial placement).
GET /flow/automations?flow_pipeline_id={uuid}
POST /flow/automations
Content-Type: application/json
{
"flow_pipeline_id": "<pipeline-uuid>",
"flow_stage_id": "<stage-uuid-or-null>",
"trigger": "stage_entered",
"event_name": "crm.opportunity.qualified",
"payload_template": {
"source": "flow",
"pipeline": "{{pipeline_id}}"
},
"is_active": true
}
flow_stage_id is optional. If null, the automation fires for all stages on this trigger.
Trigger values: stage_entered, stage_exited, item_created, sla_breached
PATCH /flow/automations/{uuid}
Content-Type: application/json
{
"is_active": false
}
DELETE /flow/automations/{uuid}
GET /flow/item-watchers?flow_item_id={uuid}
POST /flow/item-watchers
Content-Type: application/json
{
"flow_item_id": "<item-uuid>",
"iam_user_id": "<user-uuid>"
}
DELETE /flow/item-watchers/{uuid}
Before moving an item to a new stage the UI should optimistically attempt the PATCH.
If the API returns 422, parse the error to find which column IDs are missing and show
the user a dialog listing the required fields that still need values. The user must fill
them in before the move is allowed.
Do NOT block the drag-and-drop in the UI ahead of time — always try the API call and handle the 422 response. This keeps client-side logic simple and keeps validation server-authoritative.
When building or editing stages in the Pipeline Builder, ensure:
- At most one stage has
is_won = true. Toggling one on should automatically toggle any previously-won stage off (or prompt the user to confirm). - At most one stage has
is_lost = true. Same rule.
When an item successfully moves to a new stage, its checklist_state is reset by the
backend. The UI should refresh the item after a successful stage move and re-render the
checklist from the new stage's checklist definition.
For each item on the board, check: does now() - last_stage_changed_at > stage.sla_days?
- If yes: render a visual warning on the card (red border,
!badge, tooltip with time elapsed vs. SLA limit). - If
sla_daysis null on the stage: no SLA applies. - Stages with
is_won = trueoris_lost = truenever show SLA warnings.
The checklist definition is on stage.checklist (array of { key, label, required }).
The completion state is on item.checklist_state (map of key → { completed, completed_by, completed_at }).
- Render each checklist item as a checkbox.
- On toggle: build the updated
checklist_stateand PATCH the item. - If a checklist item has
required: true, you may choose to block stage transitions visually (though this is not enforced by the backend — stage entry requirements viastage_required_columnsare separate from checklists).
Pipelines with is_template = true should be shown in a separate "Templates" section
and not in the main board list. Cloning a template creates a fully independent live
pipeline — after cloning, changes to the live pipeline do not affect the template.
All responses wrap data in a standard envelope. Individual items are under data,
collections are under data as an array.
{
"id": "<uuid>",
"name": "Sales Pipeline",
"description": "Main sales process",
"object_type": "crm_opportunity",
"is_template": false,
"is_system": false,
"is_active": true,
"iam_account_id": "<uuid>",
"iam_user_id": "<uuid>",
"created_at": "2026-05-01T10:00:00Z",
"updated_at": "2026-05-01T10:00:00Z",
"deleted_at": null
}{
"id": "<uuid>",
"flow_pipeline_id": "<uuid>",
"name": "Qualified",
"color": "#60a5fa",
"position": 1,
"probability": 30,
"sla_days": 14,
"is_won": false,
"is_lost": false,
"checklist": [
{ "key": "budget_confirmed", "label": "Budget confirmed", "required": true }
],
"is_active": true,
"created_at": "2026-05-01T10:00:00Z",
"updated_at": "2026-05-01T10:00:00Z",
"deleted_at": null
}{
"id": "<uuid>",
"flow_pipeline_id": "<uuid>",
"flow_stage_id": "<uuid>",
"object_type": "crm_opportunity",
"object_id": 42,
"position": 0,
"last_stage_changed_at": "2026-05-02T08:30:00Z",
"checklist_state": {
"budget_confirmed": {
"completed": true,
"completed_by": "<user-uuid>",
"completed_at": "2026-05-03T09:00:00Z"
}
},
"iam_account_id": "<uuid>",
"iam_user_id": "<uuid>",
"assigned_iam_user_id": "<uuid>",
"created_at": "2026-05-01T10:00:00Z",
"updated_at": "2026-05-03T09:00:00Z",
"deleted_at": null
}{
"id": "<uuid>",
"flow_pipeline_id": "<uuid>",
"name": "contract_value",
"label": "Contract Value",
"field_type": "number",
"options": null,
"default_value": null,
"is_required": false,
"position": 1,
"is_active": true,
"created_at": "2026-05-01T10:00:00Z",
"updated_at": "2026-05-01T10:00:00Z",
"deleted_at": null
}{
"id": "<uuid>",
"flow_item_id": "<uuid>",
"flow_column_id": "<uuid>",
"value": "42500",
"created_at": "2026-05-02T10:00:00Z",
"updated_at": "2026-05-03T09:00:00Z"
}{
"id": "<uuid>",
"flow_item_id": "<uuid>",
"flow_pipeline_id": "<uuid>",
"from_stage_id": "<uuid-or-null>",
"to_stage_id": "<uuid>",
"moved_by_iam_user_id": "<uuid>",
"moved_at": "2026-05-02T08:30:00Z"
}{
"id": "<uuid>",
"flow_pipeline_id": "<uuid>",
"flow_stage_id": "<uuid-or-null>",
"trigger": "stage_entered",
"event_name": "crm.opportunity.qualified",
"payload_template": { "source": "flow" },
"is_active": true,
"iam_account_id": "<uuid>",
"created_at": "2026-05-01T10:00:00Z",
"updated_at": "2026-05-01T10:00:00Z",
"deleted_at": null
}{
"id": "<uuid>",
"flow_item_id": "<uuid>",
"iam_user_id": "<uuid>",
"created_at": "2026-05-01T10:00:00Z"
}1. GET /flow/pipelines/{uuid} → pipeline metadata
2. GET /flow/stages?flow_pipeline_id={uuid} → stage columns (sort by position)
3. GET /flow/items?flow_pipeline_id={uuid} → all items (group by flow_stage_id)
4. GET /flow/columns?flow_pipeline_id={uuid} → column definitions (for item detail)
1. User drags item from Stage A → Stage B
2. PATCH /flow/items/{item-uuid} { "flow_stage_id": "<stage-b-uuid>" }
├── 200 OK → update item in local state, refresh checklist from new stage definition
└── 422 → show "missing required fields" dialog
user fills fields via POST /flow/item-values (upsert)
retry PATCH /flow/items/{item-uuid}
1. GET /flow/items/{uuid} → item data
2. GET /flow/item-values?flow_item_id={uuid} → current column values
3. GET /flow/stage-history?flow_item_id={uuid} → transition timeline
4. GET /flow/item-watchers?flow_item_id={uuid} → watcher list
(columns already loaded at board level — reuse)
1. User edits field value in detail panel
2. POST /flow/item-values
{ "flow_item_id": "<uuid>", "flow_column_id": "<uuid>", "value": "..." }
→ always POST (backend does upsert — no need for conditional PUT/PATCH)
1. GET /flow/pipelines?is_template=true → list available templates
2. User selects a template and clicks "Use Template"
3. POST /flow/pipelines/{template-uuid}/do/clone → triggers cloneFromTemplate action
(or create pipeline + stages + columns manually if action endpoint is not wired)
4. Redirect to new pipeline's board
For each stage in the builder:
- Show a multi-select of all pipeline columns
- Pre-select those already in stage-required-columns for this stage
- On add: POST /flow/stage-required-columns { flow_stage_id, flow_column_id }
- On remove: DELETE /flow/stage-required-columns/{uuid}