Human Review API
Submit approve or reject decisions for runs paused at a Human Review node, and subscribe to review-needed webhook events.
Overview
A Human Review node pauses a procedure run and waits for a human decision before continuing. When a run reaches this node it transitions to awaiting_review status and stays there until a reviewer approves or rejects it.
Reviews can be submitted from:
- The Rapidfolio dashboard (the Pending Reviews queue)
- Programmatically via the review endpoint below
- Your own internal tooling, using the API
Submit a Review Decision
POST https://app.rapid.io/api/runner-proxy/review/:runId
Content-Type: application/json
This endpoint requires a valid session token (dashboard session) or an API key with reviewer permissions.
Request Body
{
"approved": true,
"outputs": {
"overrideAmount": 4500
},
"rejectionReason": "Amount exceeds policy",
"idempotencyKey": "550e8400-e29b-41d4-a716-446655440000"
}
Fields
| Field | Type | Required | Description |
|---|---|---|---|
approved | boolean | Yes | true to approve and resume the run; false to reject and fail it |
outputs | object | No | When approving, optionally override the Human Review node's output values. These replace the step's result before the run continues. |
rejectionReason | string | Required if approved: false | A human-readable reason for the rejection. Stored in run.error and visible in the run log. |
idempotencyKey | string (UUID) | Yes | A unique UUID for this submission. Prevents duplicate decisions if the request is retried. Generate a new UUID per review submission. |
Note: The
idempotencyKeymust be a valid UUID v4. Reusing the same key for the same run returns the original decision without re-processing. Reusing it for a different run is an error.
What Happens After
Approved
The run transitions back to running and execution continues from the node immediately following the Human Review node.
If outputs were provided, they replace the Human Review node's result. Downstream nodes receive the overridden values rather than whatever the step originally produced. This is useful when a reviewer needs to correct an amount, adjust a classification, or supply data that the automated step could not determine.
Rejected
The run transitions to failed. The rejectionReason is stored in run.error and is visible in the run log and timeline. No further nodes are executed.
Success Response — 200
{ "success": true }
Error Responses
| Status | Meaning |
|---|---|
404 Not Found | The run does not exist or is not in awaiting_review status |
409 Conflict | A decision has already been submitted for this run |
422 Unprocessable Entity | rejectionReason missing when approved is false, or idempotencyKey is not a valid UUID |
Webhook: Review Needed
Subscribe to the human_review_requested event to receive a notification the moment a run pauses for review. This lets you build real-time review queues, alert reviewers via Slack or email, or integrate approvals into your internal tooling.
See Webhooks for setup instructions.
Payload
{
"id": "delivery_xyz",
"event": "human_review_requested",
"createdAt": "2026-02-27T10:00:00.000Z",
"data": {
"runId": "run_clxyz123",
"procedureId": "proc_abc",
"environment": "sandbox",
"reviewTitle": "Approve payment of $5,000",
"reviewData": {
"amount": 5000,
"recipient": "Acme Corp"
}
}
}
Payload Fields
| Field | Description |
|---|---|
data.runId | The ID of the run paused for review |
data.procedureId | The procedure that contains the review node |
data.environment | sandbox or live |
data.reviewTitle | Human-readable title configured on the review node |
data.reviewData | The data surface configured for the reviewer to inspect |
Example: Automated Review Workflow
A common pattern is to receive the webhook, present the review data to an internal approval system, and then POST the decision back to Rapidfolio once the reviewer acts:
// 1. Receive the webhook
app.post('/webhooks/rapid', async (req, res) => {
const payload = req.body
if (payload.event === 'human_review_requested') {
const { runId, reviewTitle, reviewData } = payload.data
// 2. Create an approval request in your internal system
await internalApprovalQueue.create({
runId,
title: reviewTitle,
data: reviewData,
})
}
res.sendStatus(200)
})
// 3. When reviewer decides, submit back to Rapidfolio
async function submitReviewDecision(
runId: string,
approved: boolean,
rejectionReason?: string
): Promise<void> {
await fetch(`https://app.rapid.io/api/runner-proxy/review/${runId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
approved,
rejectionReason: approved ? undefined : rejectionReason,
idempotencyKey: crypto.randomUUID(),
}),
})
}