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