# Workflows

A **Workflow** is an automation graph that runs at the account level — not tied to any single agent. Workflows fire on triggers (conversation events, schedules, incoming webhooks), execute a graph of nodes, and can call external APIs, transform data, write to Tables, send messages, etc.

If a Flow is "what the agent says next", a Workflow is "what happens around the agent".

## Identity

| Field | Type | Notes |
|  --- | --- | --- |
| `id` | number | Use as `workflowId`. |
| `name` | string |  |
| `description` | string? |  |
| `status` | enum | `ACTIVE` · `DRAFT` · `PAUSED` · `ARCHIVED`. |
| `triggerType` | enum? | E.g. `CONVERSATION_ENDED`, `CONVERSATION_STARTED`, `SCHEDULED`, etc. |
| `runsCount` | number | Total executions. |
| `runsFailedCount` | number | Total failed executions. |
| `lastRunDate` | string? | ISO timestamp of the most recent run. |
| `liveSnapshotId` | string? | Published snapshot. |
| `draftSnapshotId` | string? | Working draft. |


## The graph

### Node types

| Type | Purpose |
|  --- | --- |
| `TRIGGER` | Event-based entry point (conversation ended, contact created, …). |
| `SCHEDULED_TRIGGER` | Cron-style entry point. |
| `WEBHOOK` | Entry point fed by an [Incoming Webhook](/docs/concepts/incoming-webhooks). |
| `API` | Call an external HTTP endpoint. |
| `TOOLS_AI` | Let the AI choose a Tool to call. |
| `CONDITIONAL_ROUTING` | Branch based on AI-evaluated conditions. |
| `AI_CAPTURE` | Extract structured data from text using an LLM. |
| `DATA_TRANSFORMER` | Reshape data via prompt. |
| `DYNAMIC_TABLES` | Create, update, delete, search, or change the record type of records in a Table or Object. |
| `CREATE_RECORD_ACTIVITY` | Log a manual activity (note, call, meeting, email, WhatsApp) on a specific Object record. |
| `SEND_MESSAGE` | Push a message to a conversation. |
| `TRANSCRIPTION` | Transcribe an audio URL. |


Each node carries `nodeId`, `alias`, `position`, `type`, and a typed `data` payload.

