Concepts
Human review
How a cascade pauses for a reviewer, what awaiting_review means for your integration, and the endpoints to claim, decide, note and audit reviews.
An IfElse node can route an applicant to human_review_steps instead of passing or failing them outright — a borderline credit score, a record that needs a person's judgement. When that happens the execution stops at status: awaiting_review with needs_human_review: true and review_status: in_review. The nodes listed in the decision's human_review_steps run immediately (interim work such as a hold email or pre-generating an adverse-action draft); then the execution pauses, and neither pass_steps nor fail_steps runs until someone decides.
Reviewers can decide in the Cascades app; the API exposes the same actions so you can build the review queue into your own tooling.
What happens at a decision?
POST /executions/{id}/review_decisions with { "decision": "approved" | "denied", "note": "…" } records the decision and resumes the matching branch:
approvedresumes down the decision'spass_steps.deniedresumes down the decision'sfail_steps. AnAdverseActionnode placed inhuman_review_stepshas already rendered its draft at flag time; the notice is sent when a reviewer sends it from the app, not by the decision itself (see Adverse action).
Test-mode executions resume inline: the response is 200 OK with the final state and resume_enqueued: false. Live-mode executions enqueue the resume and answer 202 Accepted with resume_enqueued: true; poll GET /executions/{id} until status leaves awaiting_review / waiting, or wait for the webhook.
If nobody has claimed the review, the decision claims it for the caller. Errors:
| Code | When |
|---|---|
409 review_not_pending |
The execution is not waiting for a review |
409 review_already_decided |
Someone already approved or denied it |
403 review_assigned_elsewhere |
The review is claimed by another user |
403 live_mode_forbidden |
Live execution, account without production access |
Claiming and releasing
POST /executions/{id}/review_assignment with { "assignee": "me" } claims the review for the user behind your key; { "assignee": null } releases it. The body field is assignee (not action) because Rails reserves that parameter name. Each API client maps to a single user, so cross-user assignment is not a meaningful operation here — teams with several reviewers use one client per reviewer.
Notes and the audit trail
POST /executions/{id}/review_noteswith{ "note": "…" }appends anote_addedevent (201 Created).GET /executions/{id}/review_eventsreturns the full trail, oldest first:flagged,assigned,unassigned,approved,denied,note_added,re_evaluated, and the adverse-action notice eventsadverse_action_drafted,adverse_action_sent,adverse_action_skippedandadverse_action_replayed, each with the actor and timestamp. This is what you reconstruct "who decided what and when" from for compliance.
Deciding many at once
POST /executions/review_bulk_actions applies approve, deny or assign_to_me to up to 25 execution ids in one request. It is always asynchronous (202) and returns { applied, enqueued, skipped }, where skipped lists a reason per id that could not be processed: not_awaiting_review, review_not_pending, assigned_elsewhere, live_mode_forbidden, already_assigned, or not_found. Ids belonging to another tenant are reported as not_found so existence is never leaked.
Finding what needs review
GET /executions?filter[needs_human_review]=true&filter[review_status]=in_review is your queue. Each row carries assigned_to_user_id so you can show who holds it.
Related
- Handle human review — a worked integration
- Executions — the status lifecycle
POST /executions/{id}/review_decisions