Workers Overview
Workers are the executors of Service Tasks in your BPMN processes. When the Priostack engine reaches a Service Task, it creates a Job. Workers poll the API to activate and process jobs, then report completion (or failure) back to the engine. Your API key is all a worker needs: registration and heartbeats are optional and never consulted by activation.
The Polling Pattern
Priostack uses an outbound job worker pattern: workers actively poll the engine for available jobs. This design means workers can run anywhere - behind firewalls, on-premise, or in any cloud - without requiring inbound connectivity to the engine.
Worker Priostack Engine · POST /api/v1/jobs/activate · { "type": "payment-processor", · "maxJobsToActivate": 5 } · ---------------------------------> · (answers at once; {"jobs": []} when nothing waits) · HTTP 200 { "jobs": [...] } · <--------------------------------- · [Worker processes the job] · POST /api/v1/jobs/{key}/complete · { "variables": { "result": ... } } · ---------------------------------> · HTTP 204 No Content · <--------------------------------- · [Token advances in process] |
{"jobs": []} when nothing waits. Sleep 1 to 5 seconds after an empty or failed poll. A tight loop goes over the limit of 300 requests per minute per IP, gets 429, and three strikes ban the IP for 2 hours (see Rate Limits).
Job Activation
POST /api/v1/jobs/activate
Content-Type: application/json
X-API-Key: your_key
{
"type": "payment-processor",
"maxJobsToActivate": 10
}
| Field | Type | Description |
|---|---|---|
type | string | Always send it. Matches the zeebe:taskDefinition type of the service task, case-sensitive. Without type or jobType, the call activates waiting jobs of every type you own, including user tasks and message waits. |
jobType | string | Accepted alias of type, used only when type is empty. Prefer type. |
maxJobsToActivate | integer | Optional, with no server default and no upper bound. Omitted, 0 or negative activates every waiting job of that type in one response; a positive value caps the batch. Always send an explicit value. |
worker | string | Accepted and ignored (not stored, not echoed). |
timeout | integer (ms) | Accepted and ignored. It sets neither a lock nor the deadline. |
Every other field is ignored, including fetchVariables and requestTimeout. The API key goes in X-API-Key, in Authorization: Bearer <key>, or as ?api_key=. The body is read before the key is checked: an empty or invalid body answers 400 {"error":"invalid json"}, and a missing key answers 401 {"error":"unauthorized"}. Activation only hands out jobs of your own instances and cases.
Activation Response
{
"jobs": [
{
"key": "a99f579d62ae71f8764f",
"type": "payment-processor",
"processInstanceKey": "run_1ce49bbc5260b39423bd",
"processDefinitionKey": "7f3c2a91b04e5d6c8a12",
"variables": {
"orderId": "ORD-001",
"amount": 149.99,
"currency": "EUR"
},
"retries": 3,
"deadline": 1746456780000,
"createdAt": "0001-01-01T00:00:00Z"
}
]
}
| Field | Type | Description |
|---|---|---|
key | string | The job key, 20 lowercase hex characters. Use it in the complete and fail paths. |
type | string | The job type. |
processInstanceKey | string | The instance (or case) the job belongs to, currently run_ plus 20 hex characters. Older instances may carry a bare hex key, so never parse the format. |
processDefinitionKey | string | 20 hex characters; empty for CMMN case jobs. |
variables | object | All input variables of the job, unfiltered. |
retries | integer | Attempts left. See Job Policies. |
deadline | integer | Unix epoch milliseconds: activation time + 300 000 (5 minutes). Informational only: nothing happens when it passes. |
createdAt | string | Always "0001-01-01T00:00:00Z" in this response. |
Every key is an opaque JSON string, never a number: a Go client must declare them as string, and an int64 field makes the whole response fail to decode. The job object has no bpmnProcessId, elementId or customHeaders field. Business identifiers such as orderId are variables, not keys.
Completing a Job
POST /api/v1/jobs/{key}/complete
Content-Type: application/json
X-API-Key: your_key
{
"variables": {
"transactionId": "txn_abc123",
"paymentStatus": "success"
}
}
Success is 204 No Content with an empty body. variables is optional and an empty body is allowed. Completing a service job costs no credits.
Failing a Job
POST /api/v1/jobs/{key}/fail
Content-Type: application/json
X-API-Key: your_key
{
"retries": 2,
"errorMessage": "Payment gateway returned 503"
}
Success is 204 No Content. retries is the absolute number of attempts left, stored as sent: the server does not decrement it. Send job.retries - 1 to use up one attempt, or job.retries unchanged to hand the job back without using one. Above 0 the job can be activated again immediately, with no delay. At 0 or below the job is removed, the instance state becomes INCIDENT, and an incident record (error_code JOB_FAILED) appears in GET /api/v1/incidents. An omitted retries counts as 0, so a body with only errorMessage raises an incident at once.
Errors on Complete and Fail
| Status | Body | When |
|---|---|---|
401 | {"error":"unauthorized"} | No API key. |
400 | {"error":"invalid json"} | The body is not valid JSON. |
404 | {"error":"job not found"} | The key is unknown, already completed, already failed to zero retries, belongs to a cancelled instance, or belongs to another account. These cases are deliberately indistinguishable. |
404 | {"error":"<engine message>"} | The engine refuses the call, for example on a job the runtime owns (a timer). |
A repeated completion answers 404, never 409, and there is no idempotency key. If a response is lost and the retried call gets 404, the first call most likely went through: read GET /api/v1/process-instances/{processInstanceKey} to reconcile.
Business Errors
The only actions under /api/v1/jobs/{key}/ are complete and fail. A worker cannot throw a BPMN error or change retries over REST: /error, /throw and other actions answer 404 {"error":"unknown action"}. To route a business outcome such as a declined card, complete the job with an outcome variable and branch on it with an exclusive gateway after the task:
POST /api/v1/jobs/{key}/complete
Content-Type: application/json
X-API-Key: your_key
{
"variables": { "paymentStatus": "declined", "declineReason": "insufficient_funds" }
}
Variable Passing
- Input: every job carries all of its input variables. There is no server-side filter; read the ones your worker needs.
- Output: Variables in the complete request are merged into the process instance. When the task declares a
zeebe:ioMappingoutput mapping, only the names it declares are merged.
Concurrency
- Run multiple goroutines/threads each polling independently
- An activated job leaves the waiting queue, so no other activation receives it while it is active
- Set
maxJobsToActivatebased on your processing capacity, and always send it - There is no lock expiry: the
deadlinefield is informational. A job stays active until you complete or fail it, its instance is cancelled, or the server restarts. To bound work in time, set a timeout in your worker and model a timer boundary event on the task - On shutdown, stop polling, wait for in-flight jobs with a bounded grace period, then fail each unfinished job with
retriesequal tojob.retries. That re-queues it without using an attempt. A worker that exits holding jobs strands them until a server restart
key. A job comes back to the queue when a worker fails it with retries above 0, and when the server restarts (every outstanding job is restored to waiting with the same key and retries), so a handler can see the same key twice.
Worker API Reference
Every call a worker makes, with its parameters and a worked example. This was a separate page at /docs-workers until the two were merged; the sections above are the same endpoints explained in order of use. The first two calls are optional: nothing in activation, job handling or incidents reads them.
Records a worker in your account's worker list. It is informational only: activation does not need it and never reads it.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
topics | string[] | required | The job types this worker handles. Must not be empty, or the call answers 400 {"error":"topics must not be empty"}. |
group_name | string | optional | A label to group workers. |
Example request
{
"topics": ["payment:charge", "payment:refund"],
"group_name": "payments"
}
Example response 201 Created
{
"id": "wkr-3f9a0c1e7b2d4a6f8e1c5b7d9a2f4e6c",
"api_key": "ps_...",
"topics": ["payment:charge", "payment:refund"],
"last_heartbeat": "2026-04-06T09:00:00Z",
"registered_at": "2026-04-06T09:00:00Z",
"active": true,
"group_name": "payments"
}
The response currently echoes your full API key in api_key; do not log it.
A GET with the worker id as a query parameter and no body. A worker reads as active while its last heartbeat is under 90 seconds old (checked every 60 seconds). Missed heartbeats release nothing: jobs stay with whoever activated them.
Example response 200 OK
{ "status": "ok", "worker_id": "wkr-3f9a0c1e7b2d4a6f8e1c5b7d9a2f4e6c" }
An unknown id, or an id registered under another API key, answers 404.
Activates and returns waiting jobs of the given type from your own instances and cases, up to maxJobsToActivate. It answers at once. An activated job is not handed out again while it is active, and there is no lock timeout.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
type | string | required | Task definition type to activate. Without it, jobs of every type are activated. |
jobType | string | optional | Alias of type, used only when type is empty. |
maxJobsToActivate | integer | optional | No default and no maximum: omitted, 0 or negative returns every waiting job of that type. Always send a value. |
worker | string | optional | Accepted and ignored. |
timeout | integer | optional | Accepted and ignored. |
Example request
{
"type": "payment:charge",
"maxJobsToActivate": 5
}
Example response 200 OK
{
"jobs": [
{
"key": "a99f579d62ae71f8764f",
"type": "payment:charge",
"processInstanceKey": "run_1ce49bbc5260b39423bd",
"processDefinitionKey": "7f3c2a91b04e5d6c8a12",
"variables": { "orderId": "ORD-42", "amount": 9900, "currency": "EUR" },
"retries": 3,
"deadline": 1775466060000,
"createdAt": "0001-01-01T00:00:00Z"
}
]
}
Marks a job as successfully completed. Optionally merges output variables back into the process instance. The process engine resumes from the next step.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
variables | object | optional | Output variables to merge into the process instance scope. |
Example request
{ "variables": { "chargeId": "ch_stripe_abc", "status": "succeeded" } }
Response 204 No Content
404 {"error":"job not found"} when the job is unknown, already completed or no longer yours.
Marks a job as failed. retries is stored as the number of attempts left. Above 0 the job is re-queued at once; at 0 or below the instance becomes INCIDENT and an incident record is created.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
retries | integer | required | Attempts left, stored as sent (not decremented by the server). Omitted counts as 0, which raises an incident immediately. |
errorMessage | string | optional | Human-readable failure reason, shown in the incident record. |
Example request
{ "retries": 2, "errorMessage": "Stripe timeout" }
Response 204 No Content
Returns the workers registered with your API key.
Example response 200 OK
{
"items": [
{
"id": "wkr-3f9a0c1e7b2d4a6f8e1c5b7d9a2f4e6c",
"api_key": "ps_...",
"topics": ["payment:charge"],
"last_heartbeat": "2026-04-06T09:00:55Z",
"registered_at": "2026-04-06T09:00:00Z",
"active": true,
"group_name": "payments"
}
],
"total": 1
}
Language-Specific Guides
- Go Worker - Polling loop that waits for in-flight jobs on shutdown
- Python Worker - Requests-based implementation
- JavaScript Worker - Node.js fetch-based implementation