Enqueuing Jobs
Note
These endpoints are available in both
application/jsonandapplication/msgpackformats.
Jobs are pushed to the queue by your application so that workers can process
them asynchronously. Jobs can be scheduled for a future date by specifying a
ready_at timestamp in the future, or by default jobs will be ready for
processing immediately.
There are two endpoints for enqueueing jobs: single enqueue, or bulk enqueue. Both take jobs inputs in the exact same shape. The server responds with the job(s) and their generated IDs.
Common Job Parameters
Both endpoints accept and return the same structure, except the bulk enqueue
endpoint wraps an array of {"jobs": [...]}.
| Field | Description |
|---|---|
queue requiredstring |
Arbitrary queue name to which the job is assigned. Must be
valid UTF-8 and must not contain any of the follow reserved
characters: ,, *, ?,
[, ], {, },
\.
|
type requiredstring |
Job type known to your application. Must be valid UTF-8 and
must not contain any of the follow reserved characters:
,, *, ?, [,
], {, }, \.
|
priorityint16 |
Optional numeric priority for the job. Lower values are
processed first (higher priority). The default value is
32768.
|
ready_atint64 |
If the client wishes to schedule this job for a future time, this field is set to the timestamp at which the job is ready for processing. |
payload requiredobject |
Any JSON-serializable type to be processed by your application |
unique_keystring |
Optional unique key for this job, which is used to protect
against duplicate job enqueues. This is paired with the
optional unique_while field which defines the
scope within which the job is considered unique. Uniqueness is
status-bound, not time-bound. There is no arbitrary expiry.
Conflicting enqueues do not produce errors, but
instead behave idempotently. A success response is returned
with details of the existing matching job, and its
duplicate field set to true. This key
is intentionally global across all queues and job types.
Clients should prefix it as necessary.
Requires a pro license.
|
unique_whilestring |
When the job has a unique key, specifies the scope within which
that job is considered unique. One of:
queued.
|
backoffobject |
Optional backoff policy which overrides the server's default
policy. All fields are required. Zizq computes the backoff delay as
base_ms +
(attempts^exponent) +
(rand(0.0..jitter_ms)*attempts)
.
The jitter_ms mitigates retry flooding when failures
occur clustered together.
|
backoff.base_msint32 |
The minimum delay in milliseconds between job retries. |
backoff.exponentfloat |
A multiplier applied to the number of attempts on each retry,
used as pow(attempts, exponent) to produce an
increasing delay in milliseconds.
|
backoff.jitter_msint32 |
A random delay added onto each attempt. Multiplied by the total
number of attempts, such as attempts * rand(0..jitter).
Prevents retries clutering together.
|
retry_limitint32 |
Overrides the severs default retry limit for this job. Once
this limit is reached, the server marks the job dead.
|
retentionobject |
Optional retention policy for dead and
completed jobs which overrides the server's
default policy. All fields are optional.
|
retention.dead_msint64 |
The number of milliseconds for which to retain
dead jobs after all retries have been exhausted.
When not set, the server's default value (7 days) applies. When
set to zero, jobs are purged as soon as all retries have been
exhausted.
|
retention.completed_msint64 |
The number of milliseconds for which to retain
completed jobs after successful processing. When
not set, the server's default value (zero) applies. When set to
zero, jobs are purged immediately upon completion.
|
batchobject |
Optional batched-job configuration. When present, subsequent
enqueues sharing the same batch.key are
folded into this job's pending payload via the
when and fold jq expressions,
rather than creating separate pending jobs. See the
Batched jobs section below for
the full semantics. All three inner fields are required
when this field is set. Mutually exclusive with
unique_key — supplying both returns
400 Bad Request.
Requires a pro license.
|
batch.keystring |
Identifies the batch. Only one unsealed batched job exists per key at a time. Enqueues sharing this key fold into the existing pending job (or start a new one if none exists or the existing batch was already sealed). |
batch.whenstring |
jq predicate that
decides whether an incoming enqueue folds into the existing
pending job. Evaluated with $existing bound to
the current pending payload and $new bound to
the incoming payload. Truthy means fold; falsy seals the
existing batch and starts a fresh one from the incoming
enqueue. Invalid jq syntax, or an expression that returns
multiple outputs, returns 422 Unprocessable Entity.
|
batch.foldstring |
jq expression that
produces the merged payload when a fold occurs. Runs with the
same $existing and $new bindings as
when. Must produce exactly one output; multiple
outputs return 422 Unprocessable Entity.
|
budgetsarray |
Array of budget bindings used to control concurrency and/or rate limiting of dispatched jobs. Requires a pro license. |
budgets[*].key requiredstring |
The identifier for the budget. Must be valid UTF-8 and must
not contain any of the follow reserved
characters: ,, *, ?,
[, ], {, },
\.
|
budgets[*].costint32 |
The number of tokens this job takes from the budget. Defaults to 1. |
budgets[*].create_withobject |
Specification from which to create this budget atomically with the job if it does not already exist. Without this, the budget must exist or a 422 response will be returned. Does not overwrite any existing budget. |
budgets[*].create_with.allocation requiredint32 |
The total number of tokens available in this budget's pool for
use by its configured strategy. No jobs can exist bound to this
budget with a cost that exceeds the allocation.
|
budgets[*].create_with.strategy requiredobject |
Details of the specific strategy that is used to manage the tokens available under this budget. |
budgets[*].create_with.strategy.type requiredstring |
Names the strategy used to manage the tokens within the budget.
One of:
|
budgets[*].create_with.strategy.duration_msint64 |
Required for time_based strategies. Invalid for
while_in_flight. Specifies the period of time in
milliseconds over which a time_based rate limit is
measured. For example, for an allocation of 1000 and a
duration_ms of 60000, the rate limit is
1000/minute.
|
budgets[*].create_with.strategy.burstint32 |
The maximum number of tokens that may be accumulated at once
for a time_based budget. Defaults to whatever the
configured allocation is. So for a
1000/hour rate limit, the budget would technically
permit a short burst of 1000 jobs if no other jobs have used
tokens from the budget for a whole hour. Setting a burst of 1
means tokens cannot accumulate and jobs are always paced
according to the configured rate limit. It is also possible to
intentionally set a burst higher than the configured
allocation, such as a burst of 2000 for a
1000/hour allocation. In this case if the budget
has been idle for 2 hours, it would permit a sudden burst of
2000 jobs at any moment. No jobs can exist bound to this
budget with a cost that exceeds the burst.
|
Common Job Response
Both endpoints accept and return the same structure, except the bulk enqueue
endpoint wraps an array of {"jobs": [...]}.
| Field | Description |
|---|---|
id requiredstring |
Unique time-sequenced job ID assigned by the server. |
queue requiredstring |
Arbitrary queue name to which the job is assigned |
type requiredstring |
Job type known to your application |
priority requiredint16 |
Numeric priority for the job. Lower values are processed first
(higher priority). The default value is 32768.
|
status requiredstring |
The job status on the server. One of:
|
unique_keystring |
Optional unique key for this job, which is used to protect
against duplicate job enqueues. This is paired with the
optional unique_while field which defines the
scope within which the job is considered unique.
|
unique_whilestring |
When the job has a unique key, specifies the scope within which
that job is considered unique. One of:
queued.
|
duplicate requiredboolean |
Only returned on enqueue responses. Set to true if
this job was a duplicate enqueue of an existing job according
to its unique_key and unique_while
scope.
|
folded requiredboolean |
Only returned on enqueue responses. Set to true
if this enqueue was folded into an existing pending batched
job (see the batch object on the request and
the Batched jobs
section). Folded responses use HTTP 200 OK
rather than 201 Created.
|
batchobject |
The batched-job configuration attached at enqueue time, if
any. Echoed back on every job-fetch response so callers can
observe the exact when / fold
expressions the server is evaluating on subsequent folds
(only the first enqueue's config applies for the life of
the batch). See the enqueue-request table for the field
shape.
|
ready_at requiredint64 |
The timestamp at which this job is ready to be dequeued by workers. |
attempts requiredint32 |
The number of times this job has been previously attempted (starts at zero). |
backoffobject |
Optional backoff policy which overrides the server's default
policy. All fields are required. Zizq computes the backoff delay as
base_ms +
(attempts^exponent) +
(rand(0.0..jitter_ms)*attempts)
.
The jitter_ms mitigates retry flooding when failures
occur clustered together.
|
backoff.base_msint32 |
The minimum delay in milliseconds between job retries. |
backoff.exponentfloat |
A multiplier applied to the number of attempts on each retry,
used as pow(attempts, exponent) to produce an
increasing delay in milliseconds.
|
backoff.jitter_msint32 |
A random delay added onto each attempt. Multiplied by the total
number of attempts, such as attempts * rand(0..jitter).
Prevents retries clutering together.
|
retry_limitint32 |
Overrides the severs default retry limit for this job. Once
this limit is reached, the server marks the job dead.
|
retentionobject |
Optional retention policy for dead and
completed jobs which overrides the server's
default policy. All fields are optional.
|
retention.dead_msint64 |
The number of milliseconds for which to retain
dead jobs after all retries have been exhausted.
When not set, the server's default value (7 days) applies. When
set to zero, jobs are purged as soon as all retries have been
exhausted.
|
retention.completed_msint64 |
The number of milliseconds for which to retain
completed jobs after successful processing. When
not set, the server's default value (zero) applies. When set to
zero, jobs are purged immediately upon completion.
|
budgetsarray |
Array of budget bindings used to control concurrency and/or rate limiting of dispatched jobs. |
budgets[*].key requiredstring |
The identifier for the budget. |
budgets[*].costint32required |
The number of tokens this job takes from the budget. |
POST /jobs
Enqueues a single job.
Request Body
Responses
200 OK
The request was processed but the specified job was a duplicate of an existing
job according to its unique_key and unique_while scope. The returned data
is that of the existing job, and the duplicate flag is set to true.
See Common Job Response.
201 Created
The request was processed and a new job has been enqueued.
See Common Job Response.
400 Bad Request
Returned when given invalid inputs.
| Field | Description |
|---|---|
error requiredstring |
A description of the error. |
403 Forbidden
Returned when the client attempts to use pro features but the server is not configured with a pro license.
| Field | Description |
|---|---|
error requiredstring |
A description of the error. |
POST /jobs/bulk
Enqueues multiple jobs atomically.
Request Body
| Field | Description |
|---|---|
jobs requiredarray |
Array of jobs in the same shape as for a single enqueue request. |
Responses
200 OK
The request was processed but all the specified jobs were duplicates of
existing jobs according to their unique_key and unique_while scopes. The
returned data is that of the existing jobs, and their duplicate flags are set
to true.
See Common Job Response.
201 Created
The request was processed and new jobs have been enqueued. Where unique_key
values were present, any duplicates are identified by their duplicate flags.
| Field | Description |
|---|---|
jobs requiredarray |
Array of jobs in the same shape as for a single enqueue response, and in the same order as the input request. |
400 Bad Request
Returned when given invalid inputs.
| Field | Description |
|---|---|
error requiredstring |
A description of the error. |
403 Forbidden
Returned when the client attempts to use pro features but the server is not configured with a pro license.
| Field | Description |
|---|---|
error requiredstring |
A description of the error. |
Examples
Enqueue a single job
Request:
http POST http://127.0.0.1:7890/jobs --raw '{ "queue": "example", "priority": 500, "type": "hello_world", "payload": {"greet": "World"} }'
Response:
HTTP/1.1 201 Created content-length: 143 content-type: application/json date: Fri, 13 Mar 2026 08:53:47 GMT{ "attempts": 0, "id": "03fr1jkpcsipbsckqj0y6pgr7", "priority": 500, "queue": "example", "ready_at": 1773392027425, "status": "ready", "type": "hello_world" }
Enqueue a scheduled Job
Jobs are explicitly scheduled by providing a ready_at timestamp with a future
dated value.
Request:
http POST http://127.0.0.1:7890/jobs --raw '{ "queue": "example", "priority": 500, "type": "hello_world", "payload": {"greet": "Later"}, "ready_at": 1773396035647 }'
Response:
HTTP/1.1 201 Created content-length: 147 content-type: application/json date: Fri, 13 Mar 2026 09:01:08 GMT{ "attempts": 0, "id": "03fr1l0cl1quc0sfe6y2711op", "priority": 500, "queue": "example", "ready_at": 1773396035647, "status": "scheduled", "type": "hello_world" }
Enqueue jobs with unique keys
Unique jobs require a pro license.
First Request:
http POST http://127.0.0.1:7890/jobs --raw '{ "queue": "example", "priority": 500, "type": "hello_world", "unique_key": "hello_world:world", "payload": {"greet": "World"} }'
First Response:
HTTP/1.1 201 Created content-length: 218 content-type: application/json date: Mon, 23 Mar 2026 11:19:58 GMT{ "attempts": 0, "duplicate": false, "id": "03ft8h3ubrx53abhw1fxbora3", "priority": 500, "queue": "example", "ready_at": 1774264798519, "status": "ready", "type": "hello_world", "unique_key": "hello_world:world", "unique_while": "queued" }
Subsequent Request:
http POST http://127.0.0.1:7890/jobs --raw '{ "queue": "example", "priority": 500, "type": "hello_world", "unique_key": "hello_world:world", "payload": {"greet": "World"} }'
Subsequent Response:
HTTP/1.1 200 OK content-length: 217 content-type: application/json date: Mon, 23 Mar 2026 11:20:26 GMT{ "attempts": 0, "duplicate": true, "id": "03ft8h3ubrx53abhw1fxbora3", "priority": 500, "queue": "example", "ready_at": 1774264798519, "status": "ready", "type": "hello_world", "unique_key": "hello_world:world", "unique_while": "queued" }
Enqueue a job against multiple budgets
Budgets are used for rate limiting and concurrency control. In this example budgets have already been created and the newly enqueued job is bound to those budgets.
Request:
http POST http://127.0.0.1:7890/jobs --raw '{ "type": "example", "queue": "example", "payload": {}, "budgets": [ { "key": "schema-bot", "cost": 2 }, {"key": "image-service"} ] }'
Response:
HTTP/1.1 201 Created content-length: 249 content-type: application/json date: Mon, 31 Aug 2026 22:49:49 GMT{ "attempts": 0, "budgets": [ { "cost": 2, "key": "schema-bot" }, { "cost": 1, "key": "image-service" } ], "duplicate": false, "folded": false, "id": "03gsa8rjaoxgqgnrsnlt7niqb", "priority": 32768, "queue": "example", "ready_at": 1788216589726, "status": "ready", "type": "example" }
Enqueue a job against budgets using create_with
In this example, the budget for a job need not be created ahead of time. If it does not exist, Zizq will create it atomically with the job.
Request:
http POST http://127.0.0.1:7890/jobs --raw '{ "type": "example", "queue": "example", "payload": {}, "budgets": [ { "key": "cpu-intensive", "create_with": { "allocation": 100, "strategy": { "type": "while_in_flight" } } } ] }'
Response:
HTTP/1.1 201 Created content-length: 219 content-type: application/json date: Mon, 31 Aug 2026 22:53:01 GMT{ "attempts": 0, "budgets": [ { "cost": 1, "key": "cpu-intensive" } ], "duplicate": false, "folded": false, "id": "03gsa9e0vd59nf3zgysm1yxd0", "priority": 32768, "queue": "example", "ready_at": 1788216781592, "status": "ready", "type": "example" }
Batched jobs
Batched jobs let successive enqueues accumulate into a single pending job.
The client attaches a batch object to the enqueue containing a key, a
when jq predicate, and a fold jq expression:
- The key identifies the batch. Only one unsealed job exists per key at a time.
- The when predicate decides, on each subsequent enqueue with the same
key, whether to fold into the existing pending job or seal it and start a
fresh one. It runs with
$existingbound to the current pending payload and$newbound to the incoming payload; truthy folds, falsy seals. - The fold expression produces the merged payload when a fold happens.
Same
$existing/$newbindings aswhen.
Batched jobs require a pro license.
Both when and fold are compiled and dry-run against the incoming payload
on every batched enqueue. Bad expressions (syntax errors, undefined
variables, or shape errors that only manifest against actual data) return
422 Unprocessable Entity up front rather than failing at first fold. An
expression that returns multiple outputs is also 422.
batch and unique_key are mutually exclusive on the same enqueue.
Supplying both returns 400 Bad Request.
Scheduling opt-out: an enqueue with batch and a future ready_at
persists as a normal scheduled job with its batch config attached for
observability, but no fold happens across a ready_at boundary in either
direction. Folding is strictly ready → ready.
First-enqueue config wins: the when and fold stored on the initial
pending job are what apply for every subsequent fold against it. Changing
the config in later enqueues has no effect until the current batch is
sealed and a new one begins.
The response includes a folded boolean indicating whether the enqueue
was folded into an existing pending job (true, status 200) or created
a new one (false, status 201). Reading the job later via GET /jobs/{id} includes the stored batch config for visibility into what
the server is evaluating on subsequent folds.
First Request:
http POST http://127.0.0.1:7890/jobs --raw '{ "queue": "push", "type": "push.notifications", "payload": {"device_ids": ["abc"], "platform": "apple"}, "batch": { "key": "push:apple", "when": "(($existing | .device_ids) + ($new | .device_ids)) | length <= 100", "fold": "$existing | .device_ids += ($new | .device_ids)" } }'
First Response:
HTTP/1.1 201 Created content-length: 419 content-type: application/json date: Mon, 23 Mar 2026 11:22:14 GMT{ "attempts": 0, "batch": { "key": "push:apple", "when": "(($existing | .device_ids) + ($new | .device_ids)) | length <= 100", "fold": "$existing | .device_ids += ($new | .device_ids)" }, "folded": false, "id": "03ft8h9pkr50xbf7ncrhy2wnk", "priority": 32768, "queue": "push", "ready_at": 1774264934000, "status": "ready", "type": "push.notifications" }
Subsequent Request (same batch key, different device_ids):
http POST http://127.0.0.1:7890/jobs --raw '{ "queue": "push", "type": "push.notifications", "payload": {"device_ids": ["def", "ghi"], "platform": "apple"}, "batch": { "key": "push:apple", "when": "(($existing | .device_ids) + ($new | .device_ids)) | length <= 100", "fold": "$existing | .device_ids += ($new | .device_ids)" } }'
Subsequent Response:
HTTP/1.1 200 OK content-length: 418 content-type: application/json date: Mon, 23 Mar 2026 11:22:31 GMT{ "attempts": 0, "batch": { "key": "push:apple", "when": "(($existing | .device_ids) + ($new | .device_ids)) | length <= 100", "fold": "$existing | .device_ids += ($new | .device_ids)" }, "folded": true, "id": "03ft8h9pkr50xbf7ncrhy2wnk", "priority": 32768, "queue": "push", "ready_at": 1774264934000, "status": "ready", "type": "push.notifications" }
Note that the id on the subsequent response matches the first — the
second enqueue folded into the existing pending job rather than creating
a new one. Fetching the job now returns a merged payload combining
["abc"] and ["def", "ghi"].
Bulk enqueue multiple jobs
An array of jobs is passed in the request, and the server responds with an array containing the same number of jobs, in the same order as the input request. This operation is atomic. If any jobs are invalid or fail to be enqueued, no jobs are enqueued and an error response is returned.
Request:
http POST http://127.0.0.1:7890/jobs/bulk --raw '{ "jobs": [ { "queue": "example", "priority": 500, "type": "hello_world", "payload": {"greet": "World"} }, { "queue": "example", "priority": 500, "type": "hello_world", "payload": {"greet": "Later"}, "ready_at": 1773396035647 } ] }'
Response:
HTTP/1.1 201 Created content-length: 302 content-type: application/json date: Fri, 13 Mar 2026 09:07:17 GMT{ "jobs": [ { "attempts": 0, "id": "03fr1m7p1mwctku2fptz1x5p4", "priority": 500, "queue": "example", "ready_at": 1773392837882, "status": "ready", "type": "hello_world" }, { "attempts": 0, "id": "03fr1m7p1mwctku2fpx425jzr", "priority": 500, "queue": "example", "ready_at": 1773396035647, "status": "scheduled", "type": "hello_world" } ] }
Enqueue a job with explicit backoff policy
Request:
http POST http://127.0.0.1:7890/jobs --raw '{ "queue": "example", "priority": 500, "type": "hello_world", "payload": {"greet": "World"}, "backoff": { "base_ms": 1000, "exponent": 1.5, "jitter_ms": 10000 } }'
Response:
HTTP/1.1 201 Created content-length: 203 content-type: application/json date: Sat, 14 Mar 2026 03:24:16 GMT{ "attempts": 0, "backoff": { "base_ms": 1000, "exponent": 1.5, "jitter_ms": 10000 }, "id": "03fr7ki3x5kqf1epbydrfebkz", "priority": 500, "queue": "example", "ready_at": 1773458656424, "status": "ready", "type": "hello_world" }
Enqueue a job with explicit retention policy
Request:
http POST http://127.0.0.1:7890/jobs --raw '{ "queue": "example", "priority": 500, "type": "hello_world", "payload": {"greet": "World"}, "retention": { "completed_ms": 86400000, "dead_ms": 604800000 } }'
Response:
HTTP/1.1 201 Created content-length: 201 content-type: application/json date: Sat, 14 Mar 2026 03:26:01 GMT{ "attempts": 0, "id": "03fr7kudjeradun2wk1v3tn7b", "priority": 500, "queue": "example", "ready_at": 1773458761086, "retention": { "completed_ms": 86400000, "dead_ms": 604800000 }, "status": "ready", "type": "hello_world" }