Profiles·Public

@upstash/qstash

semver>=2.0.0postconditions37functions28last verified2026-06-24coverage score96%

Postconditions: what we check

  • publishJSON · api-error
    error
    WhenAny HTTP or network failure at publish time: invalid QStash token (401), bad destination URL (422), rate limit exceeded (429), invalid request (400), QStash API unavailable (5xx), or network/DNS failure. QStash's retry configuration only applies to message DELIVERY — the publish call itself does not retry on failure.
    ThrowsQstashError (non-2xx HTTP responses — invalid token, bad URL, server errors). QstashRatelimitError extends QstashError (429 burst rate limit). QstashDailyRatelimitError extends QstashError (429 daily limit). Network errors: ECONNREFUSED, ETIMEDOUT when QStash API unreachable.
    Required handlingCaller MUST wrap client.publishJSON() in try/catch (or .catch()). Failure to catch means background jobs silently fail — the work is never queued, never delivered, and never retried. Users don't know their task didn't run. Minimum handling: try { await qstash.publishJSON({ url: `${process.env.NEXTAUTH_URL}/api/process-order`, body: { orderId: order.id }, }); } catch (error) { console.error('Failed to queue job:', error); throw error; // Propagate to caller }
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1][2]
  • publish · api-error
    error
    WhenAny HTTP or network failure: invalid token (401), bad destination (422), rate limit (429), server error (5xx), or network failure.
    ThrowsQstashError, QstashRatelimitError, QstashDailyRatelimitError. Network errors on connection failure.
    Required handlingCaller MUST wrap client.publish() in try/catch. try { await qstash.publish({ url: 'https://myapp.com/api/webhook', body: 'raw payload', }); } catch (error) { console.error('Failed to publish message:', error); throw error; }
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1][3]
  • enqueueJSON · api-error
    error
    WhenAny HTTP or network failure: invalid token (401), queue not found or error (400/404), rate limit (429), server error (5xx), or network failure. Also throws if no queue name was provided to the Queue constructor.
    ThrowsQstashError (non-2xx HTTP responses). QstashRatelimitError (429 burst), QstashDailyRatelimitError (429 daily). Error("Please provide a queue name to the Queue constructor") if queueName missing. Network errors on connection failure.
    Required handlingCaller MUST wrap queue.enqueueJSON() in try/catch. const queue = qstash.queue({ queueName: 'email-queue' }); try { await queue.enqueueJSON({ url: 'https://myapp.com/api/send-email', body: { to: user.email, template: 'welcome' }, }); } catch (error) { console.error('Failed to enqueue email job:', error); throw error; }
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[4][5]
  • enqueue · enqueue-api-error
    error
    WhenAny HTTP or network failure: invalid token (401), queue not found (404), rate limit exceeded (429), server error (5xx), or network failure. Also throws if no queueName was provided to the Queue constructor.
    ThrowsQstashError (non-2xx HTTP responses). QstashRatelimitError (429 burst), QstashDailyRatelimitError (429 daily). Network errors on connection failure. Error("Please provide a queue name to the Queue constructor") if queueName missing.
    Required handlingCaller MUST wrap queue.enqueue() in try/catch. Uncaught error means the message was never enqueued — the work is silently dropped, never delivered, never retried. Users see no indication their task failed.
    costmediumin prodsilent failureusers seelost datavisibilitysilent
    Sources[4][5]
  • batch · batch-api-error
    error
    WhenAny HTTP or network failure at the transport level: invalid token (401), rate limit (429), malformed request (400), server error (5xx), or network failure. The entire batch is unsent — no messages are queued.
    ThrowsQstashError (non-2xx HTTP responses). QstashRatelimitError (429 burst), QstashDailyRatelimitError (429 daily). Network errors on connection failure.
    Required handlingCaller MUST wrap client.batch() in try/catch. An uncaught batch failure means none of the messages were published. All background jobs in the batch are silently dropped. High-throughput publishers that use batch() for efficiency may lose entire job batches.
    costmediumin prodsilent failureusers seelost datavisibilitysilent
    Sources[3][1]
  • batch · batch-empty-array-error
    error
    WhenAn empty array is passed to batch().
    ThrowsQstashEmptyArrayError extends QstashError. Thrown immediately before any HTTP request.
    Required handlingCaller MUST validate the batch array is non-empty before calling batch(). QstashEmptyArrayError surfaces immediately — it is a programming error, not a transient failure.
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[3]
  • batchJSON · batchjson-api-error
    error
    WhenAny HTTP or network failure: invalid token (401), rate limit (429), malformed request (400), server error (5xx), or network failure. The entire batch fails atomically — no messages are published on transport-level errors.
    ThrowsQstashError (non-2xx HTTP responses). QstashRatelimitError (429 burst), QstashDailyRatelimitError (429 daily). Network errors on connection failure.
    Required handlingCaller MUST wrap client.batchJSON() in try/catch. An uncaught failure means the entire batch of background jobs is silently dropped. SaaS apps that use batchJSON() for bulk job submission may lose all queued work without user-facing indication.
    costmediumin prodsilent failureusers seelost datavisibilitysilent
    Sources[3][1]
  • batchJSON · batchjson-empty-array-error
    error
    WhenAn empty array is passed to batchJSON().
    ThrowsQstashEmptyArrayError extends QstashError. Thrown immediately before any HTTP request.
    Required handlingCaller MUST ensure the batch array is non-empty before calling batchJSON(). This is a programming error that surfaces immediately — easy to catch in tests.
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[3]
  • verify · verify-signature-error
    error
    WhenJWT signature verification fails with both the current signing key and the next signing key. Three cases: (1) JWT signature invalid, (2) URL subject mismatch, (3) body hash mismatch.
    ThrowsSignatureError (subclass of Error) with a descriptive message. Thrown when the request was not sent by QStash, signing keys are misconfigured, or key rotation is in progress.
    Required handlingCaller MUST catch SignatureError and return a 401 Unauthorized response. Processing the request payload after a failed verify() is a security vulnerability — attackers can trigger arbitrary background jobs by sending spoofed QStash requests. If the exception propagates, the endpoint returns 500 and QStash retries delivery.
    costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[6][7]
  • verify · verify-no-signing-keys
    error
    WhenSigning keys cannot be resolved from QSTASH_CURRENT_SIGNING_KEY, QSTASH_NEXT_SIGNING_KEY environment variables or the ReceiverConfig passed to the constructor.
    ThrowsError("No signing keys available for verification"). This is a misconfiguration error — the Receiver was instantiated without keys.
    Required handlingEnsure QSTASH_CURRENT_SIGNING_KEY and QSTASH_NEXT_SIGNING_KEY are set in the environment. All incoming webhook requests will fail verification without these keys — the entire webhook handler is non-functional and background job delivery stops completely.
    costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[6][7]
  • schedules.create · schedules-create-api-error
    error
    WhenAny HTTP failure: invalid token (401), invalid cron expression (400), invalid destination URL (422), rate limit exceeded (429), or server error (5xx). Also throws when flowControl is specified without parallelism or ratePerSecond.
    ThrowsQstashError (non-2xx HTTP responses). QstashRatelimitError (429 burst), QstashDailyRatelimitError (429 daily). QstashError("Provide at least one of parallelism or ratePerSecond for flowControl") when flowControl is misconfigured.
    Required handlingCaller MUST wrap client.schedules.create() in try/catch. Failure means the recurring job is never created — scheduled tasks (daily emails, cleanup jobs, sync crons) silently fail to register. Users never receive scheduled notifications or automated reports.
    costmediumin prodsilent failureusers seelost datavisibilitysilent
    Sources[8][9]
  • queue.upsert · queue-upsert-api-error
    error
    WhenHTTP failure: invalid token (401), invalid parallelism value (400), rate limit (429), or server error (5xx). Also throws if the Queue instance was created without queueName.
    ThrowsQstashError on HTTP failure. QstashRatelimitError (429 burst). Error("Please provide a queue name to the Queue constructor") if queueName missing.
    Required handlingCaller MUST wrap queue.upsert() in try/catch. If uncaught, the queue is not created or updated. Subsequent enqueue() calls may target a non-existent queue or an incorrectly configured one.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[4][5]
  • queue.pause · queue-pause-api-error
    error
    WhenHTTP failure: invalid token (401), queue not found (404), rate limit (429), or server error (5xx). Also throws if queueName is missing from the Queue constructor.
    ThrowsQstashError on HTTP failure. QstashRatelimitError (429 burst). Error("Please provide a queue name to the Queue constructor") if queueName missing.
    Required handlingCaller MUST wrap queue.pause() in try/catch. If uncaught, the queue is not paused — during maintenance windows, messages continue delivering to a handler that may be down or being deployed, causing delivery failures and messages entering the DLQ.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[4]
  • queue.resume · queue-resume-api-error
    error
    WhenHTTP failure: invalid token (401), queue not found (404), rate limit (429), or server error (5xx). Also throws if queueName is missing from the Queue constructor.
    ThrowsQstashError on HTTP failure. QstashRatelimitError (429 burst). Error("Please provide a queue name to the Queue constructor") if queueName missing.
    Required handlingCaller MUST wrap queue.resume() in try/catch. If uncaught, the queue remains paused after the maintenance window ends. Background jobs pile up but are never delivered — the system silently stalls with jobs accumulating indefinitely without automatic recovery.
    costmediumin prodsilent failureusers seelost datavisibilitysilent
    Sources[4]
  • schedules.delete · schedules-delete-api-error
    error
    WhenHTTP failure: invalid token (401), schedule not found (404), rate limit (429), or server error (5xx). Deleting a non-existent scheduleId returns 404 and throws QstashError.
    ThrowsQstashError on HTTP failure. QstashRatelimitError (429 burst).
    Required handlingCaller MUST wrap client.schedules.delete() in try/catch. If uncaught, the schedule is not deleted — the recurring cron job continues running after it should have been stopped. Stale background tasks execute, potentially processing data that should no longer be processed.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[8]
  • schedules.pause · schedules-pause-api-error
    error
    WhenHTTP failure: invalid token (401), schedule not found (404), rate limit (429), or server error (5xx).
    ThrowsQstashError on HTTP failure. QstashRatelimitError (429 burst).
    Required handlingCaller MUST wrap client.schedules.pause() in try/catch. If uncaught, the schedule is not paused — the cron job continues running during maintenance, and scheduled tasks execute against a system that may be down or being updated.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[8]
  • schedules.resume · schedules-resume-api-error
    error
    WhenHTTP failure: invalid token (401), schedule not found (404), rate limit (429), or server error (5xx).
    ThrowsQstashError on HTTP failure. QstashRatelimitError (429 burst).
    Required handlingCaller MUST wrap client.schedules.resume() in try/catch. If uncaught, the schedule remains paused after the maintenance window — recurring tasks silently stop executing. Daily digests, cleanup jobs, and sync crons never run. Users notice absence of scheduled features rather than an explicit error.
    costmediumin prodsilent failureusers seelost datavisibilitysilent
    Sources[8]
  • queue.delete · queue-delete-api-error
    error
    WhenAny HTTP failure: invalid token (401), queue not found (404), rate limit (429), or server error (5xx). Also throws synchronously if queueName was not provided to the Queue constructor.
    ThrowsQstashError on HTTP failure (non-2xx responses). QstashRatelimitError (429 burst). Error("Please provide a queue name to the Queue constructor") if queueName missing.
    Required handlingCaller MUST wrap queue.delete() in try/catch. An uncaught error during queue deletion can leave a queue in an inconsistent state, especially in cleanup flows. More critically: if a 404 is thrown, the caller must decide whether to treat it as success (idempotent) or failure.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[10][11]
  • queue.delete · queue-delete-data-loss
    warning
    Whenqueue.delete() is called on a queue that still has pending messages (queue.lag > 0). The SDK does NOT warn about pending messages — the delete succeeds silently and all pending messages are permanently discarded without delivery.
    ThrowsDoes not throw. The delete succeeds and returns void. Pending messages are lost.
    Required handlingCallers SHOULD check queue.get() to inspect the lag field before deleting a queue with pending messages. If message delivery matters, pause + drain the queue first before deleting it. This is a data-loss pattern, not an exception pattern.
    costhighin prodsilent failureusers seelost datavisibilitysilent
    Sources[10][5]
  • messages.cancel · messages-cancel-api-error
    error
    WhenAny HTTP failure: invalid token (401), rate limit (429), server error (5xx), or network failure.
    ThrowsQstashError on HTTP failure (non-2xx responses). QstashRatelimitError (429 burst). QstashDailyRatelimitError (429 daily limit). Network errors on connection failure.
    Required handlingCaller MUST wrap client.messages.cancel() in try/catch. An uncaught error in a cleanup flow means messages continue to be delivered to an endpoint that should no longer receive them. Background jobs run after cancellation was intended.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[12][13]
  • messages.cancel · messages-cancel-result-unchecked
    warning
    Whencancel() returns {cancelled: N} where N may be less than the number of messages expected to be cancelled. This happens when messages were already delivered, are IN_PROGRESS, messageIds don't exist, or a filter matches fewer messages than expected. An empty array input silently returns {cancelled: 0}.
    ThrowsDoes not throw. Returns {cancelled: 0} even when no messages are cancelled, including for empty string[] input.
    Required handlingCallers SHOULD check the returned cancelled count against the expected count. Discarding the return value and assuming all messages were cancelled is incorrect. In multi-step flows where cancellation is a precondition to safe re-queuing, uncancelled messages will cause duplicate delivery.
    costmediumin prodsilent failureusers seedegraded performancevisibilitysilent
    Sources[12]
  • dlq.delete · dlq-delete-api-error
    error
    WhenAny HTTP failure: invalid token (401), rate limit (429), server error (5xx), or network failure. Filter validation errors (400) if filter parameters are invalid.
    ThrowsQstashError on HTTP failure (non-2xx responses). QstashRatelimitError (429 burst). QstashDailyRatelimitError (429 daily limit). Network errors on connection failure.
    Required handlingCaller MUST wrap client.dlq.delete() in try/catch. An uncaught error in a DLQ cleanup flow leaves processed messages in the DLQ indefinitely. Re-runs of the cleanup code may attempt to process already-handled messages again.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[14][15]
  • dlq.delete · dlq-delete-cursor-not-checked
    warning
    Whendlq.delete() returns {deleted: N, cursor?: string}. If cursor is present, there are more DLQ messages to delete. Callers that don't loop until cursor is undefined will silently leave DLQ messages undeleted.
    ThrowsDoes not throw. Returns with cursor set when more messages remain.
    Required handlingCallers performing bulk DLQ cleanup MUST loop until cursor is undefined: let cursor: string | undefined; do { const result = await client.dlq.delete({ all: true, count: 100, cursor }); cursor = result.cursor; } while (cursor); Stopping after a single call leaves the remainder of the DLQ unprocessed.
    costmediumin prodsilent failureusers seedegraded performancevisibilitysilent
    Sources[14][15]
  • dlq.retry · dlq-retry-api-error
    error
    WhenAny HTTP failure: invalid token (401), rate limit (429), server error (5xx), or network failure. Filter validation errors (400) if filter parameters are invalid.
    ThrowsQstashError on HTTP failure (non-2xx responses). QstashRatelimitError (429 burst). QstashDailyRatelimitError (429 daily limit). Network errors on connection failure.
    Required handlingCaller MUST wrap client.dlq.retry() in try/catch. An uncaught error in a DLQ retry flow means failed messages remain in the DLQ without being re-queued. Recovery from incidents becomes manual — operators must re-trigger the flow after fixing the underlying issue.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[14][15]
  • dlq.retry · dlq-retry-cursor-not-checked
    warning
    Whendlq.retry() returns {cursor?: string, responses: [{messageId}][]}. If cursor is present, there are more DLQ messages remaining. Callers that don't loop until cursor is undefined will silently leave some DLQ messages unretried.
    ThrowsDoes not throw. Returns with cursor set when more messages remain.
    Required handlingCallers performing bulk DLQ retry MUST loop until cursor is undefined: let cursor: string | undefined; do { const result = await client.dlq.retry({ all: true, count: 100, cursor }); cursor = result.cursor; } while (cursor); Stopping after a single call leaves remaining failed messages in the DLQ.
    costmediumin prodsilent failureusers seedegraded performancevisibilitysilent
    Sources[14]
  • logs · logs-api-error
    error
    WhenAny HTTP failure: invalid token (401), rate limit (429), server error (5xx), or network failure.
    ThrowsQstashError on HTTP failure (non-2xx responses). QstashRatelimitError (429 burst). QstashDailyRatelimitError (429 daily limit). Network errors on connection failure.
    Required handlingCaller MUST wrap client.logs() in try/catch. Uncaught errors in log fetching loops will abort the pagination loop mid-way, producing incomplete log data. Observability pipelines that rely on complete log retrieval will silently output partial results.
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[3][16]
  • logs · logs-cursor-not-checked
    warning
    Whenclient.logs() returns {cursor?: string, logs: Log[]}. The response is paginated — maximum 1000 logs per request. Callers that don't loop until cursor is undefined will silently retrieve only the first page, missing older log entries.
    ThrowsDoes not throw. Returns with cursor set when more logs remain.
    Required handlingCallers retrieving historical logs MUST paginate: let cursor: string | undefined; const allLogs: Log[] = []; do { const res = await client.logs({ cursor }); allLogs.push(...res.logs); cursor = res.cursor; } while (cursor); Single-call implementations silently miss all but the most recent page.
    costlowin prodsilent failureusers seedegraded performancevisibilitysilent
    Sources[3][16]
  • urlGroups.addEndpoints · urlgroups-addendpoints-api-error
    error
    WhenAny HTTP failure: invalid token (401), invalid endpoint payload (400), rate limit (429), or server error (5xx). Network errors on connection failure.
    ThrowsQstashError on HTTP failure (non-2xx responses). QstashRatelimitError (429 burst). QstashDailyRatelimitError (429 daily limit). Network errors on connection failure.
    Required handlingCaller MUST wrap client.urlGroups.addEndpoints() in try/catch. An uncaught failure during subscription setup means the url group is left with missing or stale endpoints. Subsequent publishes to the url group fan out to fewer subscribers than expected — some subscribers silently miss every message because they were never wired up.
    costmediumin prodsilent failureusers seelost datavisibilitysilent
    Sources[17][18]
  • urlGroups.removeEndpoints · urlgroups-removeendpoints-api-error
    error
    WhenAny HTTP failure: invalid token (401), url group not found (404), invalid endpoint payload (400), rate limit (429), or server error (5xx). Network errors on connection failure.
    ThrowsQstashError on HTTP failure (non-2xx responses). QstashRatelimitError (429 burst). QstashDailyRatelimitError (429 daily limit). Network errors on connection failure.
    Required handlingCaller MUST wrap client.urlGroups.removeEndpoints() in try/catch. An uncaught failure during decommissioning leaves the stale endpoint subscribed — QStash continues delivering messages to a service that may no longer exist or may be processing stale data. Failed-delivery messages accumulate in the DLQ silently.
    costmediumin prodsilent failureusers seelost datavisibilitysilent
    Sources[17][18]
  • urlGroups.delete · urlgroups-delete-api-error
    error
    WhenAny HTTP failure: invalid token (401), url group not found (404), rate limit (429), or server error (5xx). Network errors on connection failure.
    ThrowsQstashError on HTTP failure (non-2xx responses). QstashRatelimitError (429 burst). QstashDailyRatelimitError (429 daily limit). Network errors on connection failure.
    Required handlingCaller MUST wrap client.urlGroups.delete() in try/catch. An uncaught failure in a cleanup flow can leave a url group orphaned with stale endpoints. Subsequent attempts to recreate the same-named url group may fail or merge with the leftover configuration. In a 404 case the caller must decide whether to treat it as idempotent success or failure.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[17][18]
  • dlq.listMessages · dlq-list-api-error
    error
    WhenAny HTTP failure: invalid token (401), invalid filter parameter (400), rate limit (429), or server error (5xx). Network errors on connection failure.
    ThrowsQstashError on HTTP failure (non-2xx responses). QstashRatelimitError (429 burst). QstashDailyRatelimitError (429 daily limit). Network errors on connection failure.
    Required handlingCaller MUST wrap client.dlq.listMessages() in try/catch. Uncaught errors in DLQ dashboards or triage tooling abort the listing mid-page, producing incomplete or empty results. Ops engineers may conclude the DLQ is empty when it is in fact full, deferring action on a real incident.
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[14][15]
  • dlq.listMessages · dlq-list-cursor-not-checked
    warning
    Whendlq.listMessages() returns {messages: DlqMessage[], cursor?: string}. The response is paginated — default 100 per page. If cursor is present, more messages exist. Callers that don't loop until cursor is undefined silently retrieve only the first page and miss the rest of the DLQ.
    ThrowsDoes not throw. Returns with cursor set when more messages remain.
    Required handlingCallers performing full DLQ inspection MUST paginate: let cursor: string | undefined; const all: DlqMessage[] = []; do { const res = await client.dlq.listMessages({ count: 100, cursor }); all.push(...res.messages); cursor = res.cursor; } while (cursor); Single-call implementations silently miss everything after the first page — a partial view of a DLQ backlog leads to incomplete recovery actions.
    costmediumin prodsilent failureusers seedegraded performancevisibilitysilent
    Sources[14][15]
  • flowControl.pause · flowcontrol-pause-api-error
    error
    WhenAny HTTP failure: invalid token (401), flow-control key not found (404), rate limit (429), or server error (5xx). Network errors on connection failure.
    ThrowsQstashError on HTTP failure (non-2xx responses). QstashRatelimitError (429 burst). QstashDailyRatelimitError (429 daily limit). Network errors on connection failure.
    Required handlingCaller MUST wrap client.flowControl.pause() in try/catch. If uncaught during a maintenance window, the flow-control key is NOT paused — messages continue delivering to a handler that may be down or being deployed. Failed deliveries accumulate in the DLQ and downstream services may receive partial traffic during what should be a controlled pause.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[19][20]
  • flowControl.resume · flowcontrol-resume-api-error
    error
    WhenAny HTTP failure: invalid token (401), flow-control key not found (404), rate limit (429), or server error (5xx). Network errors on connection failure.
    ThrowsQstashError on HTTP failure (non-2xx responses). QstashRatelimitError (429 burst). QstashDailyRatelimitError (429 daily limit). Network errors on connection failure.
    Required handlingCaller MUST wrap client.flowControl.resume() in try/catch. If uncaught, the flow-control key remains paused after the maintenance window ends. Background jobs accumulate in the waitlist indefinitely — the system silently stalls without any error surfacing. Engineers may notice missing scheduled side effects (emails not sent, syncs not run) hours later.
    costmediumin prodsilent failureusers seelost datavisibilitysilent
    Sources[19][20]
  • flowControl.pin · flowcontrol-pin-api-error
    error
    WhenAny HTTP failure: invalid token (401), flow-control key not found (404), invalid parallelism or rate value (400), rate limit (429), or server error (5xx). Network errors on connection failure.
    ThrowsQstashError on HTTP failure (non-2xx responses). QstashRatelimitError (429 burst). QstashDailyRatelimitError (429 daily limit). Network errors on connection failure.
    Required handlingCaller MUST wrap client.flowControl.pin() in try/catch. If uncaught during an incident-response flow that pins rate to protect a downstream service, the pin silently fails and delivery proceeds at publisher-specified rates — typically higher than the downstream can handle. The protection the operator intended never took effect.
    costmediumin prodsilent failureusers seedegraded performancevisibilitysilent
    Sources[19][20]
  • flowControl.unpin · flowcontrol-unpin-api-error
    error
    WhenAny HTTP failure: invalid token (401), flow-control key not found (404), rate limit (429), or server error (5xx). Network errors on connection failure.
    ThrowsQstashError on HTTP failure (non-2xx responses). QstashRatelimitError (429 burst). QstashDailyRatelimitError (429 daily limit). Network errors on connection failure.
    Required handlingCaller MUST wrap client.flowControl.unpin() in try/catch. If uncaught after an incident is resolved, the flow-control key remains pinned to the incident-time configuration. Delivery continues at the restricted rate long after the underlying issue is fixed — capacity is wasted and SLAs may be missed because the pinned restriction is silently still in force.
    costmediumin prodsilent failureusers seedegraded performancevisibilitysilent
    Sources[19][20]
  • flowControl.resetRate · flowcontrol-resetrate-api-error
    error
    WhenAny HTTP failure: invalid token (401), flow-control key not found (404), rate limit (429), or server error (5xx). Network errors on connection failure.
    ThrowsQstashError on HTTP failure (non-2xx responses). QstashRatelimitError (429 burst). QstashDailyRatelimitError (429 daily limit). Network errors on connection failure.
    Required handlingCaller MUST wrap client.flowControl.resetRate() in try/catch. If uncaught in an ops tool that resets rate state after an incident, the reset silently fails and accumulated counter state continues to throttle delivery. Operators may conclude their reset worked when it did not — leading to repeated, ineffective reset attempts while the queue continues to be throttled.
    costlowin prodsilent failureusers seedegraded performancevisibilitysilent
    Sources[19][20]

