Skip to content
Get started
Get started
TCP Control Commands: Jobs, Queues, Limits
View Markdown
api reference · tcp · control

Steer jobs, queues and limits.

Change individual jobs, pause, drain, clean and obliterate whole queues, and set rate limits, concurrency caps, job group controls and per-queue stall and DLQ configuration.

Part of the TCP protocol reference, which describes the framing, authentication, pipelining and response format that every command on this page uses.

Remove a job: a waiting, prioritized, delayed or waiting-children job, or a completed one (its data and result are deleted; a payload-free completion record stays so dependents still run, as with removeOnComplete). active and failed jobs are never removed.

Request:

{ cmd: 'Cancel', id: string }

Response:

{
ok: true;
}

When nothing was removed: { ok: false, error: 'Job not found or cannot be cancelled' } if the job does not exist, { ok: false, error: 'Job cannot be cancelled: not in queue (current state: active)' } (or failed) if it exists in a state Cancel does not remove. The second text never contains “not found”, so clients that treat “not found” as an idempotent miss keep raising for it.

Brokers up to 2.9.13 answer Job not found or cannot be cancelled for every refusal, an active, failed or completed job included. A client that must tell a missing job from a refused one on such a broker sends GetState after that reply and treats only state: 'unknown' as missing, as the TypeScript client does.


Update the progress of an active job.

Request:

{
cmd: 'Progress',
id: string,
progress: number, // 0-100
message?: string // Optional progress message
}

progress is stored as in 2.9.10 and never refused: a number is clamped to 0-100 (NaN is 0); a numeric string, a boolean or null is its Number(...) ("50" is 50, true is 1); other text is 0 with the text as the message when none is given. Clients send object progress as 0 with its JSON as the message.

Response:

{
ok: true;
}

Update the data payload of an existing job.

Request:

{
cmd: 'Update',
id: string,
data: any // New job data
}

data must be JSON serializable (Job data must be JSON serializable). Unlike PUSH, an update has no size limit, as in 2.9.10.

Response:

{
ok: true;
}

Change the priority of a queued job.

Request:

{
cmd: 'ChangePriority',
id: string,
priority: number,
lifo?: boolean // Tie-break ordering among same-priority jobs
}

priority can be any finite number, for grouped jobs too (as in 2.9.10; a numeric string counts as its number); a missing priority is 0 (BullMQ’s changePriority({ lifo: true })). lifo, when given, is made a boolean exactly as on PUSH (1 is true, 0 is false). NaN, an infinity or a non-number fails (priority must be a finite number, priority must be a number) and leaves the job unchanged; a job that is not queued fails with Job not found or not in queue, which the client SDKs treat as “not changed”, like embedded mode.

Response:

{
ok: true;
}

Move a delayed job to the waiting state immediately.

Request:

{ cmd: 'Promote', id: string }

Response:

{
ok: true;
}

Move an active job back to the delayed state.

Request:

{
cmd: 'MoveToDelayed',
id: string,
delay: number, // Delay in ms from now (required, finite; negative = past run time)
token?: string // Required when the active job has a lock
}

A missing or non-finite delay fails the command (delay is required, delay must be a finite number, …) and leaves the job unchanged. As in 2.9.10, a negative delay makes the job ready at once with a past run time (ahead of later ready jobs; a PostgreSQL broker uses “now”, as on 2.9.10) and a very large one is applied (clamped at about 136,900 years).

Response:

{
ok: true;
}

Discard a job by moving it to the dead-letter queue.

Request:

{ cmd: 'Discard', id: string, token?: string }

When the job has an active lease, token must match the current delivery token. For waiting or otherwise unlocked jobs, the field may be omitted for an administrative discard.

Response:

{
ok: true;
}

Wait for a job to complete. This is event-driven (no polling). Returns immediately if the job is already completed, including a job added with removeOnComplete: true that completed before the request arrived: the broker keeps its result for a short time (memory and SQLite: in memory only, 60 s and the last 1,000 such jobs by default; PostgreSQL: its completion record). A job that does not exist, or whose retained result is gone, answers ok: false with Job not found. Send timeout: 0 for a plain completion lookup.

Request:

{
cmd: 'WaitJob',
id: string,
timeout?: number // Max wait time in ms (default: 30000, max: 600000)
}

Response:

{ ok: true, completed: boolean, result?: any }

Pause a queue. Workers will stop pulling new jobs.

Request:

{ cmd: 'Pause', queue: string }

Response:

{
ok: true;
}

Resume a paused queue.

Request:

{ cmd: 'Resume', queue: string }

Response:

{
ok: true;
}

Check whether a queue is currently paused.

Request:

{ cmd: 'IsPaused', queue: string }

Response:

{ ok: true, paused: boolean }

Remove all waiting and delayed jobs from a queue. Active jobs are not affected.

Request:

{ cmd: 'Drain', queue: string }

Response:

{ ok: true, count: number } // Number of jobs removed

Remove all data for a queue (all jobs in all states).

Request:

{ cmd: 'Obliterate', queue: string }

Response:

{
ok: true;
}

Remove jobs older than a grace period, optionally filtered by state.

Request:

{
cmd: 'Clean',
queue: string,
grace: number, // Grace period in ms - jobs older than this are removed
state?: string, // 'waiting'/'delayed'/'prioritized'/'paused' (queued jobs, the default), 'completed', or 'failed'
limit?: number // Max jobs to remove (default: 1000)
}

