Raw schema
{
"type": "object",
"properties": {
"workflowId": {
"type": "string",
"description": "The wf-… id."
},
"name": {
"type": "string",
"description": "New display name. Omit to leave it unchanged — this is a partial update, not a replace."
},
"trigger": {
"type": "string",
"description": "Event name that enrols a contact, e.g. `order.placed`. Must match what your app actually sends, character for character: a mismatch is not an error, it simply never fires. Check volanea_list_resources { event_names } for what this project has really recorded."
},
"graph": {
"type": "object",
"description": "Automation graph. Exactly one `trigger` node is the entry point; edges connect node ids. Node types and their `config`:\n- trigger: entry node, no config.\n- send_email: { templateId? } and/or { subject?, body? (HTML), fromEmail?, fromName? } — template fields fill anything omitted.\n- delay: { minutes?, hours?, days? } (defaults to 1 minute if all omitted).\n- wait_event: { event: \"<eventName>\", timeoutHours? (default 72) }. The unlabeled outgoing edge fires when the event arrives; an edge labeled \"timeout\" is taken on timeout.\n- condition: { conditions: <same condition tree as segments> }. Outgoing edges must be labeled \"yes\" and \"no\".\n- update_contact: { set: { <customField>: value, ... }, subscribed? }.\n- webhook: { url, payload? } — POSTs workflow/contact context to your URL (5s timeout).\n- exit: ends the walk.\n`x`/`y` are canvas coordinates for the dashboard editor — space nodes out (e.g. x = index * 260, y = 80).",
"properties": {
"nodes": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"trigger",
"send_email",
"delay",
"wait_event",
"condition",
"update_contact",
"webhook",
"exit"
]
},
"label": {
"type": "string",
"maxLength": 120
},
"config": {
"type": "object",
"additionalProperties": true
},
"x": {
"type": "number"
},
"y": {
"type": "number"
}
},
"required": [
"id",
"type",
"label",
"x",
"y"
]
}
},
"edges": {
"type": "array",
"maxItems": 200,
"items": {
"type": "object",
"properties": {
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"label": {
"type": "string",
"description": "Branch label — \"yes\"/\"no\" for condition nodes, \"timeout\" for wait_event."
}
},
"required": [
"from",
"to"
]
}
}
},
"required": [
"nodes",
"edges"
]
},
"reentry": {
"type": "string",
"enum": [
"never",
"after_exit",
"always"
],
"description": "Whether a contact can enter again. `never` = once per contact, ever (welcome series). `after_exit` = again once they have finished (abandoned cart). `always` = every trigger enrols them, even mid-run — the only option that can send one contact overlapping copies, so choose it deliberately."
},
"status": {
"type": "string",
"enum": [
"draft",
"active",
"paused"
],
"description": "`draft` and `paused` workflows enrol NO ONE: matching events are silently ignored, which is the usual reason a workflow \"does not fire\". Set `active` to go live — that starts affecting real contacts immediately."
}
},
"required": [
"workflowId"
]
}