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
| Point | Trigger | What Is Validated |
|---|---|---|
| Instance creation | POST /v1/instances | Initial variables against declared dataObject types |
| Variable update | POST /v1/instances/:id/variables | Updated variables against declared dataObject types |
| Task completion | POST /v1/tasks/:id/complete | Output payload against task ioSpecification outputSets |
| Service task return | HTTP response from external service | Response body against task ioSpecification dataOutputs |
| DataStore read | Cross-instance data lookup | Projected 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:
- Does not advance the token. The activity stays in its current state.
- Fires a
ValidationErrorBPMN event that you can capture with a boundary error event on the activity. - 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"
}
]
}
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:
| Type | Valid Values | Invalid Example |
|---|---|---|
xsd:string | Any JSON string | 42 (number) |
xsd:decimal | Any JSON number | "100" (string) |
xsd:int | Integer JSON number | 3.14 (float) |
xsd:boolean | true or false | "true" (string) |
xsd:dateTime | ISO 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.