Workers API
The Workers API is used by the SDK's background worker to claim jobs, report results, send heartbeats, and register on startup. These endpoints form the execution side of the job lifecycle.
Base URL: https://api.zeridion.com
All endpoints require Bearer token authentication and are subject to rate limits.
Worker Lifecycle
POST /v1/workers/poll
Claim available jobs from the specified queues. The worker calls this in a loop to pick up new work.
Request
POST /v1/workers/poll
Authorization: Bearer <api_key>
Content-Type: application/json
Body
| Field | Type | Required | Description |
|---|---|---|---|
worker_id | string | Yes | Unique identifier for this worker instance |
queues | string[] | Yes | List of queue names to poll from |
capacity | integer | No | Maximum number of jobs to claim in one call (1–50). Default: 1 |
job_types | string[] | No | Only claim jobs matching these types |
Example
{
"worker_id": "wrk_local_dev_1",
"queues": ["default", "email"],
"capacity": 5,
"job_types": ["email.send", "report.generate"]
}
Response
200 OK
{
"jobs": [
{
"id": "job_01HYX3K7M8N9P2Q4R5S6T7U8V9",
"job_type": "email.send",
"payload": { "to": "user@example.com", "subject": "Welcome!" },
"attempt": 1,
"max_attempts": 5,
"timeout_seconds": 60,
"enqueued_at": "2026-03-18T15:30:00Z"
}
]
}
Job item fields
| Field | Type | Description |
|---|---|---|
id | string | Job identifier — pass to ack and heartbeat |
job_type | string | Job type for routing to the correct handler |
payload | object | JSON payload to pass to the job |
attempt | integer | Which attempt this is (1-based) |
max_attempts | integer | Maximum attempts before dead-lettering |
timeout_seconds | integer | Per-attempt timeout in seconds |
enqueued_at | string (ISO 8601) | When the job was originally enqueued |
Behavior
- Uses
SELECT ... FOR UPDATE SKIP LOCKEDfor atomic, race-free claiming across multiple worker instances. - The server holds the request for up to 30 seconds (long polling), checking for available jobs every 2 seconds. If no work is found within the deadline, the response returns an empty
jobsarray. This avoids excessive polling traffic while providing near-instant job pickup. - The SDK manages the client-side poll loop via
ZeridionFlareOptions.PollInterval(default 2 seconds between poll requests). - Claimed jobs transition from
pendingtoprocessingand have theirworker_idset.
POST /v1/workers/ack
Acknowledge job completion or failure. The worker calls this after executing a job.
Request
POST /v1/workers/ack
Authorization: Bearer <api_key>
Content-Type: application/json
Body
| Field | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | ID of the job being acknowledged |
worker_id | string | Yes | ID of the worker that processed the job |
status | string | Yes | "succeeded" or "failed" |
duration_ms | integer | No | How long execution took in milliseconds |
error | object | No | Error details when status is "failed" (see error detail) |
Error detail object
| Field | Type | Description |
|---|---|---|
type | string | Exception type name |
message | string | Error message |
stack_trace | string | Stack trace of the failure |
Success example
{
"job_id": "job_01HYX3K7M8N9P2Q4R5S6T7U8V9",
"worker_id": "wrk_local_dev_1",
"status": "succeeded",
"duration_ms": 1042
}
Failure example
{
"job_id": "job_01HYX3K7M8N9P2Q4R5S6T7U8V9",
"worker_id": "wrk_local_dev_1",
"status": "failed",
"duration_ms": 503,
"error": {
"type": "System.Net.Http.HttpRequestException",
"message": "Connection refused",
"stack_trace": " at System.Net.Http..."
}
}
Response
200 OK
{
"action": "retry",
"retry_at": "2026-03-18T15:31:00Z"
}
| Field | Type | Description |
|---|---|---|
action | string | "done" (job is complete or dead-lettered) or "retry" (will be retried) |
retry_at | string (ISO 8601) | When the next attempt is scheduled. Only present when action is "retry". |
children_activated | integer | Number of continuation jobs activated on success. Omitted from the response when no children exist (due to WhenWritingNull JSON serialization). |
Behavior
On success (status: "succeeded"):
- Job transitions to
succeeded,completed_atis set. - Any child continuation jobs in
scheduledstate are activated (moved topending). - Response:
action: "done". Thechildren_activatedfield is included with the count; if no children exist, the field is omitted entirely.
On failure with retries remaining (status: "failed", attempt < max_attempts):
- Job transitions back to
pendingwith a futurerun_atusing exponential backoff. - Backoff formula:
15 * 2^(attempt - 1)seconds (15s, 30s, 60s, 120s...) plus 0–3 seconds of random jitter. - Worker assignment is cleared so any worker can pick it up.
- Response:
action: "retry",retry_at: "...".
On failure with no retries remaining (status: "failed", attempt >= max_attempts):
- Job transitions to
dead_letter,completed_atis set. - Any child continuation jobs are cancelled.
- Response:
action: "done".
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | invalid_request | Validation failed (see ack validation) |
| 404 | job_not_found | Job does not exist |
| 409 | worker_mismatch | worker_id does not match the worker that claimed the job |
| 409 | invalid_state | Job is not in processing state (e.g., already acked) |
POST /v1/workers/heartbeat
Send a keep-alive signal for a job that is currently being processed. Heartbeats prevent the stuck-job reaper from reclaiming long-running jobs, and also deliver cancellation signals to the worker.
Request
POST /v1/workers/heartbeat
Authorization: Bearer <api_key>
Content-Type: application/json
Body
| Field | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | ID of the job being processed |
worker_id | string | Yes | ID of the worker processing the job |
{
"job_id": "job_01HYX3K7M8N9P2Q4R5S6T7U8V9",
"worker_id": "wrk_local_dev_1"
}
Response
200 OK
{ "status": "ok" }
status value | Meaning |
|---|---|
"ok" | Continue processing normally |
"cancel" | The job has been cancelled — abort execution and ack as failed |
Behavior
- The SDK sends heartbeats every
timeout_seconds / 3seconds (minimum 10 seconds). - A
StuckJobReaperServicescans for jobs whoseLastHeartbeatAtis stale beyond a grace period. Stuck jobs are retried or dead-lettered. - The
"cancel"status is reserved for future use. Currently, POST /v1/jobs/{job_id}/cancel only accepts jobs inpendingorscheduledstate and returns 409 for processing jobs.
Errors
| Status | Code | Condition |
|---|---|---|
| 404 | job_not_found | Job does not exist |
| 409 | invalid_state | Job is not in processing state |
| 409 | worker_mismatch | worker_id does not match the worker that claimed the job |
POST /v1/workers/register
Register a worker on startup. The SDK calls this once when FlareWorkerService starts. Registration logs worker metadata and optionally upserts recurring job schedules.
Request
POST /v1/workers/register
Authorization: Bearer <api_key>
Content-Type: application/json
Body
| Field | Type | Required | Description |
|---|---|---|---|
worker_id | string | Yes | Unique identifier for this worker instance |
queues | string[] | Yes | Queues this worker will poll |
job_types | string[] | Yes | Job types this worker can handle |
hostname | string | No | Machine hostname for diagnostics |
sdk_version | string | No | SDK version string |
recurring_schedules | array | No | Recurring job definitions to auto-register |
recurring_schedules items
| Field | Type | Required | Description |
|---|---|---|---|
job_type | string | Yes | Job type for the recurring job |
cron_expression | string | Yes | Cron schedule expression |
queue | string | No | Target queue |
timezone | string | No | IANA timezone |
max_attempts | integer | No | Per-execution retry limit |
timeout_seconds | integer | No | Per-execution timeout |
Example
{
"worker_id": "wrk_myhost_12345_abc",
"queues": ["default", "critical"],
"job_types": ["email.send", "report.generate"],
"hostname": "myhost",
"sdk_version": "0.1.0-beta.1",
"recurring_schedules": [
{
"job_type": "MyApp.Jobs.CleanupExpiredSessions",
"cron_expression": "0 3 * * *",
"queue": "maintenance"
}
]
}
Response
200 OK
{
"status": "registered"
}
Behavior
- Worker metadata is logged for diagnostics.
- If
recurring_schedulesis provided, each schedule is upserted as a recurring job with an ID derived from the job type (e.g.,rjob_CleanupExpiredSessions). This enables auto-registration of recurring jobs from SDK[JobConfig(CronSchedule = "...")]attributes. - If an individual recurring schedule upsert fails (e.g., invalid cron expression), the failure is logged but the registration still returns
200with"status": "registered". Check server logs if schedules are not appearing as expected.