Install the CLI globally:

npm i -g @rotorsh/cli
rotor --version

Authenticate once, then all commands use your saved key:

rotor login
# Paste your rt_ws_* key. Saved to ~/.rotor/config.json (mode 0600).

You can also pass the key inline via ROTOR_API_KEY — this takes precedence over the saved config.


rotor queues

rotor queues create

rotor queues create <name> [options]
 
Options:
  --concurrency <n>           Max jobs processed in parallel (default: 1)
  --retry-attempts <n>        Attempts per job before moving to DLQ (default: 3)
  --callback-url <url>        HTTPS URL Rotor POSTs each job to
  --rotate-callback-secret    Generate a new HMAC signing secret (printed once)
rotor queues create enrichment \
  --concurrency 10 \
  --retry-attempts 5 \
  --callback-url https://your-app.example.com/rotor/enrichment

rotor queues list

rotor queues list
# NAME         CONCURRENCY  JOBS (waiting/active/failed)
# enrichment   10           142 / 3 / 1
# outreach     5            0 / 0 / 0

rotor queues get

rotor queues get <name>

Prints full queue config: callback URL, retry settings, concurrency, metrics.

rotor queues update

rotor queues update <name> [options]
 
Options:
  --concurrency <n>
  --retry-attempts <n>
  --callback-url <url>
  --rotate-callback-secret    Rotate the signing secret; new value printed once
# Update callback URL and rotate the secret in one command
rotor queues update enrichment \
  --callback-url https://new-host.example.com/rotor/enrichment \
  --rotate-callback-secret

rotor queues delete

rotor queues delete <name>
# Prompts for confirmation. Pass --force to skip.

rotor jobs

rotor jobs enqueue

rotor jobs enqueue <queue> --payload <json> [options]
 
Options:
  --payload <json>            Job payload as a JSON string (required)
  --delay <seconds>           Delay before job becomes eligible
  --tags <tag,...>            Comma-separated list of tags
  --concurrency-key <key>     Mutual exclusion key (one job per key at a time)
  --idempotency-key <key>     Deduplicate same key = same job, not a new one
rotor jobs enqueue outreach \
  --payload '{"contactId":"cid_123","campaignId":"camp_456"}' \
  --tags "campaign:q2-outbound,contact:cid_123" \
  --concurrency-key "contact:cid_123"

rotor jobs get

rotor jobs get <queue> <jobId>

Prints job state, payload, tags, retry count, timestamps, and failure reason if applicable.

rotor jobs list

rotor jobs list <queue> [options]
 
Options:
  --status <state>    Filter by state: waiting|active|completed|failed|delayed
  --tag <tag>         Filter by tag
  --limit <n>         Results per page (default: 50)
rotor jobs list enrichment --status failed
rotor jobs list outreach --tag campaign:q2-outbound

rotor jobs retry

rotor jobs retry <queue> <jobId>
# Moves a failed job back to waiting. Resets attempt count.

rotor jobs cancel

rotor jobs cancel <queue> <jobId>
# Cancels a waiting or delayed job. Cannot cancel an active job.

rotor schedules

rotor schedules create

rotor schedules create [options]
 
Options:
  --queue <name>           Queue to enqueue jobs into (required)
  --cron <expression>      Cron expression (required)
  --timezone <tz>          IANA timezone (required) — e.g. "America/New_York"
  --name <name>            Human-readable name for the schedule
  --payload <json>         Payload for each generated job
rotor schedules create \
  --queue enrichment \
  --cron "0 9 * * 1-5" \
  --timezone "America/New_York" \
  --name "weekday-morning-enrichment" \
  --payload '{"source":"clearbit"}'

rotor schedules list

rotor schedules list
# NAME                          CRON            NEXT FIRE
# weekday-morning-enrichment    0 9 * * 1-5     2026-05-21T09:00:00-04:00

rotor schedules pause

rotor schedules pause <name>
# No new jobs fire until resumed. In-flight jobs are not affected.

rotor schedules resume

rotor schedules resume <name>

rotor schedules delete

rotor schedules delete <name> [--force]

rotor secrets

Secrets are workspace-scoped encrypted values. Reference them in callback URLs and job payloads as ${{ secrets.SECRET_NAME }} — Rotor injects the plaintext at dispatch time. The value never appears in your code or logs.

rotor secrets create

rotor secrets create <NAME> --value <value>
 
# Or pipe the value to avoid shell history:
echo -n "sk_live_abc123" | rotor secrets create STRIPE_KEY --stdin

Secret names must be UPPER_SNAKE_CASE. Values are encrypted at rest (AES-256).

rotor secrets list

rotor secrets list
# NAME            HINT        CREATED
# STRIPE_KEY      sk_li…      2026-05-01
# RESEND_KEY      re_…        2026-05-10

Only the masked hint is shown. Plaintext values are never returned after creation.

rotor secrets delete

rotor secrets delete <NAME> [--force]

rotor status

rotor status
# API:    ok
# Redis:  ok
# Plan:   pro
# Quota:  12,450 / 100,000 jobs used this period
# Resets: 2026-06-01

Add --watch to poll every 5 seconds:

rotor status --watch

rotor template

rotor template add <template-name>

Scaffolds a starter workflow into your current directory. Available templates:

TemplateDescription
outbound-with-approvalsMulti-step outbound sequence with human approval gate
multi-step-outboundEmail sequence with step.waitForEvent reply detection
event-driven-enrichmentParallel contact enrichment on campaign.started
scheduled-with-approvalWeekly cron report requiring approval before delivery
rotor template add outbound-with-approvals

Global flags

All commands accept:

--api-key <key>     Override saved key for this command only
--json              Output raw JSON instead of formatted table
--quiet             Suppress all output except errors
--help              Show help for any command