> **Reference validation**: on create/update, the API validates every cross-resource reference inside `data` — `aiModelId`, `customToolIds`, `tableId`, `recordTypeId`, `triggeredByAgentIds[]`, `triggerByWebhookIds[]`, `connectedAccountId`, `knowledgeBaseIds`, etc. If any referenced id does not exist or does not belong to your account, the request returns `400 bad_request` with `details.issues[].code === "not_found"` and the node is **not** persisted. See [Errors](/docs/errors#example-node-references).
Schema-level checks also apply to `SCHEDULED_TRIGGER` (`cronExpression` validated by `cron-validate`, `timezone` against IANA), `WEBHOOK` and `API` (`url` must be a valid URL).


### Runtime variables

Automation workflows expose **one runtime variable per node**, with `name = nodeId`. A node `api_1` can be referenced downstream as `{api_1}`. This is in addition to explicitly declared [Workflow Variables](/docs/concepts/workflow-variables).

Interpolable fields include the same set as Flows, plus:

- AI Capture: `prompt`, `instructions`.
- Data Transformer: `prompt`.
- Tools AI: `instructions`, `prompt`.
- Transcription: `audioUrl`.
- Create Record Activity: `rowId`, `content`.
- WhatsApp template variables: `templateVariables.header`, `body`, `buttons`.


**Capturing specific values.** The `API` node can extract values from its JSON response into named variables via its `variables` field (`{ key, value, fullResponse }`), and `AI_CAPTURE` / `TOOLS_AI` / `TRANSCRIPTION` populate variables via `captureVariables`. **All capture targets must reference a variable that already exists** (create it first via `POST /workflows/{workflowId}/variables`). In `captureVariables` you may reference it by `{ "name": "<var>" }` or `{ "id": <id> }` — the API resolves and links it to the canonical `{ id, name, description }`; an unknown name/`key` returns `400`. For the API node's `value` **path syntax** (dot/`[n]` property access into the JSON response), see [Flows → API node response paths](/docs/concepts/flows#api-node-response-paths-value).

### `CREATE_RECORD_ACTIVITY` node

Logs a manual activity on a specific Object record at workflow runtime.


```json
{
    "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": "PHONE_CALL",
        "content": "Llamada de seguimiento: {call_summary}"
    }
}
```

| Field | Required | Notes |
|  --- | --- | --- |
| `recordTypeId` | ✅ | Numeric id of the Object's record type. Validated: must exist and belong to your account. |
| `rowId` | ✅ | MongoDB ObjectId of the record to log on. Supports `{varName}` interpolation. |
| `activityType` | ✅ | One of: `NOTE`, `EMAIL`, `PHONE_CALL`, `MEETING`, `WHATSAPP`. Validated at save time. |
| `content` | ✅ | Activity body text. Supports `{varName}` interpolation. |


**Validation:** `recordTypeId` must point to a real record type on an Object (not a Table) in your account. `activityType` must be a valid enum value. Variables in `rowId` and `content` must exist — the API returns `400` with the offending variable name if any reference is unknown.

## Operations

| Verb | Path | Purpose |
|  --- | --- | --- |
| `GET` | `/public/v1/workflows` | List, with `status` filter |
| `POST` | `/public/v1/workflows` | Create |
| `GET` | `/public/v1/workflows/{workflowId}` | Detail (`?includeNodes=true` for nodes) |
| `PUT` | `/public/v1/workflows/{workflowId}` | Update metadata / status |
| `DELETE` | `/public/v1/workflows/{workflowId}` | Soft delete + cleanup of triggers |
| `GET` | `/public/v1/workflows/{workflowId}/graph` | Full graph |
| `POST` | `/public/v1/workflows/{workflowId}/nodes` | Create node |
| `PUT` | `/public/v1/workflows/{workflowId}/nodes/{nodeId}` | Update node |
| `DELETE` | `/public/v1/workflows/{workflowId}/nodes/{nodeId}` | Delete node + incident edges |
| `POST` | `/public/v1/workflows/{workflowId}/edges` | Add edge |
| `DELETE` | `/public/v1/workflows/{workflowId}/edges` | Remove edge |
| `GET` | `/public/v1/workflows/{workflowId}/analytics` | Run analytics |
| `GET` | `/public/v1/workflows/{workflowId}/logs` | List run logs (history) |
| `GET` | `/public/v1/workflows/{workflowId}/logs/{logId}` | One run + per-node results |


## Run logs

Every time a workflow runs it records an execution. Read them to audit results,
debug failures, or track credit usage.

**List** `/public/v1/workflows/{workflowId}/logs` — paginated, with optional
`status`, `start_date`, and `end_date` filters. Each run (basic fields):

| Field | Notes |
|  --- | --- |
| `id` | Use as `logId`. |
| `successful` | Whether the run completed without error. |
| `started_at` | ISO timestamp. |
| `completed_at` | ISO timestamp (null while running). |
| `duration` | Milliseconds. |
| `operations` | Number of node operations executed. |
| `ai_credits` | AI credits consumed. |
| `error` | Error message if the run failed. |
| `insufficient_credits` | Run stopped because the account ran out of credits. |
| `prevented_loop` | Run stopped because a loop was detected. |


**Detail** `/public/v1/workflows/{workflowId}/logs/{logId}` — the run above plus
`node_results: [{ node_id, alias, type, success, error, ai_credits, created_at }]`,
one entry per node that executed.

## CLI


```bash
frontline workflows list --table
frontline workflows create --name "Daily CRM Sync"
frontline workflows nodes create --data '{"nodeId":"trigger_1","type":"TRIGGER","position":{"positionX":0,"positionY":0},"data":{"type":"TRIGGER","triggerType":"CONTACT_CREATED"}}'
frontline workflows analytics --start-date 2026-01-01 --end-date 2026-12-31
frontline workflows logs --workflow-id 2 --table
frontline workflows logs --workflow-id 2 --status FAILED --start-date 2026-01-01
frontline workflows logs get 9001 --workflow-id 2 --pretty
```