# Get workflows

> GET /v1/workflows · Workflows API

Source: https://developer.kustomer.com/api/workflows/get-workflows

```http
GET https://{orgname}.api.kustomerapp.com/v1/workflows
Authorization: Bearer <API key>
```

## Authorization

Requires any one of these roles: `org.permission.workflow.read`; or legacy `org.admin.workflow.read`. The API key or user must hold at least one of them. Legacy roles predate permission sets and still work.

Retrieves all workflows. Supports pagination and optional filters by `enabled`, `eventName`, and `callable`.

## About Workflows

A workflow is an automation that runs inside Kustomer whenever an event happens. A trigger starts each run, and steps call actions, with conditions choosing the path from one step to the next.

Admins build workflows without code in **Settings › Platform › Workflows**, and apps can install their own. Use them to tag and route conversations, keep records in sync, call your own services when something changes, or handle data from inbound webhooks. Underneath the builder, each workflow is a JSON document, and this API reads and writes that document, so you can back workflows up, review changes in code, or copy them between a sandbox and production.

A workflow is created disabled unless you send `enabled: true`. Once it's enabled, every matching event starts a run; creating, enabling or changing a workflow takes about a minute to take effect. Runs execute in parallel, finish in any order, and stop after five minutes. There is no delete operation in this API: set `enabled` to `false` to stop a workflow.

#### The document

| Part | What it holds |
| --- | --- |
| `trigger` | What starts a run: an `eventName` such as `kustomer.conversation.update`, or `callable: true` with an input `schema` for workflows that are called rather than triggered by an event. Its output is step `1`. |
| `steps` | The actions to run, each with an `id`, an `action` and `params`. A step's output is stored under its ID for later steps to reference as `/#steps.<id>`. |
| `transitions` | On the trigger and each step: where to go next, each guarded by a condition. The first match wins; if none match, the run ends normally. |
| `errorCases` | On a step: whether to continue or stop when it fails. |