Response:

{ ok: true, count: number, ids: string[] } // IDs of the removed jobs

List the names of all known queues.

Request:

{
cmd: 'ListQueues';
}

Response:

{ ok: true, queues: string[] } // Queue names

For per-queue counts use GetJobCounts per queue, or the HTTP GET /queues/summary endpoint.


Set a rate limit on a queue: limit jobs per duration ms (default 1000, so jobs per second).

Request:

{
cmd: 'RateLimit',
queue: string,
limit: number, // Max jobs per window
duration?: number, // Window in ms (default 1000)
ttl?: number // Auto-expiry in ms: the server clears the limit itself
}

Invalid duration or ttl values (non-finite or not positive) fall back to the defaults (1 second window, permanent limit) instead of failing. Servers older than 2.8.35 ignore both optional fields.

Response:

{
ok: true;
}

Remove the rate limit from a queue.

Request:

{ cmd: 'RateLimitClear', queue: string }

Response:

{
ok: true;
}

Set a concurrency limit on a queue (max concurrent active jobs). Jobs already active when the limit is set count toward it.

Request:

{
cmd: 'SetConcurrency',
queue: string,
limit: number
}

Response:

{
ok: true;
}

Remove the concurrency limit from a queue.

Request:

{ cmd: 'ClearConcurrency', queue: string }

Response:

{
ok: true;
}

Read the live rate/concurrency configuration and saturation state.

{ cmd: 'GetQueueLimits', queue: string, maxJobs?: number }
{
ok: true,
data: {
limits: {
rateLimit: { max: number, duration: number } | null,
rateLimitTtl: number, // -2 when no rate limit exists
concurrencyLimit: number | null,
maxed: boolean
}
}
}

Group depth excludes active jobs and includes waiting, prioritized and delayed jobs. Every response below is wrapped in data:

{ cmd: 'GetGroupJobsCount', queue, groupId }
// -> { ok: true, data: { count: number } }
{ cmd: 'GetGroupsJobsCount', queue, maxCount? }
// -> { ok: true, data: { count: number } }
{ cmd: 'GetGroupActiveCount', queue, groupId }
// -> { ok: true, data: { count: number } }
{ cmd: 'SetGroupRateLimit', queue, groupId, max, duration }
{ cmd: 'GetGroupRateLimit', queue, groupId }
// -> { ok: true, data: { limit: { max, duration } | null } }
{ cmd: 'RemoveGroupRateLimit', queue, groupId }
// -> { ok: true, data: { removed: 0 | 1 } }
{ cmd: 'GetGroupRateLimitTtl', queue, groupId, maxJobs? }
// -> { ok: true, data: { ttl: number } }
{ cmd: 'SetGroupConcurrency', queue, groupId, concurrency }
{ cmd: 'GetGroupConcurrency', queue, groupId }
// -> { ok: true, data: { concurrency: number | null } }
{ cmd: 'RemoveGroupConcurrency', queue, groupId }
// -> { ok: true, data: { removed: 0 | 1 } }
{ cmd: 'PauseGroup', queue, groupId }
// -> { ok: true, data: { changed: boolean } }
{ cmd: 'ResumeGroup', queue, groupId }
// -> { ok: true, data: { changed: boolean } }
{ cmd: 'IsGroupPaused', queue, groupId }
// -> { ok: true, data: { paused: boolean } }
{ cmd: 'RateLimitGroup', queue, groupId, duration }
// -> { ok: true }

Group IDs are non-empty strings of at most 256 characters. max, duration, and concurrency must be positive safe integers. Stored overrides affect a claim only when PULL/PULLB carries the corresponding group default. Pause blocks only new claims from that group. RateLimitGroup installs an immediately effective manual deadline even when the Worker has no group-rate default.


{ cmd: 'GetDeduplicationJobId', queue: string, deduplicationId: string }
// -> { ok: true, data: { jobId: string | null } }
{ cmd: 'RemoveDeduplicationKey', queue: string, deduplicationId: string }
// -> { ok: true, data: { count: number } }
{ cmd: 'RemoveJobDeduplicationKey', id: string }
// -> { ok: true, data: { removed: boolean } }

The job-owned form removes a key only when the requested job is still its registered owner.


{ cmd: 'MoveToWaitingChildren', id: string, token?: string }
// -> { ok: true, data: { moved: true } }

The job must be active. The transition releases its active resources and persists the parked state. If the job has a lock, token must match it.


Per-queue stall detection configuration. Numeric fields: stallInterval, maxStalls, gracePeriod (numeric strings are coerced, non-numeric values are dropped).

Request:

{ cmd: 'SetStallConfig', queue: string, config: { stallInterval?: number, maxStalls?: number, gracePeriod?: number } }
{ cmd: 'GetStallConfig', queue: string }

Response: { ok: true } for set, { ok: true, config: {...} } for get.

Per-queue DLQ configuration. Numeric fields: autoRetryInterval, maxAutoRetries, maxAge, maxEntries.

Request:

{ cmd: 'SetDlqConfig', queue: string, config: { autoRetry?: boolean, autoRetryInterval?: number, maxAutoRetries?: number, maxAge?: number | null, maxEntries?: number } }
{ cmd: 'GetDlqConfig', queue: string }

Response: { ok: true } for set, { ok: true, config: {...} } for get.