REST API Reference
The Workflow module provides a REST API for creating workflow instances, sending events, and querying instance state. This enables integration with external systems, web applications, and custom clients. (This API is only available from version 26.1)
Overview
Base URL: workflow/api/v1
Authentication: All endpoints require authentication via bearer token or session cookie.
Content-Type: application/json
Key Concepts: - Workflow ID - Unique identifier for a workflow definition - Instance ID - Unique identifier for a workflow instance - Event - Message sent to trigger state transitions
API Endpoints
Create Workflow Instance
Create a new instance of a workflow definition.
Endpoint: POST workflow/api/v1/create
Request Body:
{
"workflowId": "workflow-id-string",
"data": {
"elxPublic": {
"field1": "value1",
"field2": "value2"
},
"elxPrivate": {
"sensitiveField": "sensitive-value"
}
}
}
Response (Success):
{
"workflowId": "workflow-id-string",
"instanceId": "instance-id-string",
"state": {
"_id": "instance-id-string",
"workflowId": "workflow-id-string",
"elxPublic": {
"field1": "value1",
"field2": "value2"
},
"elxPrivate": {
"sensitiveField": "sensitive-value"
},
"elxHistory": [
{
"user": "john.doe",
"startState": ["Start"],
"transitionEvent": "",
"endState": ["InitialState"],
"ts": "2026-02-28T10:00:00Z"
}
],
"states": [
{
"stateMachineId": "main",
"nodeId": "InitialState"
}
]
}
}
Example (curl):
curl -X POST http://localhost:8080/workflow/api/v1/create \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"workflowId": "leave-request",
"data": {
"elxPublic": {
"employeeName": "Jane Doe",
"leaveType": "Annual",
"startDate": "2026-03-01",
"endDate": "2026-03-05",
"days": 5
}
}
}'
Example (JavaScript):
const response = await fetch('/workflow/api/v1/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
workflowId: 'leave-request',
data: {
elxPublic: {
employeeName: 'Jane Doe',
leaveType: 'Annual',
startDate: '2026-03-01',
endDate: '2026-03-05',
days: 5
}
}
})
});
const result = await response.json();
console.log('Instance ID:', result.instanceId);
Example (Python):
import requests
response = requests.post(
'http://localhost:8080/workflow/api/v1/create',
headers={
'Content-Type': 'application/json',
'Authorization': f'Bearer {token}'
},
json={
'workflowId': 'leave-request',
'data': {
'elxPublic': {
'employeeName': 'Jane Doe',
'leaveType': 'Annual',
'startDate': '2026-03-01',
'endDate': '2026-03-05',
'days': 5
}
}
}
)
result = response.json()
print(f"Instance ID: {result['instanceId']}")
Send Event to Instance
Send an event to an existing workflow instance to trigger state transitions.
Endpoint: POST workflow/api/v1/event
Request Body:
{
"workflowId": "workflow-id-string",
"instanceId": "instance-id-string",
"event": {
"name": "event-name",
"data": {
"eventField1": "value1",
"eventField2": "value2"
}
}
}
Response (Success):
{
"workflowId": "workflow-id-string",
"instanceId": "instance-id-string",
"state": {
"_id": "instance-id-string",
"workflowId": "workflow-id-string",
"elxPublic": {
"field1": "updated-value1"
},
"elxPrivate": {},
"elxHistory": [
{
"user": "john.doe",
"startState": ["PreviousState"],
"transitionEvent": "event-name",
"endState": ["NewState"],
"ts": "2026-02-28T10:05:00Z"
}
],
"states": [
{
"stateMachineId": "main",
"nodeId": "NewState"
}
]
}
}
Example (curl):
curl -X POST http://localhost:8080/workflow/api/v1/event \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"workflowId": "leave-request",
"instanceId": "abc123",
"event": {
"name": "approve",
"data": {
"approver": "manager.smith",
"approvedAt": "2026-02-28T10:00:00Z",
"comments": "Approved"
}
}
}'
Example (JavaScript):
const response = await fetch('/workflow/api/v1/event', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
workflowId: 'leave-request',
instanceId: instanceId,
event: {
name: 'approve',
data: {
approver: 'manager.smith',
approvedAt: new Date().toISOString(),
comments: 'Approved'
}
}
})
});
const result = await response.json();
console.log('New state:', result.state.states[0].nodeId);
Example (Python):
response = requests.post(
'http://localhost:8080/workflow/api/v1/event',
headers={
'Content-Type': 'application/json',
'Authorization': f'Bearer {token}'
},
json={
'workflowId': 'leave-request',
'instanceId': instance_id,
'event': {
'name': 'approve',
'data': {
'approver': 'manager.smith',
'approvedAt': '2026-02-28T10:00:00Z',
'comments': 'Approved'
}
}
}
)
result = response.json()
print(f"New state: {result['state']['states'][0]['nodeId']}")
Load Instance by ID
Retrieve the current state of a workflow instance.
Endpoint: GET workflow/api/v1/instance/{workflowId}/{instanceId}
Response:
{
"state": {
"_id": "instance-id-string",
"workflowId": "workflow-id-string",
"elxPublic": {
"field1": "value1"
},
"elxPrivate": {},
"elxHistory": [...],
"states": [
{
"stateMachineId": "main",
"nodeId": "CurrentState"
}
]
}
}
Example (curl):
curl -X GET http://localhost:8080/workflow/api/v1/instance/leave-request/abc123 \
-H "Authorization: Bearer YOUR_TOKEN"
Example (JavaScript):
const response = await fetch(
`/workflow/api/v1/instance/${workflowId}/${instanceId}`,
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
const result = await response.json();
console.log('Current state:', result.state.states[0].nodeId);
console.log('Public data:', result.state.elxPublic);
Query All Instances
Retrieve all instances of a workflow with optional filtering.
Endpoint: GET workflow/api/v1/instances/{workflowId}?filter={codexFilter}
Query Parameters: - filter (optional) - CodexFilter JSON for filtering instances
Response:
{
"instances": [
{
"_id": "instance-1",
"elxPublic": {...},
"states": [...]
},
{
"_id": "instance-2",
"elxPublic": {...},
"states": [...]
}
]
}
Example (curl):
curl -X GET "http://localhost:8080/workflow/api/v1/instances/leave-request" \
-H "Authorization: Bearer YOUR_TOKEN"
Example with Filter:
# Filter for instances in "Pending Approval" state
curl -X GET "http://localhost:8080/workflow/api/v1/instances/leave-request?filter=%7B%22states.nodeId%22%3A%22PendingApproval%22%7D" \
-H "Authorization: Bearer YOUR_TOKEN"
Get Workflow Choices
Retrieve list of available workflows for dropdown/selection.
Endpoint: GET workflow/api/v1/choices
Response:
{
"workflows": [
{
"id": "leave-request",
"name": "Leave Request"
},
{
"id": "expense-claim",
"name": "Expense Claim"
}
]
}
Error Responses
Common Error Codes
400 Bad Request:
{
"error": "InvalidContent",
"message": "Invalid workflow data format"
}
403 Forbidden:
{
"error": "AccessDenied",
"message": "User does not have permission to access this workflow"
}
404 Not Found:
{
"error": "NotFound",
"message": "Workflow or instance not found"
}
409 Conflict:
{
"error": "AlreadyExists",
"message": "Workflow with this name already exists"
}
500 Internal Server Error:
{
"error": "MarshallingError",
"message": "Failed to process workflow data"
}
Integration Patterns
Pattern 1: Form Submission Workflow
Create workflow instance when user submits a form:
async function submitLeaveRequest(formData) {
try {
// Create workflow instance
const response = await fetch('/workflow/api/v1/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`
},
body: JSON.stringify({
workflowId: 'leave-request',
data: {
elxPublic: {
employeeName: formData.employeeName,
leaveType: formData.leaveType,
startDate: formData.startDate,
endDate: formData.endDate,
days: calculateDays(formData.startDate, formData.endDate),
reason: formData.reason
}
}
})
});
const result = await response.json();
// Redirect based on elxRedirect field
if (result.state.elxRedirect) {
window.location.href = result.state.elxRedirect;
} else {
window.location.href = `/leave-requests/${result.instanceId}`;
}
} catch (error) {
console.error('Failed to submit leave request:', error);
showError('Failed to submit leave request. Please try again.');
}
}
Pattern 2: Approval Action
Send approval/rejection events:
async function approveLeaveRequest(instanceId, comments) {
const response = await fetch('/workflow/api/v1/event', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`
},
body: JSON.stringify({
workflowId: 'leave-request',
instanceId: instanceId,
event: {
name: 'approve',
data: {
approver: getCurrentUser(),
approvedAt: new Date().toISOString(),
comments: comments
}
}
})
});
const result = await response.json();
// Update UI to reflect new state
updateStatusDisplay(result.state.states[0].nodeId);
// Show success message
showSuccess('Leave request approved successfully');
}
async function rejectLeaveRequest(instanceId, reason) {
const response = await fetch('/workflow/api/v1/event', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`
},
body: JSON.stringify({
workflowId: 'leave-request',
instanceId: instanceId,
event: {
name: 'reject',
data: {
rejectedBy: getCurrentUser(),
rejectedAt: new Date().toISOString(),
reason: reason
}
}
})
});
const result = await response.json();
updateStatusDisplay(result.state.states[0].nodeId);
showSuccess('Leave request rejected');
}
Pattern 3: Dashboard Query
Query instances for dashboard display:
async function loadPendingApprovals() {
const response = await fetch(
'/workflow/api/v1/instances/leave-request',
{
headers: {
'Authorization': `Bearer ${getToken()}`
}
}
);
const result = await response.json();
// Filter for pending approvals
const pending = result.instances.filter(instance =>
instance.states.some(s => s.nodeId === 'PendingApproval')
);
// Display in dashboard
displayPendingApprovals(pending);
}
Pattern 4: Polling for Updates
Poll for workflow state changes:
async function pollWorkflowState(workflowId, instanceId, onUpdate) {
let currentState = null;
const poll = async () => {
try {
const response = await fetch(
`/workflow/api/v1/instance/${workflowId}/${instanceId}`,
{
headers: {
'Authorization': `Bearer ${getToken()}`
}
}
);
const result = await response.json();
const newState = result.state.states[0].nodeId;
if (newState !== currentState) {
currentState = newState;
onUpdate(result.state);
}
} catch (error) {
console.error('Polling error:', error);
}
};
// Poll every 5 seconds
const intervalId = setInterval(poll, 5000);
// Initial poll
poll();
// Return cleanup function
return () => clearInterval(intervalId);
}
// Usage
const stopPolling = await pollWorkflowState(
'leave-request',
instanceId,
(state) => {
console.log('State changed to:', state.states[0].nodeId);
updateUI(state);
}
);
Best Practices
Security
- Always use HTTPS in production
- Validate user permissions before sending events
- Never expose sensitive data in
elxPublic- useelxPrivate - Sanitize user input before creating instances
Error Handling
- Always handle API errors gracefully
- Provide user-friendly error messages
- Log errors for debugging
- Implement retry logic for transient failures
Performance
- Use instance queries with filters to reduce data transfer
- Cache workflow choices if they don’t change frequently
- Implement pagination for large result sets
- Use WebSockets or Server-Sent Events for real-time updates instead of polling
Data Management
- Keep
elxPublicdata minimal - only what’s needed for display - Use
elxPrivatefor server-side processing data - Leverage
elxRedirectfor navigation control - Clean up completed instances periodically
See Also
- Core Concepts - Understanding workflow data model
- ETL Integration - Triggering workflows from ETL
- Examples - Complete integration examples
- Quick Start Guide - Getting started with workflows