`name` is required and unique in the org: letters, numbers, `-`, `_` and `.`, up to 128 characters. `replaces` names another workflow, usually one an app installed, that this one overrides; when both match an event, only this one runs. See [Workflow JSON](https://developer.kustomer.com/docs/workflows/workflow-json) for every field and limit.

#### Read-only attributes

Responses also include `lastError` (the most recent failure, or `{}`), `editable` (whether the builder can edit it), `containsVariables`, and timestamps. Strip these before sending a document back, because unknown keys are rejected with `400`.

#### Which endpoint to use

| To | Call |
| --- | --- |
| List workflows, filtered by `enabled`, trigger `eventName` or `callable` | [`GET /v1/workflows`](https://developer.kustomer.com/api/workflows/get-workflows) |
| Read one workflow | [`GET /v1/workflows/{id}`](https://developer.kustomer.com/api/workflows/get-a-workflow-based-on-the-id) |
| Create a workflow | [`POST /v1/workflows`](https://developer.kustomer.com/api/workflows/create-a-workflow) |
| Change, enable or disable a workflow | [`PUT /v1/workflows/{id}`](https://developer.kustomer.com/api/workflows/update-a-workflow-based-on-the-id) |
| Store secrets the workflow uses | [`POST /v1/workflows/{id}/variables`](https://developer.kustomer.com/api/workflows/create-a-workflow-variable-for-a-workflow) |
| See why runs failed | [`GET /v1/workflows/{id}/processes`](https://developer.kustomer.com/api/workflows/get-processes-errors-by-workflow) |

`PUT` updates only the root keys you send, and each one replaces the stored value wholesale, so send `trigger` and `steps` together and keep step IDs stable because references depend on them. To switch a workflow on or off, send only `enabled`. Create new workflows disabled, add their variables, then enable them. Workflows reference org-specific IDs such as tags, teams, users and queues, so replace those before copying a workflow to another org. A workflow that updates the record that triggered it fires again, so guard update triggers with a condition on `changes`.

Related: [Workflows overview](https://developer.kustomer.com/docs/workflows), [Workflow JSON](https://developer.kustomer.com/docs/workflows/workflow-json), [Workflow variables](https://developer.kustomer.com/api/workflows#workflow-variables), [Workflow processes (errors)](https://developer.kustomer.com/api/workflows#workflow-processes-errors)

### Recipes

#### 1. Back up every workflow as JSON files

Save each workflow's document to a file you can commit, so you can review changes in code and restore a workflow that someone broke in the builder.

**Step 1.** [`GET /v1/workflows`](https://developer.kustomer.com/api/workflows/get-workflows.md). Page through every workflow. Each record already holds the whole document, so you don't need to fetch them one by one. Keep only the keys `POST /v1/workflows` accepts: `name`, `description`, `meta`, `trigger` and `steps`. Drop the `_id` Kustomer adds to each step and read-only attributes such as `lastError`, `editable` and `containsVariables`, because unknown keys are rejected with `400` when you restore.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/workflows?page=1&pageSize=100" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
mkdir -p workflows
kusty api GET /v1/workflows --all | while read -r line; do
  name=$(jq -r '.data.attributes.name' <<<"$line")
  jq '.data.attributes
    | {name, description, meta, trigger, steps: [(.steps // [])[] | del(._id)]}
    | with_entries(select(.value != null))' <<<"$line" > "workflows/$name.json"
done
```

**Step 2.** [`POST /v1/workflows`](https://developer.kustomer.com/api/workflows/create-a-workflow.md). To restore a workflow you deleted or want to rebuild, post its file. It's created disabled unless the file says otherwise. The `name` must not be in use, so restoring over a workflow that still exists means sending its `trigger` and `steps` with `PUT` instead.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/workflows" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data @workflows/$NAME.json
```

```bash title="kusty"
kusty api POST /v1/workflows --data @workflows/$NAME.json --fields id,attributes.name
```

> Variable values can't be read back, so a backup only holds variable IDs; keep the values in your own secret store. Workflows with `editable: false`, such as ones an app installed, can't be changed through `PUT`, so you may want to skip them with `select(.data.attributes.editable)`.

#### 2. Copy a workflow from a sandbox to production

Promote a workflow you built and tested in a sandbox org, without rebuilding it by hand in the builder.

**Step 1.** [`GET /v1/workflows/{id}`](https://developer.kustomer.com/api/workflows/get-a-workflow-based-on-the-id.md). Read the workflow with a sandbox API key and keep only the keys you can send back. Then find the org-specific IDs it uses, such as tags, teams, users, queues and workflow variables: they're 24-character hex strings, and each one must be replaced with the production org's equivalent.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/workflows/$WORKFLOW_ID" \
  --header "Authorization: Bearer $SANDBOX_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/workflows/$WORKFLOW_ID --profile sandbox \
  | jq '.data.attributes
      | {name, description, meta, trigger, steps: [.steps[] | del(._id)]}
      | with_entries(select(.value != null))' > workflow.json
grep -oE '[a-f0-9]{24}' workflow.json | sort -u
```

**Step 2.** [`POST /v1/workflows`](https://developer.kustomer.com/api/workflows/create-a-workflow.md). Create it in production. The file has no `enabled`, so the workflow starts disabled. Kustomer stores `name` lowercased and trimmed, and it must be unique in the org: if a workflow with that name already exists, you get `409 Conflict`. A transition that points back to a step already on its path is rejected with `400`, the same as in the sandbox. Keep the new `id`.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/workflows" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data @workflow.json
```

```bash title="kusty"
kusty api POST /v1/workflows --data @workflow.json --fields id
```

**Step 3.** [`POST /v1/workflows/{id}/variables`](https://developer.kustomer.com/api/workflows/create-a-workflow-variable-for-a-workflow.md). Variables don't come with the copy. Create each one on the new workflow, then replace the sandbox variable IDs in its steps with the new ones and send `trigger` and `steps` with `PUT`. The [workflow variables](https://developer.kustomer.com/api/workflows#workflow-variables) recipes show how.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/workflows/$NEW_WORKFLOW_ID/variables" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"name\": \"warehouseToken\", \"value\": \"$WAREHOUSE_TOKEN\", \"type\": \"string\"}"
```

```bash title="kusty"
kusty api POST /v1/workflows/$NEW_WORKFLOW_ID/variables \
  --field name=warehouseToken --field value="$WAREHOUSE_TOKEN" --field type=string --fields id
```

**Step 4.** [`PUT /v1/workflows/{id}`](https://developer.kustomer.com/api/workflows/update-a-workflow-based-on-the-id.md). Turn it on by sending only `enabled`. It takes about a minute for new runs to pick it up.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/workflows/$NEW_WORKFLOW_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"enabled": true}'
```

```bash title="kusty"
kusty api PUT /v1/workflows/$NEW_WORKFLOW_ID --field enabled:=true
```

#### 3. Change one step without touching the rest

Edit a single step's params from a script, for example to point every routing workflow at a new queue, while keeping the trigger and the other steps as they are.

**Step 1.** [`GET /v1/workflows/{id}`](https://developer.kustomer.com/api/workflows/get-a-workflow-based-on-the-id.md). Read the current document. Steps are identified by their `id`, which is what `/#steps.<id>` references use, so find the step by `id` rather than by its position in the array.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/workflows/$WORKFLOW_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/workflows/$WORKFLOW_ID \
  | jq --arg step "$STEP_ID" --arg key "$PARAM" --arg value "$VALUE" '.data.attributes
      | {trigger, steps: [.steps[] | del(._id)
          | if .id == $step then .params[$key] = $value else . end]}' > update.json
```

**Step 2.** [`PUT /v1/workflows/{id}`](https://developer.kustomer.com/api/workflows/update-a-workflow-based-on-the-id.md). Send `trigger` and `steps` together. `PUT` only updates the root keys you send, but each one replaces the stored value, so the `steps` array must contain every step, not just the one you changed. Keep step IDs stable, because other steps reference them. If the workflow has `editable: false`, the request fails with `409`.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/workflows/$WORKFLOW_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data @update.json
```

```bash title="kusty"
kusty api PUT /v1/workflows/$WORKFLOW_ID --data @update.json --fields attributes.updatedAt
```

> Each save creates a new version, which admins can open from the builder's version picker. Changes take about a minute to reach new runs.

#### 4. Pause every workflow on one trigger during a migration

Turn off the workflows that react to an event, such as conversation updates, while you bulk-change records, then turn the same ones back on.

**Step 1.** [`GET /v1/workflows`](https://developer.kustomer.com/api/workflows/get-workflows.md). List the enabled workflows whose trigger `eventName` matches, and save their IDs so you re-enable only the ones you paused.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/workflows?eventName=kustomer.conversation.update&enabled=true&pageSize=100" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/workflows --query eventName=kustomer.conversation.update --query enabled=true --all \
  | jq -r '.data.id' > paused.txt
```

**Step 2.** [`PUT /v1/workflows/{id}`](https://developer.kustomer.com/api/workflows/update-a-workflow-based-on-the-id.md). Send only `{ "enabled": false }` to each one. Nothing else changes, and this works even for workflows with `editable: false`. Allow about a minute before you start the migration, because running workflows pick up changes after a short delay.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/workflows/$WORKFLOW_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"enabled": false}'
```

```bash title="kusty"
while read -r id; do
  kusty api PUT /v1/workflows/$id --field enabled:=false --fields attributes.enabled
done < paused.txt
```

**Step 3.** When the migration is done, send `{ "enabled": true }` to the same IDs. Events that happened while a workflow was off don't start runs afterwards.

```bash title="kusty"
while read -r id; do kusty api PUT /v1/workflows/$id --field enabled:=true; done < paused.txt
```

### Example object

A real record from [`GET /v1/workflows/{id}`](https://developer.kustomer.com/api/workflows/get-a-workflow-based-on-the-id.md):

```json
{
  "type": "workflow",
  "id": "57504f6460da071c000dfaee",
  "attributes": {
    "name": "assign-user-on-message-send",
    "description": "Assign user to a conversation when the user sends a message and the conversation was previously unassigned.",
    "lastError": {},
    "trigger": {
      "transitions": [
        {
          "condition": {
            "values": [
              {
                "values": [
                  "/#steps.1.createdBy"
                ],
                "op": "exists"
              },
              {
                "values": [
                  "/#steps.1.status",
                  "sent"
                ],
                "op": "eq"
              }
            ],
            "op": "and"
          },
          "target": "2"
        }
      ],
      "eventName": "kustomer.message.create",
      "id": "1"
    },
    "steps": [
      {
        "id": "2",
        "action": "kustomer.conversation.find-by-id",
        "params": {
          "id": "/#steps.1.conversation"
        },
        "_id": "57504f6460da071c000dfaf0",
        "errorCases": [],
        "transitions": [
          {
            "target": "3",
            "condition": {
              "op": "lt",
              "values": [
                "/#steps.2.assignedUsers.length",
                1
              ]
            }
          }
        ]
      },
      {
        "id": "3",
        "action": "kustomer.conversation.update",
        "params": {
          "id": "/#steps.1.conversation",
          "assignedUsers": [
            "/#steps.1.createdBy"
          ]
        },
        "_id": "57504f6460da071c000dfaef",
        "errorCases": [],
        "transitions": []
      }
    ],
    "enabled": false,
    "editable": true,
    "updatedAt": "2016-06-02T15:23:16.431Z",
    "createdAt": "2016-06-02T15:23:16.431Z",
    "containsVariables": false
  },
  "relationships": {
    "org": {
      "links": {
        "self": "/v1/orgs/57504f64d76808100089eb44"
      },
      "data": {
        "type": "org",
        "id": "57504f64d76808100089eb44"
      }
    },
    "createdBy": null,
    "modifiedBy": null
  },
  "links": {
    "self": "/v1/workflows/57504f6460da071c000dfaee"
  }
}
```

Fields:

- `attributes.name` (string): Unique in the org. Kustomer stores it lowercased and trimmed, so `Tag-VIP` and `tag-vip` collide, and a duplicate fails with `409`. Letters, numbers, `-`, `_` and `.`, up to 128 characters.
- `attributes.description` (string): Free text for people, up to 4,096 characters.
- `attributes.lastError` (object): Read-only. The most recent failed run, or `{}` if there is none. The full history is in [workflow processes](https://developer.kustomer.com/api/workflows#workflow-processes-errors).
- `attributes.trigger` (object): What starts a run. Its output is step `1`, which later steps read as `/#steps.1.<field>`.
- `attributes.trigger.transitions` (array): Where the run goes after the trigger. Transitions are checked in order and the first match wins; if none match, the run ends without doing anything.
- `attributes.trigger.transitions[].condition` (object): A rule with `op` and `values`. Use `{ "op": "true", "values": [true] }` to always continue.
- `attributes.trigger.transitions[].condition.values` (array): For a comparison, `[left, right]`, usually a reference such as `/#steps.1.status` and a literal. For `and` and `or`, the nested rules.
- `attributes.trigger.transitions[].condition.op` (string): `exists`, `dne`, `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `gt-date`, `lt-date`, `contains` or `not-contains` compare values; `and` and `or` combine nested rules.
- `attributes.trigger.transitions[].target` (string): The `id` of the step to run next. It can't point back to a step already on the path: loops are rejected with `400` when you save.
- `attributes.trigger.eventName` (string): The event that starts a run, such as `kustomer.message.create`, or `kustomer.hooks.web.<hookId>.receive` for an inbound hook. For a callable workflow, Kustomer sets it to `kustomer.workflow.<workflowId>.call`.
- `attributes.trigger.id` (string): Keep it `"1"`: later steps read the trigger's output as `/#steps.1.<field>`.
- `attributes.steps` (array): The actions to run. `PUT` replaces the whole array, so send every step, and send `trigger` with it.
- `attributes.steps[].id` (string): Unique in the workflow. The step's output is stored as `/#steps.<id>`, so renaming a step breaks every reference to it.
- `attributes.steps[].action` (string): The action to run, such as `kustomer.conversation.update`. Leave it out for a step that only branches.
- `attributes.steps[].params` (object): The action's inputs. A value that is exactly a reference, such as `/#steps.1.conversation`, keeps its type; any other string is rendered as a `{{ }}` template.
- `attributes.steps[]._id` (string): Added by Kustomer. Remove it before sending steps back, because unknown step keys are rejected with `400`.
- `attributes.steps[].errorCases` (array): What to do when this step fails, checked in order: `continue` moves on to its transitions and `stop-processing` ends the run quietly. With none, a failure ends the run and is recorded as a [workflow process](https://developer.kustomer.com/api/workflows#workflow-processes-errors).
- `attributes.steps[].transitions` (array): Where to go after this step, with the same rules as the trigger's transitions. An empty array makes it a final step.
- `attributes.steps[].transitions[].condition` (object): Conditions here can also read this step's output, such as `/#steps.2.assignedUsers.length`.
- `attributes.enabled` (boolean): Whether events start runs. New workflows are `false` unless you send `true`. To switch it, send only `{ "enabled": … }` on `PUT`.
- `attributes.editable` (boolean): Read-only. When `false`, for example on a workflow an app installed, `PUT` can only change `enabled` or `logging` on its own; anything else fails with `409`.
- `attributes.containsVariables` (boolean): Read-only. `true` when the trigger or steps reference a [workflow variable](https://developer.kustomer.com/api/workflows#workflow-variables) with `/#variables.` or `{{{variables.`.

### Relationships

- Points to **Workflows** via `relationships.replaces`: Another workflow, usually one an app installed, that this workflow overrides. When both match an event, only the replacing workflow runs. Set it by sending `replaces` with the other workflow's ID.
- Referenced by **Workflow processes (errors)** via `relationships.workflow`: The workflow whose run failed. Each workflow keeps about a week of failed runs, newest first, and copies the latest one to its `lastError`.
- Referenced by **Workflow variables** via `relationships.workflow`: Each workflow variable belongs to exactly one workflow, and only that workflow's steps can read it. A workflow can have many variables.

### Permissions

Fewest roles for a read-only integration: `org.permission.workflow.read`. For every endpoint: `org.permission.workflow.read`, `org.permission.workflow.create`, `org.permission.workflow.update`.

### Events

Workflow actions:

- `kustomer.workflow.respond` (Workflow: Send Response): required inputs `outputs`
- `kustomer.app.scheduler.schedule-workflow` (Schedule a Workflow (Scheduler app)): required inputs `workflowId`, `runAt`, `inputs`

## Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | number | no | Number of pages to return for results. Default value: 1. |
| `pageSize` | number | no | Number of results to return per page. Default value: 100. |
| `enabled` | boolean | no | Filter by enabled status. Pass `true` to return only enabled workflows, `false` for disabled. |
| `eventName` | string | no | Filter by the workflow trigger event name (e.g. `kustomer.conversation.create`). |
| `callable` | boolean | no | Filter by callable flag. Pass `true` to return only callable workflows. |

## Responses

### 200

OK

- `meta` (object, required)
  - `pageSize` (integer, required)
  - `page` (integer, required)
- `data` (array<object>, required)
  - `type` (string, required)
  - `id` (string, required)
  - `attributes` (object, required)
    - `name` (string, required)
    - `description` (string, required)
    - `lastError` (object, required)
    - `trigger` (object, required)
    - `steps` (array<object>, required)
    - `enabled` (boolean, required)
    - `editable` (boolean, required)
    - `updatedAt` (string, required)
    - `createdAt` (string, required)
    - `containsVariables` (boolean, required)
  - `relationships` (object, required)
    - `org` (object, required)
    - `createdBy` (string, required, nullable)
    - `modifiedBy` (string, required, nullable)
  - `links` (object, required)
    - `self` (string, required)
- `links` (object, required)
  - `self` (string, required)
  - `first` (string, required)
  - `prev` (string, required, nullable)
  - `next` (string, required, nullable)

```json
{
  "meta": {
    "pageSize": 100,
    "page": 1
  },
  "data": [
    {
      "type": "workflow",
      "id": "57504f6460da071c000dfaee",
      "attributes": {
        "name": "assign-user-on-message-send",
        "description": "Assign user to a conversation when the user sends a message and the conversation was previously unassigned.",
        "lastError": {},
        "trigger": {
          "transitions": [
            {
              "condition": {
                "values": [
                  {
                    "values": [
                      "/#steps.1.createdBy"
                    ],
                    "op": "exists"
                  },
                  {
                    "values": [
                      "/#steps.1.status",
                      "sent"
                    ],
                    "op": "eq"
                  }
                ],
                "op": "and"
              },
              "target": "2"
            }
          ],
          "eventName": "kustomer.message.create",
          "id": "1"
        },
        "steps": [
          {
            "id": "2",
            "action": "kustomer.conversation.find-by-id",
            "params": {
              "id": "/#steps.1.conversation"
            },
            "_id": "57504f6460da071c000dfaf0",
            "errorCases": [],
            "transitions": [
              {
                "target": "3",
                "condition": {
                  "op": "lt",
                  "values": [
                    "/#steps.2.assignedUsers.length",
                    1
                  ]
                }
              }
            ]
          },
          {
            "id": "3",
            "action": "kustomer.conversation.update",
            "params": {
              "id": "/#steps.1.conversation",
              "assignedUsers": [
                "/#steps.1.createdBy"
              ]
            },
            "_id": "57504f6460da071c000dfaef",
            "errorCases": [],
            "transitions": []
          }
        ],
        "enabled": false,
        "editable": true,
        "updatedAt": "2016-06-02T15:23:16.431Z",
        "createdAt": "2016-06-02T15:23:16.431Z",
        "containsVariables": false
      },
      "relationships": {
        "org": {
          "links": {
            "self": "/v1/orgs/57504f64d76808100089eb44"
          },
          "data": {
            "type": "org",
            "id": "57504f64d76808100089eb44"
          }
        },
        "createdBy": null,
        "modifiedBy": null
      },
      "links": {
        "self": "/v1/workflows/57504f6460da071c000dfaee"
      }
    }
  ],
  "links": {
    "self": "/v1/workflows?page=1&pageSize=100",
    "first": "/v1/workflows?page=1&pageSize=100",
    "prev": null,
    "next": null
  }
}
```

### 401

Unauthorized. Missing or invalid bearer token.

### 403

Forbidden. The authenticated user does not have permission to view workflows.

## Example request

```bash
curl --request GET \
  --url 'https://api.kustomerapp.com/v1/workflows' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```
