Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Enqueuing Jobs

Note

These endpoints are available in both application/json and application/msgpack formats.

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 required
string
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 required
string
Job type known to your application. Must be valid UTF-8 and must not contain any of the follow reserved characters: ,, *, ?, [, ], {, }, \.
priority
int16
Optional numeric priority for the job. Lower values are processed first (higher priority). The default value is 32768.
ready_at
int64
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 required
object
Any JSON-serializable type to be processed by your application
unique_key
string
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_while
string
When the job has a unique key, specifies the scope within which that job is considered unique. One of:
queued
Other jobs with the same unique_key will not be enqueued while this job is in the scheduled or ready statuses.
active
Other jobs with the same unique_key will not be enqueued while this job is in the scheduled, ready or in_flight statuses.
exists
Other jobs with the same unique_key will not be enqueued for as long as this job exists (i.e. until this job is reaped, according to the retention policy).
The default scope is queued.
backoff
object
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_ms
int32
The minimum delay in milliseconds between job retries.
backoff.exponent
float
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_ms
int32
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_limit
int32
Overrides the severs default retry limit for this job. Once this limit is reached, the server marks the job dead.
retention
object
Optional retention policy for dead and completed jobs which overrides the server's default policy. All fields are optional.
retention.dead_ms
int64
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_ms
int64
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.
batch
object
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.key
string
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.when
string
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.fold
string
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.
budgets
array
Array of budget bindings used to control concurrency and/or rate limiting of dispatched jobs. Requires a pro license.
budgets[*].key required
string
The identifier for the budget. Must be valid UTF-8 and must not contain any of the follow reserved characters: ,, *, ?, [, ], {, }, \.
budgets[*].cost
int32
The number of tokens this job takes from the budget. Defaults to 1.
budgets[*].create_with
object
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 required
int32
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 required
object
Details of the specific strategy that is used to manage the tokens available under this budget.
budgets[*].create_with.strategy.type required
string
Names the strategy used to manage the tokens within the budget. One of:
while_in_flight
Concurrency control — tokens are spent from the budget when jobs are dispatched to workers, and returned when the job completes or fails, or the worker disconnects uncleanly. For example, for an allocation of 5, at most 5 jobs bound to this budget can be in-flight at any given time.
time_based
Rate limit — tokens are spent from the budget when jobs are dispatched to workers and are only returned after a configured period of time, regardless of the outcome of the job.
budgets[*].create_with.strategy.duration_ms
int64
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.burst
int32
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 required
string
Unique time-sequenced job ID assigned by the server.
queue required
string
Arbitrary queue name to which the job is assigned
type required
string
Job type known to your application
priority required
int16
Numeric priority for the job. Lower values are processed first (higher priority). The default value is 32768.
status required
string
The job status on the server. One of:
  • scheduled
  • ready
  • in_flight
  • completed
  • dead
Actual statuses shown will be context-dependent.
unique_key
string
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_while
string
When the job has a unique key, specifies the scope within which that job is considered unique. One of:
queued
Conflicting jobs will not be enqueued while this job is in the scheduled or ready statuses.
active
Conflicting jobs will not be enqueued while this job is in the scheduled, ready or in_flight statuses.
exists
Conflicting jobs will not be enqueued while this job exists in any status (i.e. until the job is reaped, according to the retention policy).
The default scope is queued.
duplicate required
boolean
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 required
boolean
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.
batch
object
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 required
int64
The timestamp at which this job is ready to be dequeued by workers.
attempts required
int32
The number of times this job has been previously attempted (starts at zero).
backoff
object
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_ms
int32
The minimum delay in milliseconds between job retries.
backoff.exponent
float
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_ms
int32
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_limit
int32
Overrides the severs default retry limit for this job. Once this limit is reached, the server marks the job dead.
retention
object
Optional retention policy for dead and completed jobs which overrides the server's default policy. All fields are optional.
retention.dead_ms
int64
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_ms
int64
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.
budgets
array
Array of budget bindings used to control concurrency and/or rate limiting of dispatched jobs.
budgets[*].key required
string
The identifier for the budget.
budgets[*].cost
int32
required
The number of tokens this job takes from the budget.

POST /jobs

Enqueues a single job.

Request Body

See Common Job Parameters.

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 required
string
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 required
string
A description of the error.

POST /jobs/bulk

Enqueues multiple jobs atomically.

Request Body

Field Description
jobs required
array
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 required
array
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 required
string
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 required
string
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 $existing bound to the current pending payload and $new bound to the incoming payload; truthy folds, falsy seals.
  • The fold expression produces the merged payload when a fold happens. Same $existing / $new bindings as when.

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 readyready.

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"
}