Approvals are human-in-the-loop gates on jobs. When a queue has approvals enabled, every job enqueued to it pauses and waits for a human to approve or reject before the callback handler is called. The job payload is visible during review so you can make an informed decision.
Use approvals when:
- An AI agent is generating outbound emails and a human should review before send
- A financial transaction above a threshold requires sign-off
- Sensitive data access (e.g., exporting a contact list) needs audit-trail approval
- An automated process touches production systems and you want a manual gate
How the flow works
- •
Enable approvals on the queue
Set
approval_required: truewhen creating or updating the queue. - •
Enqueue a job
Your code enqueues a job normally. The job enters a
pending_approvalstate instead ofwaiting. - •
Rotor creates an approval record
An
approval_recordrow is created withstatus: "pending"and anapv_<32hex>ID. - •
Webhook fires
If you've configured a webhook, Rotor sends an
approval.pendingevent to your endpoint with the approval ID, job ID, queue name, and payload. - •
Human approves or rejects
Via CLI, SDK, REST API, or the MCP
approve_jobtool in Claude Code. - •
Job proceeds or fails
On approval, the job moves to
waitingand your callback handler runs. On rejection, the job is markedfailedwith the rejection reason.
Enable approvals on a queue
Create a new queue with approvals
import { Rotor } from "@rotorsh/sdk";
const rotor = new Rotor({ apiKey: process.env.ROTOR_API_KEY! });
await rotor.queues.create({
name: "ai-outreach",
callback_url: "https://your-app.example.com/rotor/ai-outreach",
approval_required: true,
defaultJobOptions: { attempts: 3 },
});Enable approvals on an existing queue
await rotor.queues.update("ai-outreach", {
approval_required: true,
});Listing pending approvals
CLI
rotor approvals list --status pendingSDK
const { approvals } = await rotor.approvals.list({
status: "pending",
limit: 50,
});
for (const apv of approvals) {
console.log(apv.id); // apv_<32hex>
console.log(apv.jobId); // the job waiting for approval
console.log(apv.queueName); // which queue
console.log(apv.payload); // the job payload — inspect before deciding
console.log(apv.createdAt); // when the approval record was created
}Filter by queue
const { approvals } = await rotor.approvals.list({
status: "pending",
queue: "ai-outreach",
});Get a single approval
const apv = await rotor.approvals.get("apv_abc123...");
console.log(apv.payload); // inspect the job payloadApproving and rejecting
CLI
# Approve
rotor approvals approve apv_abc123...
# Reject
rotor approvals reject apv_abc123...SDK
// Approve — job proceeds to your callback handler
await rotor.approvals.approve("apv_abc123...");
// Reject — job is marked failed
await rotor.approvals.reject("apv_abc123...");REST API
# Approve
curl -X POST "https://api.rotor.sh/v1/approvals/apv_abc123.../approve" \
-H "Authorization: Bearer $ROTOR_API_KEY"
# Reject
curl -X POST "https://api.rotor.sh/v1/approvals/apv_abc123.../reject" \
-H "Authorization: Bearer $ROTOR_API_KEY"The approval.pending webhook
If you configure a webhook endpoint, Rotor sends a POST to it when an approval record is created. The payload type is ApprovalPendingData.
// Webhook payload shape
type ApprovalPendingData = {
approvalId: string; // apv_<32hex>
jobId: string; // the waiting job
queueName: string; // which queue
payload: unknown; // the job payload — safe to log and display
workspaceId: string; // your workspace
};Example payload:
{
"event": "approval.pending",
"data": {
"approvalId": "apv_a1b2c3d4...",
"jobId": "job_e5f6g7h8...",
"queueName": "ai-outreach",
"payload": {
"contactId": "cid_123",
"subject": "Quick question about your stack",
"body": "Hi Sarah, ..."
},
"workspaceId": "ws_abc123"
}
}Use this webhook to post a Slack message to your team, create a Notion entry, or trigger any other notification flow. Your webhook handler can embed a direct link to the approval using the approvalId.
You can approve and reject directly from a Slack message by posting the approvalId back to the Rotor API via a Slack shortcut or button. The MCP approve_job tool makes this even easier from Claude Code.
Using the MCP approve_job tool
If you use the Rotor MCP server, Claude Code gains an approve_job tool. From your terminal you can list pending approvals and approve or reject them without leaving your editor.
# In Claude Code
> List pending approvals for the ai-outreach queue
> Approve apv_abc123...
> Reject apv_xyz789... — the subject line is too aggressive
This is the fastest review path for Claude Code-using teams: see the payload, make a call, continue building.
See MCP quickstart to connect the MCP server.
What happens after rejection
A rejected job is marked failed with reason: "rejected". It does not retry — rejection is terminal. The job appears in your failed jobs list with the rejection timestamp.
If you need to re-attempt a rejected job, enqueue a new job with the corrected payload.
Approvals pause the job indefinitely — there is no automatic timeout. If an approval sits pending for a long time, the job will not run until someone acts. Build a notification flow (webhook → Slack, email) so approvals don't silently stall.
Filtering by status
The status filter accepts: pending, approved, rejected.
// All approved approvals for audit purposes
const { approvals } = await rotor.approvals.list({
status: "approved",
queue: "ai-outreach",
limit: 100,
});Use cursor for pagination:
const page1 = await rotor.approvals.list({ status: "pending", limit: 20 });
const page2 = await rotor.approvals.list({
status: "pending",
limit: 20,
cursor: page1.nextCursor,
});