Sources

Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.

Official documentation
  • [1]
    upstash.com/docs/qstash/sdks
    Publish
  • [5]
    upstash.com/docs/qstash/features
    Queues
  • [7]
    upstash.com/docs/qstash/features
    Security
  • [9]
    upstash.com/docs/qstash/features
    Schedules
  • [11]
    upstash.com/docs/qstash/sdks
    Queues
  • [13]
    upstash.com/docs/qstash/sdks
    Messages
  • [15]
    upstash.com/docs/qstash/features
    Dlq
  • [16]
    upstash.com/docs/qstash/sdks
    Events
  • [18]
    upstash.com/docs/qstash/features
    Url Groups
  • [20]
    upstash.com/docs/qstash/features
    Flow Control
Source code

Research notes

Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.

Sources — @upstash/qstash

Claim: publishJSON/publish/enqueueJSON throw QstashError on HTTP failures

Source 1: GitHub source code — src/client/http.ts https://github.com/upstash/qstash-js/blob/main/src/client/http.ts

The HttpClient class used by all publish methods throws QstashError (and subclasses) on non-2xx responses. The processRequest method:

  1. Checks response status
  2. On 429: throws QstashRatelimitError, QstashDailyRatelimitError, or QstashChatRatelimitError based on headers
  3. On other non-2xx: throws QstashError with response body text

Source 2: QStash TypeScript SDK Examples https://upstash.com/docs/qstash/sdks/ts/examples/publish

Official documentation for publishJSON usage.

Source 3: npm package page https://www.npmjs.com/package/@upstash/qstash

Claim: Error class hierarchy

Source: GitHub source code — src/client/error.ts (inferred from http.ts imports)

QstashError extends Error
  QstashRatelimitError extends QstashError
  QstashDailyRatelimitError extends QstashError
  QstashChatRatelimitError extends QstashError
  QstashEmptyArrayError extends QstashError
SignatureError extends Error (from receiver.ts)

Claim: No auto-retry at publish time

Source: QStash documentation on retries https://upstash.com/docs/qstash/features/retry

Retries apply to message DELIVERY (QStash retrying the destination URL). The SDK's RetryConfig controls HTTP request retries on the client side, but defaults to 0.

Claim: receiver.verify() throws SignatureError

Source: GitHub source code — src/receiver.ts https://github.com/upstash/qstash-js/blob/main/src/receiver.ts

SignatureError is thrown when JWT signature is invalid, URL doesn't match, or body hash doesn't match.

Package Metadata

Need a different package?
Request a profile