Skip to main content

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:

FieldTypeRequiredDescription
namestringYesJob name (unique identifier)
categorystringNoJob category for grouping
cronstringYesCron expression (e.g., "0 0 * * *")
executionTypenumberYes0=Command, 1=CommandChain, 2=Code, 3=Process
commandstringConditionalRequired if executionType=0 or 1
executionCodestringConditionalRequired if executionType=2
processDefinitionIdnumberConditionalRequired if executionType=3
contextstringNoJSON string with execution context/parameters
contextLoaderstringNoContext loader function name
enabledbooleanNoEnable/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:

ExpressionDescription
0 0 * * *Every day at midnight
0 */6 * * *Every 6 hours
0 0 1 * *First day of every month
0 0 * * 0Every 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:

  1. Every day at 1 AM, Hangfire triggers the job
  2. Executes GenerateTransactionReportCommand with context data
  3. Command generates report and emails recipients
  4. 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:

  1. Every 2 hours, Hangfire triggers the job
  2. Executes JavaScript code expression
  3. Code queries low balance accounts and sends alert
  4. 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:

  1. First day of every month at midnight, Hangfire triggers
  2. Starts BPMN process (ID=15) with context data
  3. Process variables include:
    Context = {
    accountTypes: ["SAVINGS", "FIXED_DEPOSIT"],
    month: "current"
    }
  4. 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:

FieldTypeRequiredDescription
pageIndexnumberNoPage index (default: 0)
pageSizenumberNoPage size (default: 20)
enabledbooleanNoFilter by enabled status
categorystringNoFilter 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​

ExpressionDescriptionNext Run Example
0 0 * * *Daily at midnightJan 12, 2026 00:00
0 9 * * *Daily at 9 AMJan 12, 2026 09:00
0 */6 * * *Every 6 hoursJan 11, 2026 12:00
0 0 * * 0Every SundayJan 18, 2026 00:00
0 0 1 * *First of monthFeb 1, 2026 00:00
0 0 1 1 *January 1stJan 1, 2027 00:00
*/15 * * * *Every 15 minutesJan 11, 2026 10:15
0 0 * * 1-5Weekdays at midnightJan 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



Last Updated: January 11, 2026