Processes
Stateway supports three formats for process definitions: BPMN 2.0 XML, JSON, and YAML. All three are parsed into the same internal model and run on the same engine. BPMN 2.0 XML is the canonical format — JSON and YAML express a subset of what it can model. They are convenience formats for the same concepts, never a superset with different semantics. See JSON/YAML vs BPMN — Known Limitations for what BPMN can express that JSON/YAML cannot.
Supported Formats
| Format | source_type | Best For |
|---|---|---|
| BPMN 2.0 XML | bpmn | Standard interchange; compatible with Camunda Modeler, bpmn.io |
| JSON | json | Programmatic creation; AI agents and automated pipelines |
| YAML | yaml | Human-readable authoring |
| BPMN (base64) | bpmn-base64 | Transmitting binary BPMN files as JSON strings |
JSON/YAML vs BPMN — Known Limitations
BPMN 2.0 XML is the canonical format. JSON and YAML cover the same execution model, but a few BPMN capabilities have no JSON/YAML equivalent. When you need one of these, author the process — or just the affected elements — in BPMN 2.0 XML.
| Capability | BPMN 2.0 XML | JSON / YAML |
|---|---|---|
| Typed I/O specifications — named, typed activity inputs/outputs, form auto-generation, and typed output validation | ✅ via ioSpecification + itemDefinition | ❌ Declare the form explicitly with formSchema; task outputs are not type-validated. See Typed I/O & Form Schemas. |
Data objects & data stores — itemDefinition, dataObject, dataStoreReference | ✅ parsed from XML | ❌ Not expressible in JSON/YAML. See Types & Validation. |
Gateway default flow attribute — a named fallback flow | ✅ via default="flowId" | ❌ No named default; the fallback is the first outgoing flow with no condition. See Expressions in Conditions. |
Everything else — element types, flows, conditions, timers, assignments, service-task definitions, and variableFilter — behaves identically across all three formats.
Creating a Definition
JSON Format
The JSON source object requires a key, an elements array, and a flows array. Elements declare their outgoing flow IDs; flows declare their sourceRef and targetRef.
Request:
curl -X POST https://api.stateway.io/v1/definitions \
-H "X-API-Key: sw_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"key": "expense-approval",
"name": "Expense Approval",
"source_type": "json",
"source": {
"key": "expense-approval",
"elements": [
{ "id": "start", "type": "startEvent", "outgoing": ["flow1"] },
{ "id": "review", "type": "userTask", "name": "Review Expense", "outgoing": ["flow2"] },
{ "id": "end", "type": "endEvent" }
],
"flows": [
{ "id": "flow1", "sourceRef": "start", "targetRef": "review" },
{ "id": "flow2", "sourceRef": "review", "targetRef": "end" }
]
}
}'
Response:
{
"data": {
"id": "def_01j...",
"key": "expense-approval",
"version": 1,
"name": "Expense Approval",
"source_type": "json",
"source_hash": "a3f8c2d1...",
"is_active": true,
"created_at": "2026-04-26T12:00:00.000Z"
}
}
YAML Format
YAML source is a string that serializes the same structure as JSON:
Request:
curl -X POST https://api.stateway.io/v1/definitions \
-H "X-API-Key: sw_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"key": "expense-approval",
"name": "Expense Approval",
"source_type": "yaml",
"source": "key: expense-approval\nelements:\n - id: start\n type: startEvent\n outgoing: [flow1]\n - id: review\n type: userTask\n name: Review Expense\n outgoing: [flow2]\n - id: end\n type: endEvent\nflows:\n - id: flow1\n sourceRef: start\n targetRef: review\n - id: flow2\n sourceRef: review\n targetRef: end\n"
}'
Response:
{
"data": {
"id": "def_01j...",
"key": "expense-approval",
"version": 1,
"name": "Expense Approval",
"source_type": "yaml",
"source_hash": "b4d9f7e2...",
"is_active": true,
"created_at": "2026-04-26T12:00:00.000Z"
}
}
BPMN XML Format
To generate well-formed BPMN 2.0 XML with correct element IDs, namespace declarations, and Stateway extensions, use the bpmn-xml-generator Claude skill.
Pass the raw BPMN XML as a string in source:
Request:
curl -X POST https://api.stateway.io/v1/definitions \
-H "X-API-Key: sw_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"key": "expense-approval",
"name": "Expense Approval",
"source_type": "bpmn",
"source": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><bpmn:definitions xmlns:bpmn=\"http://www.omg.org/spec/BPMN/20100524/MODEL\" xmlns:stateway=\"https://stateway.io/schema/bpmn/1.0\"><bpmn:process id=\"expense-approval\" isExecutable=\"true\"><bpmn:startEvent id=\"start\"><bpmn:outgoing>flow1</bpmn:outgoing></bpmn:startEvent><bpmn:userTask id=\"review\" name=\"Review Expense\"><bpmn:incoming>flow1</bpmn:incoming><bpmn:outgoing>flow2</bpmn:outgoing></bpmn:userTask><bpmn:endEvent id=\"end\"><bpmn:incoming>flow2</bpmn:incoming></bpmn:endEvent><bpmn:sequenceFlow id=\"flow1\" sourceRef=\"start\" targetRef=\"review\"/><bpmn:sequenceFlow id=\"flow2\" sourceRef=\"review\" targetRef=\"end\"/></bpmn:process></bpmn:definitions>"
}'
Response:
{
"data": {
"id": "def_01j...",
"key": "expense-approval",
"version": 1,
"name": "Expense Approval",
"source_type": "bpmn",
"source_hash": "c6e8a1b3...",
"is_active": true,
"created_at": "2026-04-26T12:00:00.000Z"
}
}
BPMN (base64) Format
If you're loading a BPMN file from disk or transmitting it over a JSON-only channel, use source_type: "bpmn-base64". The API base64-decodes the source string to UTF-8 XML before parsing — the result is identical to sending source_type: "bpmn" directly. The stored source_type in the response will be "bpmn" (the decoded form).
To encode a file: base64 expense-approval.bpmn
Request:
curl -X POST https://api.stateway.io/v1/definitions \
-H "X-API-Key: sw_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"key": "expense-approval",
"name": "Expense Approval",
"source_type": "bpmn-base64",
"source": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48YnBtbjpkZWZpbml0aW9ucy..."
}'
Response:
{
"data": {
"id": "def_01j...",
"key": "expense-approval",
"version": 1,
"name": "Expense Approval",
"source_type": "bpmn",
"source_hash": "c6e8a1b3...",
"is_active": true,
"created_at": "2026-04-26T12:00:00.000Z"
}
}
JSON/YAML Element Reference
Each element in the elements array supports these fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | ✅ | Unique identifier within the process |
type | string | ✅ | Element type (see below) |
name | string | — | Human-readable label |
outgoing | string[] | — | IDs of outgoing flows |
timerType | date | duration | cycle | — | For timerEvent elements |
timerExpression | string | — | ISO 8601 date, duration, or cycle expression |
assignee | string | — | For userTask — supports {{variables.x}} |
taskDefinition | object | — | For serviceTask / businessRuleTask |
taskHeaders | object | — | Key-value headers for service tasks |
ioMapping | object | — | Input/output variable mapping |
formSchema | object | — | For userTask — JSON Schema for the task form, rendered by the Frontend Gateway. In JSON/YAML you declare it explicitly; there is no auto-generation from types (see below). |
Typed I/O & Form Schemas (BPMN XML only)
BPMN 2.0 XML lets you attach a typed I/O specification to an activity — named, typed inputs and
outputs (ioSpecification with dataInput/dataOutput, whose types come from itemDefinition
declarations). JSON and YAML definitions express a subset of the process model and cannot declare
this typed contract. Two behaviors that depend on it are therefore unavailable when a process is
authored in JSON/YAML:
- Form auto-generation. In BPMN, a
userTaskwith a typed I/O specification gets its form generated automatically from the declared inputs and their types. In JSON/YAML there is no typed I/O specification, so you must declare the form explicitly with theformSchemafield on theuserTaskelement. - Typed output validation. In BPMN, the outputs submitted when a task or service task completes
are validated against the activity's declared
outputSetsand types. Activities authored in JSON/YAML have no typed contract, so their outputs are accepted without type validation.
This is a documented limitation, not a bug: JSON/YAML are convenience formats for the same execution model, and they offer a subset of what BPMN XML can express — never a superset. If you need typed inputs/outputs, enumerations, or output validation, author the process (or at least the typed activities) in BPMN 2.0 XML. See Types & Validation for the full typed-modeling guide.
Supported Element Types
For the full, always up-to-date list of every element, field, and its support status, see the Supported Elements & Fields reference.
| Type | Description |
|---|---|
startEvent | Process entry point |
endEvent | Process termination |
task | Generic/abstract task — completes immediately and continues to the next flow (no side effects) |
userTask | Human task requiring manual action |
serviceTask | Automated HTTP call (sync or async) |
sendTask | Fire-and-forget HTTP call — the token advances immediately and the response is never mapped into variables. The response is still recorded, and is subject to the same 5 MB cap as a service task: a larger response is discarded and the call is recorded as a failure — even though the request itself was delivered. The process continues either way |
businessRuleTask | Evaluates a Decision Model (DMM) |
exclusiveGateway | XOR — exactly one outgoing branch taken |
parallelGateway | AND — all outgoing branches taken, waits for all to complete |
inclusiveGateway | OR-split — evaluates conditions, can take multiple paths simultaneously; when no condition matches, routes to the default flow (the BPMN default attribute, or in JSON/YAML the first outgoing flow with no condition) |
timerEvent | Pauses until a time condition is met (as startEvent or intermediateCatchEvent; see caveat below) |
intermediateThrowEvent | Emits a notification:published event (delivered via webhook and WebSocket), including the variables named in variableFilter when present |
boundaryEvent | Attached to an activity. Only Error boundary events fire today — they catch an output-schema ValidationError from the attached businessRuleTask or serviceTask and route to an alternate flow. Timer, Message, Signal, and Escalation boundary events are parsed but never trigger (see caveat below) |
intermediateCatchEvent only fires for Timer. Message, Signal, and Conditional catch events are recognized but pass through immediately (no correlation mechanism yet — see Unsupported Element Types).
Boundary events only fire for Error. Timer, Message, Signal, and Escalation boundary events are parsed but never trigger — there is no mechanism today to schedule/cancel them against the host activity's lifecycle. Planned, not yet supported.
Unsupported Element Types (yet)
The following BPMN 2.0 element types are not yet fully supported. Unrecognized types are silently skipped during parsing (the element and its outgoing flows are excluded from the process model). Recognized-but-unhandled types pass through immediately without executing any action.
Recognized but not executed:
| Type | Purpose | Notes |
|---|---|---|
scriptTask | Execute an inline script (Groovy, JavaScript, etc.) | Completes immediately without executing any script |
receiveTask | Wait for an inbound message before continuing | Completes immediately without waiting |
eventBasedGateway | Branch based on whichever event arrives first | Passes through |
subProcess | Encapsulate a nested flow inside a collapsed sub-process | Completes immediately without executing nested flow |
callActivity | Invoke a reusable child process definition | Completes immediately without calling the child process |
Not recognized (silently skipped during parsing):
| Type | Purpose | Notes |
|---|---|---|
messageFlow | Message-based communication between participants | Not supported |
laneSet / lane | Visual swimlane organization of elements | Not supported |
compensationEvent | Trigger compensating actions on rollback | Not supported |
escalationEvent | Escalate to a parent process scope | Not supported |
linkEvent | Jump between points in the same process (go-to) | Not supported |
complexGateway | Merge/split based on custom boolean conditions | Not supported |
multiInstanceTask | Execute a task N times (loop / parallel fan-out) | Not supported |
Data objects and data stores are a BPMN 2.0 XML feature. To declare typed process variables or query data from other process instances directly in the BPMN diagram, see Types & Validation. These constructs (itemDefinition, dataObject, dataStoreReference) are parsed only from BPMN XML — JSON/YAML definitions express a subset and cannot declare them.
Expressions in Conditions
Gateway flows can use the {{expression}} syntax in their condition field:
{
"flows": [
{ "id": "f-high", "sourceRef": "gw1", "targetRef": "high-value", "condition": "{{variables.amount > 10000}}" },
{ "id": "f-std", "sourceRef": "gw1", "targetRef": "standard", "condition": "{{variables.amount <= 10000}}" }
]
}
Format differences:
- BPMN XML: Conditions are placed on
bpmn:sequenceFlowelements usingbpmn:conditionExpression. Gateways support adefault="flowId"attribute to specify the fallback flow when no condition matches. - JSON/YAML: Use the
conditionfield on flow objects. There is nodefaultflow attribute equivalent in JSON/YAML format.
Both formats evaluate conditions using the same underlying mechanism.
Available context: variables, instance, now(). Operators: ==, !=, >, <, >=, <=, &&, ||, !
Listing Definitions
Request:
curl https://api.stateway.io/v1/definitions \
-H "X-API-Key: sw_live_your_key"
Response:
{
"data": [
{
"id": "def_01j...",
"key": "expense-approval",
"version": 2,
"name": "Expense Approval",
"source_type": "json",
"source_hash": "a3f8c2d1...",
"is_active": true,
"created_at": "2026-04-26T12:00:00.000Z"
}
]
}
Versioning
Every PUT to an existing definition creates a new version. Previous versions remain accessible. GET /definitions/:key always returns the currently active version.
List All Versions
Request:
curl https://api.stateway.io/v1/definitions/expense-approval/versions \
-H "X-API-Key: sw_live_your_key"
Response:
{
"data": [
{
"id": "def_01j...",
"key": "expense-approval",
"version": 1,
"name": "Expense Approval",
"source_type": "json",
"source_hash": "a3f8c2d1...",
"is_active": false,
"created_at": "2026-04-26T12:00:00.000Z"
},
{
"id": "def_02k...",
"key": "expense-approval",
"version": 2,
"name": "Expense Approval v2",
"source_type": "json",
"source_hash": "b9d7f3e5...",
"is_active": true,
"created_at": "2026-05-01T09:00:00.000Z"
}
]
}
Get a Specific Version
Request:
curl https://api.stateway.io/v1/definitions/expense-approval/versions/1 \
-H "X-API-Key: sw_live_your_key"
Response:
{
"data": {
"id": "def_01j...",
"key": "expense-approval",
"version": 1,
"name": "Expense Approval",
"source_type": "json",
"source_hash": "a3f8c2d1...",
"is_active": false,
"created_at": "2026-04-26T12:00:00.000Z"
}
}
Create a New Version
Request:
curl -X PUT https://api.stateway.io/v1/definitions/expense-approval \
-H "X-API-Key: sw_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Expense Approval v2",
"source_type": "json",
"source": {
"key": "expense-approval",
"elements": [
{ "id": "start", "type": "startEvent", "outgoing": ["flow1"] },
{ "id": "review", "type": "userTask", "name": "Review Expense", "outgoing": ["flow2"] },
{ "id": "approve", "type": "serviceTask", "name": "Notify Finance", "outgoing": ["flow3"] },
{ "id": "end", "type": "endEvent" }
],
"flows": [
{ "id": "flow1", "sourceRef": "start", "targetRef": "review" },
{ "id": "flow2", "sourceRef": "review", "targetRef": "approve" },
{ "id": "flow3", "sourceRef": "approve", "targetRef": "end" }
]
}
}'
Response:
{
"data": {
"id": "def_02k...",
"key": "expense-approval",
"version": 2,
"name": "Expense Approval v2",
"source_type": "json",
"source_hash": "b9d7f3e5...",
"is_active": true,
"created_at": "2026-05-01T09:00:00.000Z"
}
}
New instances use the latest active version. Existing instances continue running on the version they were started with.
Rollback to a Previous Version
Atomically reactivate a previous version, deactivating the current one:
Request:
curl -X POST https://api.stateway.io/v1/definitions/expense-approval/rollback \
-H "X-API-Key: sw_live_your_key" \
-H "Content-Type: application/json" \
-d '{ "version": 1 }'
Response:
{
"data": {
"id": "def_01j...",
"key": "expense-approval",
"version": 1,
"is_active": true,
"updated_at": "2026-05-10T08:00:00.000Z"
}
}
After rollback, new instances will use version 1. The next PUT (new version) will be assigned MAX(version) + 1 — in this case version 3 — to avoid collision with existing version numbers.
Instances that were already running are not affected — they remain bound to the definition_id they were started with.
See Process Versioning for a full guide on version management strategies.
Exporting as BPMN XML
Export any definition back to BPMN XML (regardless of original format):
Request:
curl https://api.stateway.io/v1/definitions/expense-approval/xml \
-H "X-API-Key: sw_live_your_key" \
-o expense-approval.bpmn
The response has Content-Type: application/xml with the BPMN 2.0 XML document.
Auto-Layout
Add ?layout=true to automatically inject diagram coordinates using bpmn-auto-layout. The result can be opened immediately in bpmn.io or Camunda Modeler without any manual positioning:
Request:
curl "https://api.stateway.io/v1/definitions/expense-approval/xml?layout=true" \
-H "X-API-Key: sw_live_your_key" \
-o expense-approval.bpmn
To export a specific version:
Request:
curl "https://api.stateway.io/v1/definitions/expense-approval/xml?version=1" \
-H "X-API-Key: sw_live_your_key" \
-o expense-approval-v1.bpmn
Mermaid Preview
Generate a Mermaid flowchart diagram from a definition — useful for documentation, sharing, and visual review:
Request:
curl https://api.stateway.io/v1/definitions/expense-approval/preview.mermaid \
-H "X-API-Key: sw_live_your_key"
The response is text/plain Mermaid syntax:
flowchart TD
start([start]) --> flow1
flow1 --> review[Review Expense]
review --> flow2
flow2 --> end([end])
To preview a specific version:
Request:
curl "https://api.stateway.io/v1/definitions/expense-approval/preview.mermaid?version=2" \
-H "X-API-Key: sw_live_your_key"
The Mermaid output is for visualization only — the BPMN (or JSON/YAML) definition remains the source of truth. It is not re-imported.
Deleting a Definition
Request:
curl -X DELETE https://api.stateway.io/v1/definitions/expense-approval \
-H "X-API-Key: sw_live_your_key"
Returns 204 No Content. This is a soft delete — running instances continue to execute on their original version. The definition key can be reused by creating a new definition with the same key.
Best Practices
- Use descriptive keys —
expense-approvalnotproc1 - Use meaningful element IDs —
review-expensenotnode3 - Add names to all elements — improves readability in API responses, logs, and task lists
- Start with JSON/YAML — easier to version-control and author programmatically; export to BPMN when visual editing is needed
- Use
?layout=trueon first export — saves time positioning elements in bpmn.io or Camunda Modeler - Version deliberately — each version is immutable; use rollback to revert rather than deleting versions