# Workflow JSON

> The complete workflow document schema, field by field, with limits and patterns. Covers root keys and flat meta, triggers (event and callable), steps, transitions and the save-time loop check, error cases, the replaces field for overriding app workflows, and creating and partially updating workflows through the API.

Source: https://developer.kustomer.com/docs/workflows/workflow-json

Everything you build in the workflow builder is stored as a JSON document. Reading and writing that JSON lets you review workflow changes in pull requests, copy workflows between a sandbox and production, generate workflows with code or an AI agent, and ship them inside [apps](https://developer.kustomer.com/docs/apps/workflows-actions-triggers).

This page is the field-by-field reference. For a step-by-step tutorial, see [Write workflow JSON](https://developer.kustomer.com/docs/workflows/write-workflow-json).

```json title="A complete workflow"
{
  "name": "vip-first-reply",
  "description": "Raise priority and tag new conversations from gold-tier customers",
  "enabled": true,
  "meta": { "displayName": "VIP first reply" },
  "trigger": {
    "id": "1",
    "eventName": "kustomer.conversation.create",
    "transitions": [
      { "target": "customer", "condition": { "op": "true", "values": [true] } }
    ]
  },
  "steps": [
    {
      "id": "customer",
      "action": "kustomer.customer.find",
      "params": { "identifier": "/#steps.1.customer" },
      "meta": { "displayName": "Look up the customer" },
      "transitions": [
        {
          "target": "raise",
          "condition": { "op": "eq", "values": ["/#steps.customer.custom.tierStr", "gold"] },
          "meta": { "name": "Gold tier" }
        }
      ],
      "errorCases": []
    },
    {
      "id": "raise",
      "action": "kustomer.conversation.update",
      "params": { "id": "/#steps.1.id", "priority": 4 },
      "meta": { "displayName": "Raise priority" },
      "transitions": [
        { "target": "tag", "condition": { "op": "true", "values": [true] } }
      ]
    },
    {
      "id": "tag",
      "action": "kustomer.conversation.append-tags",
      "params": { "id": "/#steps.1.id", "tags": ["64f1c0ffee5ca1ab1e000002"] },
      "meta": { "displayName": "Tag VIP" },
      "transitions": []
    }
  ]
}
```

## Root

No other root keys are allowed. Unknown keys are rejected.

Root `meta` must be flat:

| Rule    | Limit                                                                              |
| ------- | ---------------------------------------------------------------------------------- |
| Keys    | Match `^[a-zA-Z][a-zA-Z0-9-_]{1,64}$`: a letter, then letters, numbers, `-` or `_` |
| Values  | Strings of up to 1,024 characters, numbers, booleans or `null`                     |
| Size    | Up to 100 keys                                                                     |
| Nesting | Not allowed. An object or array value is rejected                                  |

## Trigger

### Event trigger

```json
{ "id": "1", "eventName": "kustomer.conversation.update", "transitions": [ … ] }
```

### Callable trigger

```json
{ "id": "1", "callable": true, "schema": { "type": "object", "properties": { … } }, "transitions": [ … ] }
```

All four keys are required; a callable trigger without one is rejected when you save. Declare the workflow's outputs in the root [`response`](#root) key.

How callers run callable workflows is covered in [Callable and scheduled workflows](https://developer.kustomer.com/docs/workflows/triggers-and-actions#callable-and-scheduled-workflows).

## Steps

Only `id` and `transitions` are required. The numbers on the builder's canvas (1, 2, 3…) show each step's position, not its ID. Only the trigger's ID is always `1`; use **Copy Step ID** in the builder to get any other step's ID.

## Transitions

```json
{ "target": "raise", "condition": { "op": "eq", "values": ["/#steps.customer.custom.tierStr", "gold"] }, "meta": { "name": "Gold tier" } }
```

The first transition whose condition passes wins. If none pass, the run ends successfully. In the builder, a step's branches sit side by side, and their left-to-right order is the order of `transitions`.

Kustomer checks the transitions when you save:

- **No loops.** A transition can't target a step that's already on the path leading to it. Kustomer rejects the save with `400`: `Step with ID <id> transitions to a previous step with ID <workflowId>:<target>`. The builder shows the same problem on the canvas as "Cycle detected". To repeat work later, schedule a [callable workflow](https://developer.kustomer.com/docs/workflows/triggers-and-actions#schedule-a-workflow).
- **Unique transitions.** Two identical transitions on one step are rejected.
- **At most 10** transitions per step.

To keep a workflow readable in the builder, give each branch its own final step rather than pointing two branches at the same step. Kustomer's own templates do this.

## Error cases

```json
{
  "condition": { "op": "and", "values": [{ "op": "eq", "values": ["/#error.status", 400] }] },
  "errorAction": { "type": "continue" }
}
```

Cases are checked in order, and the first match applies. A step can have up to 10.

## Read-only attributes

The API returns these alongside your fields. Don't send them back.

| Attribute                | Meaning                                                                                                                                                                                                                                      |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `lastError`              | The most recent failure, or `{}`                                                                                                                                                                                                             |
| `editable`               | Whether the workflow can be edited in the builder                                                                                                                                                                                            |
| `containsVariables`      | Whether the trigger or steps reference a [variable](https://developer.kustomer.com/docs/workflows/conditions-and-expressions#variables-and-secrets) (`/#variables.` or `{{variables.`). Variables are loaded for a run only when it's `true` |
| `createdAt`, `updatedAt` | Timestamps                                                                                                                                                                                                                                   |

## Create and update with the API

#### Create

```bash
curl -X POST "https://acme.api.kustomerapp.com/v1/workflows" \
  -H "Authorization: Bearer $KUSTOMER_API_KEY" -H "Content-Type: application/json" \
  --data @vip-first-reply.json
```

The response is the workflow with its `id`. Create it with `"enabled": false`, add any [variables](https://developer.kustomer.com/api/workflows/create-a-workflow-variable-for-a-workflow) it needs, then reference them by the IDs the variables endpoint returns, and enable it.

#### Update

`PUT /v1/workflows/{id}` updates only the root keys you send. Each key you send replaces the stored value wholesale (the whole `steps` array, for example), and keys you leave out are unchanged. Always send `trigger` and `steps` together, because transitions and steps refer to each other.

```bash
kusty api GET /v1/workflows/$ID --output json | jq '.data.attributes | {trigger, steps} | del(.steps[]._id)' > wf.json
# edit wf.json
kusty api PUT /v1/workflows/$ID --data @wf.json --dry-run
kusty api PUT /v1/workflows/$ID --data @wf.json
```

For a callable workflow, include `response` too if its outputs change. To switch a workflow on or off, send only `enabled`, as the builder's switch does:

```bash
kusty api PUT /v1/workflows/$ID --data '{ "enabled": true }'
```

Each save creates a new version, and Kustomer keeps the 30 most recent. Workflows installed by an app can't be edited (`409`), but you can still turn them on or off and change `logging`.

#### Wait, then test

Allow about a minute for the change to apply, then cause the trigger event and check the [logs](https://developer.kustomer.com/docs/workflows/debugging).

> **Copying between orgs:** Workflows reference org-specific IDs: tags, teams, users, queues, Klass names, business schedules, other workflows and workflow variables. Before you create a copy in another org, replace every ID with the target org's equivalent. Search the JSON for 24-character hex strings to find them. Variables belong to one workflow, so create them on the copy first, then replace each `/#variables.<variableId>` and `{{{variables.<variableId>}}}` with the new IDs. The same applies when you copy JSON between two workflows in one org.

### Edit workflow JSON, Export JSON and Duplicate

The builder has three ways to get at the JSON. They don't all include every root key:

| Feature                                       | What it covers                                                                                                                                                                                  |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Edit workflow JSON** (builder options menu) | Shows `description`, `name`, `steps` and `trigger` from the **last saved version**, so save canvas edits first. **Update** saves the JSON straight to the workflow, and the server validates it |
| **Export JSON** (workflows list)              | Downloads `description`, `name`, `meta`, `steps` and `trigger`, with `name` changed to `export-<name>--<uuid>`                                                                                  |
| **Duplicate** (workflows list)                | Creates a copy with the same keys, named `copy-<name>`                                                                                                                                          |

Export JSON and Duplicate leave out `response` (a callable workflow's outputs), `enabled` and `logging`, so copies start disabled and callable copies need their **Output Parameters** added again. Both also remove `trigger.eventName` from callable workflows; Kustomer sets a new one when the copy is saved. To keep a complete copy, use `GET /v1/workflows/{id}` instead.

## Override an app workflow with replaces

To change a workflow an app installed without editing the app's version, copy it, edit the copy, and set the copy's `replaces` to the original's ID. When both match an event, only the copy runs; the original is skipped.

```json
{
  "name": "shopify-order-sync-custom",
  "replaces": "6513a0d1a4b1e1001a6b3e77",
  "enabled": true,
  "trigger": { … },
  "steps": [ … ]
}
```

Disable or delete the copy to restore the original behavior. Keep `appVersion` on copied steps that use the app's actions.

## Validate before you save

The API validates documents against the schema and rejects invalid ones with a `400` validation error. Common causes:

| Error mentions                              | Fix                                                                                                                                              |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name` pattern                              | Use only letters, numbers, `-`, `_` and `.`; no spaces                                                                                           |
| `409` on create or update                   | Another workflow in the org already has this `name` (names are compared lowercased), or the workflow was installed by an app and can't be edited |
| `additionalProperties`                      | Remove keys the schema doesn't allow, such as read-only attributes copied from a `GET`                                                           |
| `meta`                                      | Root `meta` must be flat: no nested objects, keys that start with a letter, string values of up to 1,024 characters, and at most 100 keys        |
| `target` pattern                            | Step IDs used as targets can't contain dots                                                                                                      |
| `maxItems`                                  | More than 10 transitions or error cases on one step, or more than 5 conditions in an error case. Split the logic                                 |
| `uniqueItems`                               | Two identical transitions on one step. Remove the duplicate                                                                                      |
| `transitions to a previous step`            | A transition loops back to a step already on its path. Restructure, or schedule a callable workflow to repeat work                               |
| `Invalid app settings configuration passed` | A `{{{ setting … }}}` reference doesn't match the app's settings                                                                                 |
| `appVersion` pattern                        | Use `<app>-<semver range>`, such as `kustomer-^1.8.16`                                                                                           |

In the builder, the on/off switch also refuses to enable a workflow while any condition lacks a target: "All conditions in active workflows must point to a target". Saving an enabled workflow runs the same check.

> **Generating workflows:** Agents generating workflow JSON should: use only actions and triggers from [this catalog](https://developer.kustomer.com/docs/workflows/triggers-and-actions) or the org's installed apps; reference the trigger as `/#steps.1`; refer to variables by ID (`/#variables.<variableId>`); guard update triggers with a `changes` condition; never point a transition back at an earlier step; keep root `meta` flat; end every branch deliberately; and create disabled first. When updating, send `trigger` and `steps` together. The [create workflow reference](https://developer.kustomer.com/api/workflows/create-a-workflow) has the full request schema, and [Patterns](https://developer.kustomer.com/docs/workflows/patterns) has proven examples.
