Skip to main content

Validation

Stateway validates typed variables at every point where data enters or transitions inside a process. There are no blind spots: if a variable has a declared type, it is checked every time it is written.

Validation Points

PointTriggerWhat Is Validated
Instance creationPOST /v1/instancesInitial variables against declared dataObject types
Variable updatePOST /v1/instances/:id/variablesUpdated variables against declared dataObject types
Task completionPOST /v1/tasks/:id/completeOutput payload against task ioSpecification outputSets
Service task returnHTTP response from external serviceResponse body against task ioSpecification dataOutputs
DataStore readCross-instance data lookupProjected fields against the target dataObject type

Untyped variables (not declared as dataObject) always pass through without validation.

Validation Failures at the API Boundary

For POST /v1/instances and POST /v1/instances/:id/variables, validation failures return HTTP 400:

{
"error": "ValidationError",
"errors": [
{
"path": "applicant.email",
"value": "not-an-email",
"message": "must match format \"email\""
},
{
"path": "applicant.fullName",
"value": "A",
"message": "must NOT have fewer than 2 characters"
}
]
}

The instance is not created. No partial state is written.

Validation Failures Inside the Engine

For failures triggered during execution (task completion, service task response, DataStore read), Stateway:

  1. Does not advance the token. The activity stays in its current state.
  2. Fires a ValidationError BPMN event that you can capture with a boundary error event on the activity.
  3. If no boundary event captures it, the instance moves to status: 'error'.

See Error Handling for boundary event patterns and the retry flow.

Task Completion Validation

When POST /v1/tasks/:id/complete is submitted with output that doesn't satisfy any declared outputSet:

Example: Task expects { decision, approvedLimit } or { decision, rejectionReason }, but receives { decision } alone.

curl -X POST https://api.stateway.io/v1/tasks/task_01.../complete \
-H "X-API-Key: sw_live_your_key" \
-d '{ "decision": "approved" }'

Response 400:

{
"error": "ValidationError",
"message": "No outputSet satisfied. Tried: Approval (missing: approvedLimit), Rejection (missing: rejectionReason)"
}

The task remains in claimed state. The user may resubmit with the correct payload.

Enum Validation

Enum types reject any value not in the declared set:

curl -X POST https://api.stateway.io/v1/tasks/task_01.../complete \
-H "X-API-Key: sw_live_your_key" \
-d '{ "decision": "APPROVED" }'

Response 400:

{
"error": "ValidationError",
"errors": [
{
"path": "decision",
"value": "APPROVED",
"message": "must be equal to one of the allowed values: draft, approved, rejected"
}
]
}
warning

Enum values are case-sensitive. "APPROVED" and "approved" are different strings.

Object Validation

Object types validate each property individually:

curl -X POST https://api.stateway.io/v1/instances \
-H "X-API-Key: sw_live_your_key" \
-d '{
"definition_key": "loan-application",
"variables": {
"applicant": { "email": "bad" }
}
}'

Response 400:

{
"error": "ValidationError",
"errors": [
{ "path": "applicant.fullName", "message": "must have required property 'fullName'" },
{ "path": "applicant.email", "message": "must match format \"email\"" }
]
}

Collection Validation

Each element of a typed collection is validated individually. The error path includes the array index:

{
"error": "ValidationError",
"errors": [
{ "path": "documents[1].url", "message": "must match format \"uri\"" }
]
}

XSD Primitive Validation

XSD primitive types enforce the corresponding JSON type:

TypeValid ValuesInvalid Example
xsd:stringAny JSON string42 (number)
xsd:decimalAny JSON number"100" (string)
xsd:intInteger JSON number3.14 (float)
xsd:booleantrue or false"true" (string)
xsd:dateTimeISO 8601 date-time string"2026-06" (partial)

Ad-hoc Variables Are Never Rejected

Variables not declared as dataObject are always accepted without validation:

# "internal_counter" is not typed — always accepted
curl -X POST https://api.stateway.io/v1/instances \
-d '{ "definition_key": "...", "variables": { "internal_counter": 0 } }'

This lets you introduce types incrementally without touching existing variables.