- Docs
- Reference
- TCP Protocol
- Jobs
Push, pull, settle.
Add jobs one at a time, in batches or as an atomic flow graph, claim them with optional locks, and acknowledge or fail them. Job logs, lock extension and the remaining job transitions follow.
Part of the TCP protocol reference, which describes the framing, authentication, pipelining and response format that every command on this page uses.
Core Commands
Section titled “Core Commands”Add a single job to a queue.
Request:
{ cmd: 'PUSH', queue: string, // Queue name (required, max 256 chars, alphanumeric/underscore/dash/dot/colon) name: string, // Job name metadata (required for protocol v3 clients) data: any, // Untouched user payload (required, max 10 MB) priority?: number, // Ungrouped: any finite number, higher first. With groupId: 0 first, then ascending (0..2097151) delay?: number, // Delay in ms before processing (default: 0; negative = ready, past run time) maxAttempts?: number, // Max attempts (default: 3; 1 or less runs once, fraction rounds up, max 2147483647) backoff?: number, // Retry backoff delay in ms (default: 1000; each retry capped at 1 h or maxDelay) ttl?: number, // Time-to-live in ms (>= 0) timeout?: number, // Processing timeout in ms (>= 0; 0 = none) uniqueKey?: string, // Deduplication key jobId?: string, // Custom job ID (idempotent) dependsOn?: string[], // Job IDs this job depends on tags?: string[], // Metadata tags groupId?: string, // Job group identifier groupMaxSize?: number, // Positive safe-integer pending-depth admission cap lifo?: boolean, // Last-in-first-out (default: false) removeOnComplete?: boolean, // Auto-remove on completion (default: false) removeOnFail?: boolean, // Auto-remove on failure (default: false) durable?: boolean, // SQLite: bypass write buffer; PostgreSQL is already transactional repeat?: { // Repeat configuration every?: number, // Repeat interval in ms pattern?: string, // Cron expression (alternative to every) limit?: number, // Max repetitions count?: number, // Current count startDate?: number, // Don't fire before this timestamp endDate?: number, // Don't fire after this timestamp tz?: string, // IANA timezone for pattern immediately?: boolean // Fire once on creation }, // Flow / parent-child (used by FlowProducer): parentId?: string, // Parent job ID childrenIds?: string[], // Child job IDs (flow parent) failParentOnFailure?: boolean, removeDependencyOnFailure?: boolean, ignoreDependencyOnFailure?: boolean, continueParentOnFailure?: boolean, // Advanced options: stallTimeout?: number, // Stall detection timeout in ms (finite) stackTraceLimit?: number, // Cap on stored stack trace lines (finite) keepLogs?: number, // BullMQ metadata, stored as given (finite) sizeLimit?: number, // BullMQ metadata, stored as given (finite) dedup?: { ttl?: number, extend?: boolean, replace?: boolean }, // Dedup options (uniqueKey carries the id); ttl finite debounceId?: string, // Debounce identifier debounceTtl?: number, // Debounce window in ms (finite) timestamp?: number // Explicit creation timestamp (within ±4,320,000,000,000,000)}Every numeric option must be a finite number (a numeric string such as "3" counts as
its number; repeat.every must also be positive); timeout and ttl
cannot be negative; maxAttempts of 1 or less runs the job once and Infinity is
stored as 2,147,483,647. A
violation fails the command with { ok: false, error } naming the field, for example
timeout must be at least 0 or delay must be a finite number. Every value 2.9.10
accepted in either mode is accepted with its 2.9.10 result; durations above about
136,900 years (4.32e15 ms) are clamped. Durations may be fractional. A negative delay
keeps its past run time, as in 2.9.10: the job is ready at once (never delayed) and
runs ahead of ready jobs with a later run time. Embedded Queue.add, addBulk, flows and job schedulers apply the same
rules (their messages name the SDK option, such as attempts).
The backoff field also accepts an object form: { type: 'fixed' | 'exponential', delay?: number, maxDelay?: number } (any other type runs as exponential, as in 2.9.10). maxDelay caps each computed retry delay for that job (default: 1 hour when omitted). A missing delay is the 1000 ms default base; a given one must be a finite number of at least 0; maxDelay must be a finite number between 0 and 86,400,000 ms (1 day); maxDelay: null is treated as omitted, and any other invalid value fails the command. The same rule applies to every job in PUSHB and PUSHF.
Response:
{ ok: true, id: string } // The generated job ID (UUIDv7)Batch push multiple jobs to a queue.
Request:
{ cmd: 'PUSHB', queue: string, jobs: Array<{ name: string, data: any, priority?: number, delay?: number, maxAttempts?: number, backoff?: number, ttl?: number, timeout?: number, uniqueKey?: string, customId?: string, dependsOn?: string[], tags?: string[], groupId?: string, groupMaxSize?: number, lifo?: boolean, removeOnComplete?: boolean, removeOnFail?: boolean, durable?: boolean, // ...and every other PUSH option (stallTimeout, timestamp, dedup, ...) }>}Each job is validated with the same rules as PUSH (option bounds and
dependsOn existence). A dependsOn entry may also reference the customId
of any job in the same batch, so order-independent intra-batch chains work. On
violation the whole batch is rejected with an error naming the offending index
(jobs[i]: ...).
When groupId is present, priority is the intra-group priority and must be an
integer from 0 through 2,097,151; lower values run first. groupMaxSize
makes pending-depth admission atomic. If one member would exceed its group cap,
the complete PUSHB is rejected without partial writes.
Response:
{ ok: true, ids: string[] } // Array of generated job IDsAtomically commit a fully resolved, potentially multi-queue FlowProducer graph.
This is the command used by the Bun package and all six current official SDKs.
Previously published clients may still compose legacy PUSH/UpdateParent
calls.
Request:
{ cmd: 'PUSHF', jobs: Array<{ id: string, // Final ID; non-empty, no colon queue: string, input: { name: string, data: unknown, // untouched user payload dependsOn?: string[], parentId?: string, childrenIds?: string[], groupId?: string, priority?: number, // with groupId: 0 first, then ascending groupMaxSize?: number, // supported ordinary scheduling/retry/failure options } }>}The complete graph is validated before mutation: strict runtime types, duplicate/missing/asymmetric edges, cycles, policy conflicts, 10,000 jobs, 10 MB per job and 64 MB aggregate data. With configured SQLite, all job rows commit in one immediate transaction before any leaf becomes visible. In PostgreSQL mode commits the graph, dependency edges, and ordered durable events in one database transaction before publication. In memory-only mode, publication is still atomic but not crash-durable.
Response:
{ ok: true, data: { jobs: Job[] } }The returned array has exactly one authoritative committed snapshot per input
ID. Any validation, ownership or persistence error returns { ok: false, error } and publishes no job.
Pull the next available job from a queue. Supports optional long polling and lock-based ownership.
Request:
{ cmd: 'PULL', queue: string, timeout?: number, // Long poll timeout in ms (0-60000, default: 0) owner?: string, // Client identifier for lock-based pull lockTtl?: number, // Lock TTL in ms (default: 30000; any finite number, as in 2.9.10) detach?: boolean, // Don't auto-release the job when this connection closes (CLI usage; ignored with owner) group?: { concurrency?: number, limit?: { max: number, duration: number } }}Response (without owner):
{ ok: true, job: Job | null }Response (with owner, includes lock token):
{ ok: true, job: Job | null, token: string | null }The token must be passed to ACK or FAIL to verify ownership.
Batch pull multiple jobs from a queue.
Request:
{ cmd: 'PULLB', queue: string, count: number, // Number of jobs to pull (1-1000) timeout?: number, // Long poll timeout in ms (0-60000, default: 0), with or without owner owner?: string, // Client identifier for lock-based pull lockTtl?: number, // Lock TTL in ms (default: 30000; any finite number, as in 2.9.10) group?: { concurrency?: number, limit?: { max: number, duration: number } }}A lockTtl that is not a finite number (lockTtl must be a number, lockTtl must be a finite number) fails the command before any job is claimed; NaN or an infinity would be
a lease that never expires. Every finite value is granted as given, as in 2.9.10 (a
2.9.10 Worker with lockDuration: 0 sends lockTtl: 0).
Response (without owner):
{ ok: true, jobs: Job[] }Response (with owner, includes lock tokens):
{ ok: true, jobs: Job[], tokens: string[] }Acknowledge a job as completed.
Request:
{ cmd: 'ACK', id: string, // Job ID result?: any, // Optional result data token?: string, // Lock token (required if pulled with owner) removeOnComplete?: boolean // true: remove the job after completion for this call}Response:
{ ok: true;}If an exact timeout or retired cron generation already finalized before the ACK claimed it, the response is a successful no-op rather than a retryable transport error:
{ ok: true, data: { applied: false, reason: 'already-finalized' } }Batch acknowledge multiple jobs.
Request:
{ cmd: 'ACKB', ids: string[], // Job IDs results?: any[], // Optional results (same order as ids; if provided, length must match ids) tokens?: string[] // Lock tokens (same order/length as ids; required for leased jobs)}The broker validates every token before completing any item. A missing or incorrect token rejects the whole batch and leaves all jobs, locks, and results unchanged.
Response:
{ ok: true;}A timeout may win after the batch’s lease preflight. Live positions still apply and the broker reports the exact ignored input positions in order:
{ ok: true, data: { ignoredIds: ['job-id'], ignoredIndices: [2] }}Clients must use ignoredIndices when IDs repeat. Wrong/missing tokens and
ordinary missing/completed jobs remain errors.
Mark a job as failed. The job will be retried with exponential backoff if it has remaining attempts, otherwise it is moved to the dead-letter queue.
Request:
{ cmd: 'FAIL', id: string, // Job ID error?: string, // Error message stack?: string[], // Failure stack trace lines, persisted server-side, capped at job.stackTraceLimit (#74) unrecoverable?: boolean, // Skip all remaining retries and fail terminally (straight to DLQ) token?: string, // Lock token (required if pulled with owner) removeOnFail?: boolean // true: remove the job on terminal failure for this call}The optional stack is stored on the job and surfaced by GetJob and on DLQ entries, so a failed job’s stack trace survives a restart.
Response:
{ ok: true;}An exact late generation uses the same successful no-op envelope as ACK:
{ ok: true, data: { applied: false, reason: 'already-finalized' } }Log Commands
Section titled “Log Commands”AddLog
Section titled “AddLog”Add a log entry to a job.
Request:
{ cmd: 'AddLog', id: string, // Job ID message: string, // Log message level?: 'info' | 'warn' | 'error' // Log level (default: 'info')}Response:
{ ok: true, data: { added: true } }GetLogs
Section titled “GetLogs”Get all log entries for a job.
Request:
{ cmd: 'GetLogs', id: string, start?: number, end?: number } // start/end: inclusive pagination indexesResponse:
{ ok: true, data: { logs: Array<{ message: string, level: string, timestamp: number }>, count: number } }count is the total number of stored log entries (before pagination). Logs are capped at 100 entries per job.
Lock Commands
Section titled “Lock Commands”ExtendLock
Section titled “ExtendLock”Extend the lock TTL on an active job (lock-based processing).
Request:
{ cmd: 'ExtendLock', id: string, duration: number, token?: string }duration is the new lease length from now: any finite number, applied as given (as in
2.9.10; 0 or less ends the lease now). An omitted duration keeps the lease’s current
TTL; NaN, an infinity or a non-number fails with duration must be ... and leaves the
lease unchanged.
Response: { ok: true } or { ok: false, error: 'Lock not found or invalid token' }
ExtendLocks
Section titled “ExtendLocks”Batch variant of ExtendLock (positional arrays, same order).
Request:
{ cmd: 'ExtendLocks', ids: string[], tokens: string[], durations: number[] }Each duration follows the ExtendLock rule. One invalid entry fails the whole
command with durations[i]: duration must be ... before any lease is extended.
Response:
{ ok: true, count: number } // Number of locks successfully extendedMore Job Commands
Section titled “More Job Commands”ChangeDelay
Section titled “ChangeDelay”Change the delay of a delayed job (recomputes runAt).
Request: { cmd: 'ChangeDelay', id: string, delay: number, token?: string }
delay (ms from now) is required and must be a finite number (a numeric string counts
as its number). As with the PUSH delay and as in 2.9.10, a negative value makes the
job ready at once with a past run time (ahead of later ready jobs); a PostgreSQL broker
applies it as “now”, as it did on 2.9.10. NaN or an infinity fails the command (delay must be a finite number).
token is required when the job is active and currently leased. Worker
processor Job objects forward their current delivery token automatically;
unlocked administrative transitions may omit it.
Response: { ok: true }
MoveToWait
Section titled “MoveToWait”Move a job back to waiting, dispatching by current state: active is released back to the queue, delayed is promoted, failed is retried from the DLQ, waiting/prioritized is a no-op success.
Request: { cmd: 'MoveToWait', id: string, token?: string }
Response: { ok: true }
For an active locked job, token is required and must match the current lease.
An active job without a lock can still be moved administratively.
PromoteJobs
Section titled “PromoteJobs”Promote all (or up to count) delayed jobs in a queue to waiting.
Request: { cmd: 'PromoteJobs', queue: string, count?: number }
Response: { ok: true, count: number }
ClearLogs
Section titled “ClearLogs”Clear a job’s log entries, optionally keeping the most recent N.
Request: { cmd: 'ClearLogs', id: string, keepLogs?: number }
keepLogs is the number of most recent entries to keep, applied as in 2.9.10: omitted,
0 or a negative value clears every entry, a fraction keeps its whole part (2.5 keeps
2), a numeric string counts as its number and a value above the entry count keeps them
all. NaN or text fails with keepLogs must be a number and leaves the logs unchanged.
Response: { ok: true }
SetWebhookEnabled
Section titled “SetWebhookEnabled”Enable or disable a webhook without deleting it.
Request: { cmd: 'SetWebhookEnabled', id: string, enabled: boolean }
Response: { ok: true } or { ok: false, error: 'Webhook not found' }
CompactMemory
Section titled “CompactMemory”Trigger internal memory compaction.
Request: { cmd: 'CompactMemory' }
Response: { ok: true }