> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useorbitcrm.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflows API

> Build, publish, run and inspect workflows from code or an AI agent: the node catalogue, graphs of nodes and edges, activation, runs and AI agent templates.

A workflow is a graph: one trigger node (a form submission, a contact added, a deal stage change, a meeting booked…) connected to action nodes (create a deal or task, add a note, update the contact, run an AI agent, send to an integration) through filters, routers and delays. The same engine, validators and plan rules as the editor apply, so a graph an agent builds is held to the same standard as one drawn by hand. The [MCP server](https://docs.useorbitcrm.ai/mcp/tools.md) exposes the same operations as tools.

> [!NOTE]
>
> **Start with the schema.** `GET /api/v1/workflows/schema` returns every trigger and node type with its provider, the config fields each needs, the outputs it exposes to later nodes, plan requirements and the graph rules.

## Scopes

| Scope | Allows |
| - | - |
| `workflows:read` | Read workflows, runs and AI agent templates |
| `workflows:write` | Create, update, activate, pause and run workflows; manage AI agent templates. Active and running workflows carry out their steps, which can email people, enroll contacts in sequences, change CRM records and send data to connected apps and webhooks |
| `workflows:delete` | Delete workflows and AI agent templates |

## Endpoints

| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/api/v1/workflows/schema` | Node catalogue and graph rules |
| `GET` | `/api/v1/workflows` | List (`status`, `search`, `page`, `per_page`) with trigger, node and run counts |
| `POST` | `/api/v1/workflows` | Create a draft: `{ name, description?, trigger_config?, nodes?, edges?, folder_id? }` |
| `GET` | `/api/v1/workflows/:id` | The workflow with its nodes, edges and `has_unpublished_changes` |
| `PATCH` | `/api/v1/workflows/:id` | Change name, description, trigger config, nodes and edges (saved as a draft), `status`, or `folder_id` (a folder from `GET /api/v1/folders?kind=workflow`; `null` unfiles it) |
| `DELETE` | `/api/v1/workflows/:id` | Delete the workflow and its runs |
| `POST` | `/api/v1/workflows/:id/run` | Run now: `{ contact_ids, idempotency_key? }` or `{ trigger_data }`; both run in the background (202) |
| `GET` | `/api/v1/workflows/:id/runs` | Runs, newest first (`status`) |
| `GET` | `/api/v1/workflows/runs/:runId` | One run with every node step, input, output and timing |
| `GET` / `POST` | `/api/v1/agent-templates` | AI agent templates (instructions, criteria, model) the team owns plus shared ones / create |
| `PATCH` / `DELETE` | `/api/v1/agent-templates/:id` | Change or delete a template the team owns |

## Building a graph

Nodes may carry a `key` of your choosing so edges can reference them before they have ids; ids are assigned on save. `trigger_config` mirrors the trigger node (`{ provider, form_id }` for form triggers) and is derived from it when omitted.

```bash theme={null}
curl -X POST https://app.useorbitcrm.ai/api/v1/workflows \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Demo requests → pipeline",
    "nodes": [
      { "key": "trigger", "type": "trigger", "provider": "form", "config": { "form_id": "FORM_ID" } },
      { "key": "deal", "type": "automation", "provider": "create_deal", "config": { "pipeline_id": "PIPELINE_ID", "stage_id": "STAGE_ID", "name_template": "{{company}} demo" } },
      { "key": "task", "type": "automation", "provider": "create_task", "config": { "title_template": "Call {{first_name}}", "due_in_days": 1, "assignee_mode": "contact_owner" } }
    ],
    "edges": [
      { "source_node_id": "trigger", "target_node_id": "deal" },
      { "source_node_id": "deal", "target_node_id": "task" }
    ]
  }'
