Recurring Job APIs
Complete API reference for BankLingo's Recurring Job (Scheduled Task) System powered by Hangfire.
Overview
Recurring Jobs enable scheduled execution of commands, code, or BPMN processes using cron expressions. Perfect for EOD processing, report generation, data cleanup, and automated workflows.
Base Endpoint
POST /api/v1/execute
Content-Type: application/json
Authorization: Bearer {token}
Create Recurring Job
Create a new scheduled job.
CreateRecurringJobCommand
Request:
{
"cmd": "CreateRecurringJobCommand",
"data": {
"name": "Daily EOD Report",
"category": "Reports",
"cron": "0 0 * * *",
"executionType": 0,
"command": "GenerateEODReportCommand",
"context": "{\"reportType\":\"summary\"}",
"enabled": true
}
}
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Job name (unique identifier) |
category | string | No | Job category for grouping |
cron | string | Yes | Cron expression (e.g., "0 0 * * *") |
executionType | number | Yes | 0=Command, 1=CommandChain, 2=Code, 3=Process |
command | string | Conditional | Required if executionType=0 or 1 |
executionCode | string | Conditional | Required if executionType=2 |
processDefinitionId | number | Conditional | Required if executionType=3 |
context | string | No | JSON string with execution context/parameters |
contextLoader | string | No | Context loader function name |
enabled | boolean | No | Enable/disable job (default: true) |
Execution Types:
- 0 - ExecuteNativeCommand: Execute a command with context data
- 1 - ExecuteCommandChain: Execute multiple chained commands
- 2 - ExecuteCode: Execute JavaScript code expression
- 3 - StartProcess: Start a BPMN process with context data
Cron Expression Examples:
| Expression | Description |
|---|---|
0 0 * * * | Every day at midnight |
0 */6 * * * | Every 6 hours |
0 0 1 * * | First day of every month |
0 0 * * 0 | Every Sunday at midnight |
*/15 * * * * | Every 15 minutes |
Response:
{
"isSuccessful": true,
"message": "Recurring job created successfully",
"data": {
"id": 1,
"name": "Daily EOD Report",
"cron": "0 0 * * *",
"executionType": 0,
"enabled": true,
"nextRun": "2026-01-12T00:00:00Z"
}
}
Example 1: Execute Command Job
Daily Report Generation:
{
"cmd": "CreateRecurringJobCommand",
"data": {
"name": "Daily Transaction Report",
"cron": "0 1 * * *",
"executionType": 0,
"command": "GenerateTransactionReportCommand",
"context": "{\"reportType\":\"daily\",\"recipients\":[\"finance@bank.com\"]}",
"enabled": true
}
}
What Happens:
- Every day at 1 AM, Hangfire triggers the job
- Executes
GenerateTransactionReportCommandwith context data - Command generates report and emails recipients
- Execution result saved in
LastExecutionResult
Example 2: Execute Code Job
Balance Check & Alert:
{
"cmd": "CreateRecurringJobCommand",
"data": {
"name": "Low Balance Alert",
"cron": "0 */2 * * *",
"executionType": 2,
"executionCode": "var balances = doQuery('GetLowBalanceAccountsQuery', { threshold: 1000 }); if (balances.length > 0) { doCmd('SendEmailCommand', { to: 'ops@bank.com', subject: 'Low Balance Alert', body: balances.length + ' accounts below threshold' }); } return { ok: true, count: balances.length };",
"enabled": true
}
}
What Happens:
- Every 2 hours, Hangfire triggers the job
- Executes JavaScript code expression
- Code queries low balance accounts and sends alert
- Returns result object with alert count
Example 3: Start Process Job
Monthly Interest Posting:
{
"cmd": "CreateRecurringJobCommand",
"data": {
"name": "Monthly Interest Posting",
"cron": "0 0 1 * *",
"executionType": 3,
"processDefinitionId": 15,
"context": "{\"accountTypes\":[\"SAVINGS\",\"FIXED_DEPOSIT\"],\"month\":\"current\"}",
"enabled": true
}
}
What Happens:
- First day of every month at midnight, Hangfire triggers
- Starts BPMN process (ID=15) with context data
- Process variables include:
Context = {
accountTypes: ["SAVINGS", "FIXED_DEPOSIT"],
month: "current"
} - Process can access context:
execution.getVariable('Context')
Example BPMN Process:
<bpmn:process id="MonthlyInterestPostingProcess">
<bpmn:scriptTask id="LoadAccounts" name="Load Accounts">
<bpmn:script>
var context = execution.getVariable('Context');
var accountTypes = context.accountTypes;
var accounts = doQuery('GetAccountsByTypesQuery', {
types: accountTypes
});
execution.setVariable('accounts', accounts);
</bpmn:script>
</bpmn:scriptTask>
<bpmn:serviceTask id="PostInterest" name="Post Interest"
bankLingo:collection="${accounts}"
bankLingo:elementVariable="account"
isSequential="false">
<bankLingo:commandName>PostInterestCommand</bankLingo:commandName>
<bankLingo:inputParameter name="accountId">${account.id}</bankLingo:inputParameter>
</bpmn:serviceTask>
</bpmn:process>
Update Recurring Job
Update an existing scheduled job.
UpdateRecurringJobCommand
Request:
{
"cmd": "UpdateRecurringJobCommand",
"data": {
"id": 1,
"cron": "0 2 * * *",
"context": "{\"reportType\":\"detailed\"}",
"enabled": true
}
}
Response:
{
"isSuccessful": true,
"message": "Recurring job updated successfully",
"data": {
"id": 1,
"name": "Daily EOD Report",
"cron": "0 2 * * *",
"nextRun": "2026-01-12T02:00:00Z"
}
}
Get Recurring Job By ID
Retrieve job details including execution code.
GetRecurringJobByIdQuery
Request:
{
"cmd": "GetRecurringJobByIdQuery",
"data": {
"id": 1
}
}
Response:
{
"isSuccessful": true,
"message": "recurring job data retrieved successfully",
"data": {
"id": 1,
"name": "Daily EOD Report",
"category": "Reports",
"cron": "0 0 * * *",
"command": "GenerateEODReportCommand",
"executionType": 0,
"executionTypeDesc": "Execute Native Command",
"executionCode": null,
"processDefinitionId": null,
"context": "{\"reportType\":\"summary\"}",
"enabled": true,
"lastRun": "2026-01-11T00:00:00Z",
"lastFinished": "2026-01-11T00:05:23Z",
"lastExecutionResult": "{\"ok\":true,\"message\":\"Report generated\"}",
"nextRun": "2026-01-12T00:00:00Z",
"tenantId": 1
}
}
List Recurring Jobs
List all scheduled jobs (excludes ExecutionCode for security).
GetRecurringJobListQuery
Request:
{
"cmd": "GetRecurringJobListQuery",
"data": {
"pageIndex": 0,
"pageSize": 20,
"enabled": true,
"category": "Reports"
}
}
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
pageIndex | number | No | Page index (default: 0) |
pageSize | number | No | Page size (default: 20) |
enabled | boolean | No | Filter by enabled status |
category | string | No | Filter by job category |
Response:
{
"isSuccessful": true,
"message": "20/45 records",
"count": 45,
"pages": 3,
"size": 20,
"hasNext": true,
"data": [
{
"id": 1,
"name": "Daily EOD Report",
"category": "Reports",
"cron": "0 0 * * *",
"executionType": 0,
"executionTypeDesc": "Execute Native Command",
"command": "GenerateEODReportCommand",
"enabled": true,
"lastRun": "2026-01-11T00:00:00Z",
"lastFinished": "2026-01-11T00:05:23Z",
"nextRun": "2026-01-12T00:00:00Z",
"nextRunTooltip": "Tomorrow at 12:00 AM"
},
{
"id": 2,
"name": "Monthly Interest Posting",
"category": "Batch Jobs",
"cron": "0 0 1 * *",
"executionType": 3,
"executionTypeDesc": "Start Process",
"enabled": true,
"lastRun": "2026-01-01T00:00:00Z",
"nextRun": "2026-02-01T00:00:00Z",
"nextRunTooltip": "February 1 at 12:00 AM"
}
]
}
Note: executionCode is excluded from list for security.
Execute Job Immediately
Trigger a scheduled job to run immediately (outside schedule).
ExecuteRecurringJobCommand
Request:
{
"cmd": "ExecuteRecurringJobCommand",
"data": {
"jobId": "1"
}
}
Response:
{
"isSuccessful": true,
"message": "Job enqueued for immediate execution",
"data": {
"jobId": "1",
"status": "ENQUEUED"
}
}
Use Cases:
- Test scheduled jobs before deployment
- Force EOD processing after hours
- Manual report generation on demand
Delete Recurring Job
Delete a scheduled job.
DeleteRecurringJobCommand
Request:
{
"cmd": "DeleteRecurringJobCommand",
"data": {
"id": 1
}
}
Response:
{
"isSuccessful": true,
"message": "Recurring job deleted successfully"
}
Activate/Deactivate Job
Enable or disable a job without deleting it.
ActivateRecurringJobCommand
Request:
{
"cmd": "ActivateRecurringJobCommand",
"data": {
"id": 1
}
}
DeactivateRecurringJobCommand
Request:
{
"cmd": "DeactivateRecurringJobCommand",
"data": {
"id": 1
}
}
Re-Initialize All Jobs
Reinitialize all recurring jobs (reload from database to Hangfire).
ReInitializeAllRecurringJobCommand
Request:
{
"cmd": "ReInitializeAllRecurringJobCommand",
"data": {}
}
Response:
{
"isSuccessful": true,
"message": "All recurring jobs reinitialized successfully",
"data": {
"totalJobs": 45,
"enabledJobs": 38,
"disabledJobs": 7
}
}
Use Cases:
- After deployment or server restart
- After bulk job updates
- Sync Hangfire with database state
Error Handling
Common Errors
Job Not Found:
{
"isSuccessful": false,
"message": "recurring job with ID '99' not found."
}
Invalid Cron Expression:
{
"isSuccessful": false,
"message": "Invalid cron expression: '99 99 * * *'"
}
Job Disabled:
INFO: RecurringSupervisorJob is disabled for Id: 1
(Job won't execute but remains in database)
Missing Required Field:
{
"isSuccessful": false,
"message": "ProcessDefinitionId is required when executionType=3"
}
Complete Example: Monthly Reconciliation Process
Step 1: Create Scheduled Job
{
"cmd": "CreateRecurringJobCommand",
"data": {
"name": "Monthly Account Reconciliation",
"category": "Compliance",
"cron": "0 0 1 * *",
"executionType": 3,
"processDefinitionId": 100,
"context": "{\"month\":\"current\",\"accountCategories\":[\"DEPOSITS\",\"LOANS\"]}",
"enabled": true
}
}
Step 2: Create BPMN Reconciliation Process (ID=100)
<bpmn:process id="MonthlyReconciliationProcess">
<bpmn:startEvent id="Start"/>
<!-- Load accounts for reconciliation -->
<bpmn:scriptTask id="LoadAccounts" name="Load Accounts">
<bpmn:script>
var context = execution.getVariable('Context');
var categories = context.accountCategories;
var accounts = doQuery('GetAccountsForReconciliationQuery', {
categories: categories,
month: context.month
});
execution.setVariable('accounts', accounts);
doLog('Loaded ' + accounts.length + ' accounts for reconciliation');
</bpmn:script>
</bpmn:scriptTask>
<!-- Reconcile each account (parallel) -->
<bpmn:serviceTask id="ReconcileAccounts" name="Reconcile Accounts"
bankLingo:collection="${accounts}"
bankLingo:elementVariable="account"
isSequential="false">
<bankLingo:commandName>ReconcileAccountCommand</bankLingo:commandName>
<bankLingo:inputParameter name="accountId">${account.id}</bankLingo:inputParameter>
<bankLingo:inputParameter name="month">${Context.month}</bankLingo:inputParameter>
</bpmn:serviceTask>
<!-- Generate reconciliation report -->
<bpmn:serviceTask id="GenerateReport" name="Generate Report"
bankLingo:commandName="GenerateReconciliationReportCommand">
<bankLingo:inputParameter name="month">${Context.month}</bankLingo:inputParameter>
<bankLingo:inputParameter name="accounts">${accounts}</bankLingo:inputParameter>
</bpmn:serviceTask>
<!-- Email report to stakeholders -->
<bpmn:serviceTask id="EmailReport" name="Email Report"
bankLingo:commandName="SendEmailCommand">
<bankLingo:inputParameter name="to">compliance@bank.com</bankLingo:inputParameter>
<bankLingo:inputParameter name="subject">Monthly Reconciliation Report</bankLingo:inputParameter>
<bankLingo:inputParameter name="attachmentPath">${reportPath}</bankLingo:inputParameter>
</bpmn:serviceTask>
<!-- Error handling -->
<bpmn:boundaryEvent id="Error" attachedToRef="ReconcileAccounts">
<bpmn:errorEventDefinition/>
</bpmn:boundaryEvent>
<bpmn:serviceTask id="NotifyError" name="Notify Error"
bankLingo:commandName="SendEmailCommand">
<bankLingo:inputParameter name="to">ops@bank.com</bankLingo:inputParameter>
<bankLingo:inputParameter name="subject">Reconciliation Failed</bankLingo:inputParameter>
<bankLingo:inputParameter name="body">${errorMessage}</bankLingo:inputParameter>
</bpmn:serviceTask>
<bpmn:endEvent id="End"/>
</bpmn:process>
Step 3: Monitor Execution
{
"cmd": "GetRecurringJobByIdQuery",
"data": {
"id": 1
}
}
Check Execution Result:
{
"lastRun": "2026-01-01T00:00:00Z",
"lastFinished": "2026-01-01T00:25:15Z",
"lastExecutionResult": "{\"jobId\":\"process-job-xyz\",\"message\":\"Scheduled process dispatched successfully\"}",
"nextRun": "2026-02-01T00:00:00Z"
}
Best Practices
1. Use Descriptive Names
{
"name": "Monthly_Interest_Posting_SAVINGS_FD",
"category": "Batch Processing"
}
2. Test Before Enabling
{
"enabled": false // Test with ExecuteRecurringJobCommand first
}
3. Use Process Execution for Complex Jobs
Don't put complex logic in ExecuteCode - use StartProcess:
- ✅ Process: Multi-step, error handling, parallel execution
- ❌ Code: Simple calculations, quick checks
4. Set Appropriate Cron Schedules
// Avoid high-frequency jobs that could overload system
"cron": "*/1 * * * *" // ❌ Every minute - risky
"cron": "*/15 * * * *" // ✅ Every 15 minutes - safer
5. Monitor Execution Results
Regularly check lastExecutionResult for errors:
{
"lastExecutionResult": "{\"error\":\"Connection timeout\",\"message\":\"Failed to connect to external service\"}"
}
6. Use Categories for Organization
{
"category": "Reports", // Reports
"category": "Batch Jobs", // Bulk operations
"category": "Monitoring", // System checks
"category": "Compliance" // Regulatory tasks
}
Cron Expression Reference
Common Patterns
| Expression | Description | Next Run Example |
|---|---|---|
0 0 * * * | Daily at midnight | Jan 12, 2026 00:00 |
0 9 * * * | Daily at 9 AM | Jan 12, 2026 09:00 |
0 */6 * * * | Every 6 hours | Jan 11, 2026 12:00 |
0 0 * * 0 | Every Sunday | Jan 18, 2026 00:00 |
0 0 1 * * | First of month | Feb 1, 2026 00:00 |
0 0 1 1 * | January 1st | Jan 1, 2027 00:00 |
*/15 * * * * | Every 15 minutes | Jan 11, 2026 10:15 |
0 0 * * 1-5 | Weekdays at midnight | Jan 12, 2026 00:00 |
Cron Format
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday=0)
│ │ │ │ │
* * * * *
Online Tool: https://crontab.guru
Related Documentation
Last Updated: January 11, 2026