API Reference
Job Queue Admin exposes REST API endpoints for external integration. These APIs enable Azure Functions middleware, custom automation, and third-party integrations.
API Overview
Available APIs
| API | Purpose | Use Case |
|---|---|---|
| Callback API | Handle interactive actions | Teams/Slack button clicks |
| Pending Notifications API | Layer 3 Azure polling | Backup notification delivery |
Authentication
All API endpoints use Business Central's standard authentication:
-
OAuth 2.0: For Azure AD authenticated clients
-
Service-to-Service (S2S): For Azure Functions middleware
-
Basic Auth: For development/testing only
Callback API
Overview
The Callback API handles interactive actions triggered from notification cards (Teams/Slack buttons). When a user clicks "Restart" or "Put On Hold" in a notification, the Azure Functions middleware calls this API.
Base URL
/api/jemel/jobQueueMonitor/v1.0/companies({companyId})/jqaCallbacks
Endpoints
Get Job Queue Entry
GET /jqaCallbacks({systemId})
Returns job queue entry details for displaying in notification cards.
Response Fields:
| Field | Type | Description |
|---|---|---|
| id | GUID | System ID of job queue entry |
| description | Text | Job description |
| status | Option | Current status (Ready, In Process, Error, On Hold) |
| objectTypeToRun | Option | Codeunit or Report |
| objectIdToRun | Integer | Object ID to execute |
| noOfAttemptsToRun | Integer | Current attempt count |
| maximumNoOfAttemptsToRun | Integer | Maximum allowed attempts |
| earliestStartDateTime | DateTime | Scheduled start time |
| errorMessage | Text | Last error message |
| jobQueueCategoryCode | Code | Job category |
Restart Job
POST /jqaCallbacks({systemId})/restart
Restarts a failed or on-hold job queue entry.
Behavior:
-
Sets status to Ready
-
Sets Earliest Start Date/Time to current time
-
Returns error if job is currently running
Response:
| Field | Value |
|---|---|
| actionResult | "Success" or "Error" |
| actionMessage | Descriptive message |
Put On Hold
POST /jqaCallbacks({systemId})/putOnHold
Puts a job queue entry on hold.
Behavior:
-
Sets status to On Hold
-
Prevents auto-restart rules from restarting
-
Returns error if job is currently running
Response:
| Field | Value |
|---|---|
| actionResult | "Success" or "Error" |
| actionMessage | Descriptive message |
Set to Ready
POST /jqaCallbacks({systemId})/setToReady
Sets a job queue entry to Ready status without immediate restart.
Response:
| Field | Value |
|---|---|
| actionResult | "Success" or "Error" |
| actionMessage | Descriptive message |
Restart All Failed
POST /jqaCallbacks/restartAllFailed
Bulk restart all job queue entries in Error status.
Response:
| Field | Value |
|---|---|
| actionResult | "Success" |
| actionMessage | "X job(s) restarted" |
Resume All On Hold
POST /jqaCallbacks/resumeAllOnHold
Bulk resume all job queue entries in On Hold status.
Response:
| Field | Value |
|---|---|
| actionResult | "Success" |
| actionMessage | "X job(s) resumed" |
Pending Notifications API
Overview
The Pending Notifications API supports Layer 3 (Azure Polling) notification delivery. An Azure Functions timer trigger polls this API to find and process stale notifications that Layer 1 and Layer 2 failed to deliver.
Base URL
/api/jemel/jobQueueMonitor/v1.0/companies({companyId})/jqaPendingNotifications
Endpoints
Get Pending Notifications
GET /jqaPendingNotifications
Returns notifications with Pending or Failed status.
Response Fields:
| Field | Type | Description |
|---|---|---|
| entryNo | Integer | Queue entry number |
| jobQueueEntryId | GUID | Source job queue entry ID |
| channelCode | Code | Target notification channel |
| channelType | Enum | Email, Teams, or Slack |
| status | Enum | Pending or Failed |
| triggerEvent | Enum | OnError, OnAutoRestart, OnCooldown |
| createdAt | DateTime | When notification was queued |
| attemptCount | Integer | Number of send attempts |
| jobDescription | Text | Job description snapshot |
| jobErrorMessage | Text | Error message snapshot |
| companyName | Text | Source company |
| deepLink | Text | URL to job in BC |
| channelTeamsWorkflowUrl | Text | Teams webhook URL |
| channelSlackBotToken | Text | Slack bot token |
| channelSlackChannelId | Text | Slack channel ID |
| channelEmailAddress | Text | Email recipient |
Get Stale Notifications
POST /jqaPendingNotifications/getStaleNotifications
Marks notifications older than the stale threshold as "picked up by Azure". Prevents duplicate processing.
Behavior:
-
Finds notifications older than configured threshold (default 5 minutes)
-
Sets "Picked Up By Azure" flag to true
-
Records pickup timestamp
Use Case: Call this at the start of Azure polling cycle to claim notifications.
Mark as Sent
POST /jqaPendingNotifications({entryNo})/markAsSent
Marks a notification as successfully sent by Azure polling.
Behavior:
-
Creates log entry with Sent status
-
Sets delivery layer to "Azure Polling"
-
Deletes queue entry
-
Records success in telemetry
Mark as Failed
POST /jqaPendingNotifications({entryNo})/markAsFailed
Marks a notification as failed after Azure polling attempt.
Behavior:
-
Creates log entry with Failed status
-
Updates queue entry with error message
-
Does not delete queue entry (may retry)
-
Records failure in telemetry
Send Notification
POST /jqaPendingNotifications({entryNo})/sendNotification
Sends a notification through BC using the configured channel handler. This is the only way to send Email notifications from Azure polling, as Email requires BC's Email Account (SMTP credentials stored in BC).
Behavior:
-
Retrieves channel configuration
-
Uses appropriate channel handler (Email, Teams, or Slack)
-
For Email: Sends via BC Email Account system
-
For Teams/Slack: Sends via channel's webhook/API
-
On success: Creates log entry, deletes queue entry
-
On failure: Marks queue entry as failed with error
Use Case: Azure middleware should call this for Email channel notifications instead of trying to send directly (middleware has no SMTP access).
Response:
| Outcome | Result Code | Queue Entry |
|---|---|---|
| Success | Deleted | Removed |
| Failure | Updated | Marked as Failed |
Azure Functions Integration
Typical Polling Flow (Layer 3 - Safety Net)
1. Timer triggers Azure Function (every 30 minutes) 2. Function calls GET /jqaPendingNotifications with filter:
- Created At < (now - 15 minutes) // Stale threshold
- Picked Up By Azure = false
- For each notification:
a. Call POST /getStaleNotifications to claim
b. Based on channel type:
- Email: Call POST /sendNotification (BC sends via Email Account)
- Teams: Middleware sends to webhook, then POST /markAsSent
- Slack: Middleware sends via Bot API, then POST /markAsSent c. On error: Call POST /markAsFailed
Important: Email notifications must use /sendNotification because the middleware cannot send email directly (BC's SMTP credentials are not accessible externally).
Note: Layer 3 is a safety net. Most notifications (90%+) are delivered by Layer 1 (immediate) or Layer 2 (BC job queue) before Layer 3 polling runs.
Sample Azure Function (TypeScript)
import { AzureFunction, Context } from '@azure/functions'; import { BCApiClient } from '../shared/bcApiClient';
const pollPendingNotifications: AzureFunction = async function (
context: Context
): Promise
// Get stale notifications
const notifications = await bcClient.get('/jqaPendingNotifications', {
$filter: "pickedUpByAzure eq false"
});
for (const ntf of notifications.value) {
try {
// Mark as picked up
await bcClient.post(
\`/jqaPendingNotifications(${ntf.entryNo})/getStaleNotifications\`
);
// Send notification based on channel type
if (ntf.channelType === 'Email') {
// Email: Delegate to BC (BC has SMTP credentials)
await bcClient.post(
\`/jqaPendingNotifications(${ntf.entryNo})/sendNotification\`
);
// sendNotification handles marking as sent/failed internally
} else if (ntf.channelType === 'Teams') {
await sendTeamsNotification(ntf);
await bcClient.post(
\`/jqaPendingNotifications(${ntf.entryNo})/markAsSent\`
);
} else if (ntf.channelType === 'Slack') {
await sendSlackNotification(ntf);
await bcClient.post(
\`/jqaPendingNotifications(${ntf.entryNo})/markAsSent\`
);
}
} catch (error) {
// Mark as failed (only for Teams/Slack - Email handled by sendNotification)
if (ntf.channelType !== 'Email') {
await bcClient.post(
\`/jqaPendingNotifications(${ntf.entryNo})/markAsFailed\`
);
}
}
}
};
export default pollPendingNotifications;
Configuration
Stale Notification Threshold
Configure when notifications are considered stale:
-
Open Job Queue Admin Setup
-
Set Stale After (Minutes) field (default: 5)
-
Notifications older than this are eligible for Azure polling
Layer 3 Toggle
Enable or disable Azure polling:
-
Open Job Queue Admin Setup
-
Toggle Azure Polling Enabled (Layer 3)
-
When disabled, Layer 3 API calls are still accepted but not used
Error Handling
HTTP Status Codes
| Code | Meaning | Action |
|---|---|---|
| 200 | Success | Continue |
| 400 | Bad Request | Check request format |
| 401 | Unauthorized | Verify authentication |
| 403 | Forbidden | Check permissions |
| 404 | Not Found | Entry doesn't exist |
| 500 | Server Error | Retry with backoff |
Common Errors
| Error | Cause | Solution |
|---|---|---|
| "Cannot restart job that is currently running" | Job in process | Wait for completion |
| "Entry not found" | Already processed | Skip to next |
| "Unauthorized" | Invalid token | Refresh authentication |
Security Considerations
API Access Control
-
APIs require BC authentication
-
Use service-to-service (S2S) for Azure Functions
-
Limit API access to specific IP ranges if possible
Data Exposure
-
Channel credentials (tokens, URLs) are included in API responses
-
Use HTTPS only
-
Consider additional encryption for sensitive channels
Audit Trail
-
All API actions are logged in Notification Log
-
Telemetry tracks API usage
-
Action source marked as "API"
Next Steps: Review Notification Queue & Log for monitoring API activity, or see Azure Enterprise App Setup for S2S authentication configuration.