```

### Installed apps as steps

Apps installed in the workspace that offer [workflow actions](https://docs.useorbitcrm.ai/apps/workflow-actions.md) are listed by `GET /api/v1/workflows/schema` under `app_actions`, each with its `app` (`id`, `name`) and `actions` (`id`, `key`, `name`, `inputs`). Use one as a destination node:

```json theme={null}
{ "key": "cometly", "type": "destination", "provider": "app_action", "config": { "app_id": "APP_ID", "action_id": "ACTION_ID", "action_key": "send_event", "inputs": { "event_name": "purchase", "amount": "{{deal.amount}}" } } }
```

`inputs` maps each input key the action declares to a template; required inputs must be mapped before the workflow can be activated. The step delivers to the app signed with the app's secret and fails, without retry, if the workspace has since uninstalled the app.

### Record-change triggers

Besides forms and meetings, a workflow can start when records change. Each trigger's config is listed by `GET /api/v1/workflows/schema`:

| Provider | Starts when | Config |
| - | - | - |
| `record_created` | A contact, company or deal is created | `object`; deals may add `pipeline_id` |
| `deal_won` / `deal_lost` | A deal moves into a won / lost stage (or is created in one) | `pipeline_id` (optional) |
| `field_changed` | A field of a contact, company, deal or task changes | `object`, `field` (a column such as `owner_user_id`, `stage_id`, `amount`, or `cf:<custom field>`), optional `from` / `to` conditions `{ operator, value }` |
| `task_completed` | A task is marked done | `task_types` (optional) |
| `call_completed` | A dialer call gets its outcome | `outcomes` (optional) |
| `tag_added` | A tag is added to a contact, company or deal | `object`, `tag_ids` (optional) |
| `schedule` | Daily, weekly or monthly, for each record in a saved view | `frequency`, `time`, `time_zone`, `days_of_week` / `day_of_month`, `object` (`contact` or `company`), `view_id` |
| `list_entered` | A contact or company is added to a static list, or starts matching a saved view (checked about every 15 minutes, up to 10,000 records of the view) | `object` (`contact` or `company`), `list_type` (`list`, the default, or `view`), `list_id` or `view_id`, `include_existing` (views: also run for the records already in the view when the workflow starts) |
| `job_changed` | A re-check by the workspace's enrichment provider finds a contact at another employer, or with another title (only while re-checks are on, under Settings › Integrations). Runs add `job_change` (`company` or `title`), `previous_company`, `new_company`, `previous_job_title`, `new_job_title` and `enrichment_provider` | `change` (optional: `company` or `title`; either when left out) |
| `record_enriched` | Enrichment fills fields on a contact or company (a new record, a whole-CRM run, Enrich now, a workflow step or the API). Values people typed are never replaced, so they never start a run. Runs add `enriched_fields` and `enrichment_provider` | `object` (`contact` or `company`), `fields` (optional: only when one of these was filled, e.g. `industry`, `job_title`, `custom_fields.linkedin_url`) |

Every one takes `enrollment` (`every_time`, the default, or `once` per record) and `change_sources` (`people`, `api`, `workflows`, `imports`; imports are off unless listed). Runs receive the record's fields (for example `deal_id`, `deal_name`, `stage_name` and the primary contact's `email` for deals) plus `record_type`, `record_id` and `change_source`. A workflow acts for its creator: a change to a record outside the creator's record access (or in a pipeline they can't see) starts nothing, and a schedule evaluates the view as the creator sees it, up to 10,000 records per run (with `once`, later runs continue with the records that have not run yet).

Loop protection, for these triggers and for **Deal Stage Changed**: a workflow never starts on a change its own steps made, a chain of workflows starting each other stops after three, and one record starts a workflow at most ten times an hour. Changes refused this way show in the workflow's runs as cancelled, with the reason.

Record steps write through the same rules as the API: `update_deal` (fields, `move_stage` with `stage_id`, `lost_reason_template`; a stage move follows the pipeline's rules), `create_company`, `update_company`, `link_records` (`contact_company`, `deal_contact`, `deal_company`), `rotate_owner` (round robin over `user_ids`, remembered per step), `complete_task` and `add_line_item` (Add Product to Deal: `product_id`, with optional `quantity`, `unit_price`, `discount` and `discount_type` as numbers or `{{fields}}`, and `set_deal_amount` to make the deal amount follow its line items; see [Products, line items and quotes](https://docs.useorbitcrm.ai/developers/api/products-quotes.md)). A step that can't act on the run's record (no deal in the run, a pipeline rule refusing the move, a value that doesn't fit a field) is skipped with the reason, which the run's steps show; skips never count toward pausing the workflow. Database errors fail the step and are retried.

## Publishing, pausing, resuming

A workflow is created as a **draft**. `PATCH` with `{ "status": "active" }` validates the graph (missing config, unconnected nodes, plan limits), publishes it and starts running it on its trigger; the response carries the validation problems when it is not ready. Edits to an active workflow are saved as a draft (`has_unpublished_changes: true`) until `status: "active"` is sent again. `{ "status": "paused" }` stops new runs; sending `"active"` again resumes it, and `process_queue` (`all`, `skip`, `none`) says what happens to submissions queued while it was paused.

Pass `expected_updated_at` (the `updated_at` you last read) with a `PATCH` to have the save refused with `409 conflict` when someone changed the workflow meanwhile.

## Running

`POST /api/v1/workflows/:id/run` with `contact_ids` starts one run per contact (the trigger must be Contact Added or Form Submission); the same `contact_ids` with the same `idempotency_key` never run twice. With `trigger_data` the workflow runs once with that data; the response carries a `trigger_key`, and `GET /api/v1/workflows/:id/runs?trigger_key=...` finds that run. Both respond `202`: runs execute in the background, so poll the run for its status and per-node steps. Runs can send email, call integrations and change CRM data.

> [card] [CRM API](https://docs.useorbitcrm.ai/developers/api/crm.md)
>
> The objects workflows create and update

> [card] [MCP tools](https://docs.useorbitcrm.ai/mcp/tools.md)
>
> The same operations as MCP tools


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.