Skip to main content

I/O Specification

An I/O Specification declares the typed inputs and outputs of a specific task, independent of the process-level variable names. It is the local contract for one activity.

Available in BPMN XML only

These typed constructs (itemDefinition, ioSpecification, typed dataInput/dataOutput) are parsed only from BPMN 2.0 XML definitions. JSON/YAML process definitions express a subset: declare a userTask form explicitly via formSchema, and note that activity outputs are not type-validated. See Processes → Typed I/O & Form Schemas.

Why a Local Contract?

Without an I/O specification, a task implicitly reads and writes named variables. With one, you get:

  1. Name decoupling. The form shown to a user can use amount while the process variable is application.amount.
  2. Multiple completion paths. An approval task might accept either { decision, approvedLimit } or { decision, rejectionReason } — each is a valid outputSet.
  3. Automatic form generation. When the Frontend Gateway generates a task form, it reads the ioSpecification to know what fields to render and what types to enforce.

Elements Inside ioSpecification

ElementPurpose
<bpmn:dataInput>Named input parameter with a type
<bpmn:dataOutput>Named output parameter with a type
<bpmn:inputSet>Groups required inputs for one mode of execution
<bpmn:outputSet>Groups required outputs for one completion mode

Outside the spec, association elements wire parameters to process-level variables:

ElementPurpose
<bpmn:dataInputAssociation>Maps a process dataObjectReference → task dataInput
<bpmn:dataOutputAssociation>Maps a task dataOutput → process dataObjectReference

Minimal Example — Single Output

A review task that writes a typed decision:

<bpmn:userTask id="review" name="Review Application">
<bpmn:ioSpecification>
<bpmn:dataOutput id="out_decision" name="decision" itemSubjectRef="LoanStatus" />
<bpmn:outputSet>
<bpmn:dataOutputRefs>out_decision</bpmn:dataOutputRefs>
</bpmn:outputSet>
</bpmn:ioSpecification>

<bpmn:dataOutputAssociation>
<bpmn:sourceRef>out_decision</bpmn:sourceRef>
<bpmn:targetRef>StatusDataRef</bpmn:targetRef>
</bpmn:dataOutputAssociation>
</bpmn:userTask>

When POST /v1/tasks/:id/complete is called, the engine:

  1. Checks that decision is present in the payload.
  2. Validates decision against the LoanStatus enum.
  3. Merges the value into instance.variables.loan_status (via the output association).

Multiple Output Sets — Different Completion Paths

An approval task that accepts two distinct completion modes:

<bpmn:userTask id="approve" name="Approve Loan">
<bpmn:ioSpecification>
<!-- Inputs: populated from process variables by the Frontend Gateway -->
<bpmn:dataInput id="in_amount" name="amount" itemSubjectRef="Money" />
<bpmn:dataInput id="in_applicant" name="applicant" itemSubjectRef="Applicant" />
<bpmn:inputSet name="Default">
<bpmn:dataInputRefs>in_amount</bpmn:dataInputRefs>
<bpmn:dataInputRefs>in_applicant</bpmn:dataInputRefs>
</bpmn:inputSet>

<!-- Outputs -->
<bpmn:dataOutput id="out_decision" name="decision" itemSubjectRef="LoanStatus" />
<bpmn:dataOutput id="out_limit" name="approvedLimit" itemSubjectRef="Money" />
<bpmn:dataOutput id="out_reason" name="rejectionReason" itemSubjectRef="xsd:string" />

<!-- Path 1: approved with a limit -->
<bpmn:outputSet name="Approval">
<bpmn:dataOutputRefs>out_decision</bpmn:dataOutputRefs>
<bpmn:dataOutputRefs>out_limit</bpmn:dataOutputRefs>
</bpmn:outputSet>
<!-- Path 2: rejected with a reason -->
<bpmn:outputSet name="Rejection">
<bpmn:dataOutputRefs>out_decision</bpmn:dataOutputRefs>
<bpmn:dataOutputRefs>out_reason</bpmn:dataOutputRefs>
</bpmn:outputSet>
</bpmn:ioSpecification>

<!-- Input association: map process variable field to formal input -->
<bpmn:dataInputAssociation>
<bpmn:sourceRef>ApplicationDataRef</bpmn:sourceRef>
<bpmn:targetRef>in_amount</bpmn:targetRef>
<bpmn:assignment>
<bpmn:from>application.amount</bpmn:from>
<bpmn:to>in_amount</bpmn:to>
</bpmn:assignment>
</bpmn:dataInputAssociation>

<!-- Output association: write formal output to process variable -->
<bpmn:dataOutputAssociation>
<bpmn:sourceRef>out_decision</bpmn:sourceRef>
<bpmn:targetRef>StatusDataRef</bpmn:targetRef>
</bpmn:dataOutputAssociation>
</bpmn:userTask>

The engine checks whether the submitted output satisfies at least one declared outputSet. If neither is satisfied, validation fails and the task stays in claimed state.

Completing with Path 1 (Approval)

curl -X POST https://api.stateway.io/v1/tasks/task_01.../complete \
-H "X-API-Key: sw_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"decision": "approved",
"approvedLimit": 50000
}'

Completing with Path 2 (Rejection)

curl -X POST https://api.stateway.io/v1/tasks/task_01.../complete \
-H "X-API-Key: sw_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"decision": "rejected",
"rejectionReason": "Insufficient income documentation"
}'

I/O Specification on Service Tasks

Service tasks can also have an ioSpecification. The dataOutput types validate the HTTP response body returned by the external service:

<bpmn:serviceTask id="credit-check" name="Credit Check">
<bpmn:ioSpecification>
<bpmn:dataOutput id="out_score" name="creditScore" itemSubjectRef="xsd:int" />
<bpmn:dataOutput id="out_approved" name="approved" itemSubjectRef="xsd:boolean" />
<bpmn:outputSet>
<bpmn:dataOutputRefs>out_score</bpmn:dataOutputRefs>
<bpmn:dataOutputRefs>out_approved</bpmn:dataOutputRefs>
</bpmn:outputSet>
</bpmn:ioSpecification>
</bpmn:serviceTask>

If the external service returns { "creditScore": "good", "approved": true }, validation fails because creditScore must be an integer. The service task's boundary error event can catch this, or the instance moves to error state.

What the Frontend Gateway Delivers

When a task has an ioSpecification, the Frontend Gateway's task snapshot includes only the declared dataInput fields — by their formal name, not the underlying process variable name:

{
"taskId": "task_01...",
"name": "Approve Loan",
"snapshot": {
"amount": 75000,
"applicant": { "fullName": "Ana Lima", "email": "ana@example.com" }
}
}

The SDK exposes these via work.snapshot.amount and work.snapshot.applicant. If no form_schema is present, the Gateway automatically generates a form from the dataInput types, rendering enums as dropdowns and objects as field groups. See Form Schema Migration for details on using both together.

tip

Use multiple outputSet elements when a task can be completed in genuinely different ways (approve vs. reject, full vs. partial). Use a single outputSet with all optional fields when the completion payload is always the same shape but some fields may be absent.