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.
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.
This page is the field-by-field reference. For a step-by-step tutorial, see Write workflow JSON.
{
"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
namestringrequired- Machine-friendly name: letters, numbers,
-,_and., up to 128 characters. Kustomer stores it lowercased and trimmed, and it must be unique in the org; a duplicate is rejected with409. The builder makes it from the workflow title (lowercased, spaces replaced with-) and keeps the title as typed inmeta.displayName. Renaming a workflow in the builder's Edit info changesnametoo, so look workflows up by ID. descriptionstring- Up to 4,096 characters.
enabledboolean- Whether the workflow runs. New workflows are created disabled unless you set this. Changes take about a minute to apply. The builder's on/off switch sends only this key.
triggerobject- An event trigger or a callable trigger. You can save a workflow without one, but it never runs until it has one.
stepsobject[]- The steps, in any order. The trigger's transitions decide which runs first.
responseobject- For callable workflows, the outputs the workflow sends back:
{ "schema": { "properties": { "<name>": { "type": "string" } }, "required": [] } }. It's the builder's Output Parameters tab. See Callable and scheduled workflows. objectMappingobject- Used by object-mapping workflows, which map an inbound payload onto Kustomer records:
mappedKlasses,attributeMapping, andsampleHookPayload(up to 10,240 characters). Leave it as Kustomer wrote it. loggingboolean- Run logging. In the builder, turning on Debug Mode in View Logs sets this and records runs for one hour. See Debugging.
replacesstring | null- The ID of another workflow that this one replaces.
metaobject- A flat map of metadata. The builder stores the workflow's title here as
displayName. See the rules below.
No other root keys are allowed. Unknown keys are rejected.
Root meta must be flat:
Trigger
Event trigger
{ "id": "1", "eventName": "kustomer.conversation.update", "transitions": [ … ] }idstringrequired- The trigger's step ID. Always
"1"by convention, so its output is/#steps.1. eventNamestringrequired- The event that starts a run, such as
kustomer.conversation.update. Must start with a letter; letters, numbers,-,_and.; up to 128 characters. See triggers. transitionsobject[]required- Where to go first. Up to 10. An empty array means the workflow does nothing.
appVersionstring- For an app's trigger, the app and version range, such as
"shopify-^2.1.0". metaobject- Builder metadata.
displayNameis the trigger's title. For a custom-object trigger, the builder also storestemplate, such as"kustomer.kobject.__name__.create", with the Klass filled intoeventName.
Callable trigger
{ "id": "1", "callable": true, "schema": { "type": "object", "properties": { … } }, "transitions": [ … ] }idstringrequired"1".callabletruerequired- Marks the workflow as callable. Kustomer sets
eventNametokustomer.workflow.<workflowId>.callfor you. schemaobjectrequired- A JSON Schema for the inputs, written by the builder as
{ "properties": { "<name>": { "type": "string" } }, "required": […] }. Inputs are cast to it and become step1's output, with nothing else added: read an input as/#steps.1.<name>. transitionsobject[]required- Where to go first.
All four keys are required; a callable trigger without one is rejected when you save. Declare the workflow's outputs in the root response key.
How callers run callable workflows is covered in Callable and scheduled workflows.
Steps
idstringrequired- Unique within the workflow: letters, numbers,
-,_and., up to 128 characters. Other steps reference the output as/#steps.<id>. The builder generates short random IDs, such asSAk4K54Ly; hand-written workflows can use readable ones likelookup. Use IDs without dots, because transition targets can't contain them. actionstring- The action to run, such as
kustomer.note.create. See actions. Optional: a step without an action only evaluates its transitions. The builder calls this a Condition Step. paramsobject- The action's inputs: literals, references, functions and templates. Each is cast to the action's input type.
transitionsobject[]required- Where to go next, checked in order. Up to 10, and no two identical. Empty ends the run after this step.
errorCasesobject[]- What to do if the step fails. Up to 10. The classic builder can't edit these, so add them in JSON. See Handle errors.
appVersionstring- For an app's action, the app and a semver range:
<app>-<range>, such as"scheduler-^1.0.1"or"kustomer-^1.8.16". The builder adds it to every step when you save; it's optional in the API. metaobject- Builder metadata.
displayNameis the step's title in the builder, such as "Look up the customer".
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
{ "target": "raise", "condition": { "op": "eq", "values": ["/#steps.customer.custom.tierStr", "gold"] }, "meta": { "name": "Gold tier" } }targetstringrequired- The ID of the next step: letters, numbers,
-and_(no dots). A target that doesn't exist fails the run with "next step not found". conditionobjectrequired{ "op", "values" }. Use{ "op": "true", "values": [true] }to always take this path. See operators.metaobject- Builder metadata.
namelabels the branch on the canvas. It's free text, used only for display.
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. - 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
{
"condition": { "op": "and", "values": [{ "op": "eq", "values": ["/#error.status", 400] }] },
"errorAction": { "type": "continue" }
}conditionobjectrequiredopmust beandoror, with up to 5 nested conditions. Emptyvaluesmatches every error. The error is available as/#error, never as/#steps.<id>.error.errorAction.typestringrequiredcontinueorstop-processing. The schema also acceptsretry-workflow, which behaves exactly likecontinue, andretry-action, which isn't handled, so the step fails as if no case matched.
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.
Create and update with the API
Create
Shellcurl -X POST "https://acme.api.kustomerapp.com/v1/workflows" \ -H "Authorization: Bearer $KUSTOMER_API_KEY" -H "Content-Type: application/json" \ --data @vip-first-reply.jsonThe response is the workflow with its
id. Create it with"enabled": false, add any variables 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 wholestepsarray, for example), and keys you leave out are unchanged. Always sendtriggerandstepstogether, because transitions and steps refer to each other.Shellkusty 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.jsonFor a callable workflow, include
responsetoo if its outputs change. To switch a workflow on or off, send onlyenabled, as the builder's switch does:Shellkusty 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 changelogging.Wait, then test
Allow about a minute for the change to apply, then cause the trigger event and check the logs.
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:
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.
{
"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:
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.