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 - use elxPrivate
  • 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 elxPublic data minimal - only what’s needed for display
  • Use elxPrivate for server-side processing data
  • Leverage elxRedirect for navigation control
  • Clean up completed instances periodically

See Also