Approval & Review Process — Multi-Stage Approval Chains
Multi-stage approval workflows for deals (PipelineItem), quotes, contacts, and proposals. Admins build named ApprovalProcess templates with sequential or parallel approver steps; team members submit entities into the process; approvers approve/reject from the inbox or via email. Submitted entities are hard-locked (HTTP 423 on edit attempts) while pending so nobody slips a change through during review. Distinct from "AI Actions & Approvals" which gates the AI's own actions — this one gates HUMAN-driven business processes. Plan-gated: Pro (10 active processes) / Max (unlimited).
Steps
- Build the process: go to Approvals (sidebar > Approvals) > Processes tab > "+ New process". Pick the entityType this process applies to (PipelineItem / Quote / Contact / Proposal / custom), set name + description, and toggle the global flags: requireSubmitNote (force submitter to add context), allowSelfApproval (rare — usually off), lockOnSubmit (default ON — entities lock on submit).
- Configure approver chain. Each ApprovalStep has an order number, a name, approverUserIds (explicit list) AND/OR approverRoles (role-name strings like ADMIN / EXECUTIVE), and a logic mode: "any" (any approver can advance the step) or "all" (every named approver must approve). approvalType on the parent process is "sequential" (each step waits for the prior) OR "parallel" (all steps fire simultaneously) OR "any_one" (first approval anywhere wins).
- Optionally add a triggerCriteria JSON to auto-route entities through this process. e.g. {"dealValue": {"gt": 10000}} on a PipelineItem process means deals over $10k auto-match. Multiple processes can match — the resolver either picks the best fit (1 match → auto-submit) or asks the user to choose (2+ → "pending_approval_choice" UI banner with a chooser).
- Set escalation: escalateAfterHours + escalationUserId. If a request sits in pending state past the threshold, the approval-escalate worker fires an email reminder to the escalation user and appends an "escalate" entry to the audit trail.
- Submit an entity for approval: from the entity detail page (deal, quote, etc.), click "Submit for approval". If a process matches automatically, it submits in one click. If multiple processes match, you see a chooser. The entity's status flips to pending_approval; lockedByApprovalRequestId is set on the row; PATCH/DELETE on it now returns 423.
- Approvers act: pending requests show in /dashboard/approvals > Inbox (sidebar badge counts pending-for-you). Click in, see the diff context (what entity, who submitted, submitter note), then click Approve, Request Info, or Reject. Reject + Request Info require a reason. Approval moves the request to the next step (or completes it). Final approval unlocks the entity and sets it to "approved"; rejection unlocks it and sets it to "rejected".
- Email path: every step transition sends an email to the next approver (approvalRequestEmail), the submitter on terminal events (approvalApprovedEmail / approvalRejectedEmail / approvalInfoRequestedEmail), and to escalation users on hour-based timeouts (approvalEscalationEmail). Approvers can act from the inbox AND from a one-click email link.
- Withdraw your own request: submitter can withdraw any non-terminal ApprovalRequest from the request detail page. Withdrawing unlocks the entity and lets you edit + resubmit.
Good to know
- Lockable entity types are PipelineItem (deals) and Quote in v1. Contact and Proposal are submittable but don't hard-lock — you can still edit them while approval is pending. (We chose narrow lock semantics intentionally to avoid breaking existing CRM-edit-during-review workflows for those types.)
- Approvals integrates with CPQ: a CpqPriceRule with requiresApproval=true on the matching quote auto-fires submitForApprovalAuto on quote send. The result is one of three outcomes: pending_approval (1 process matched), pending_approval_choice (2+ matched — UI shows chooser banner), or draft + warning ("0 processes match — fix your approval setup"). All three are surfaced in the quote detail page.
- Approver authorization is checked on every action — even if you have a stale email link, the backend re-resolves "is this user authorized for this step right now?" before accepting. Roles are dereferenced via the User.role enum (single-string, not a join table).
- Notify lists (notifyOnApprove / notifyOnReject) are user IDs that get email notifications on terminal outcomes IN ADDITION to the submitter. Useful for cc'ing a manager or finance lead.
- requireSubmitNote=true forces a non-empty submitter note on the request. Use this for high-stakes processes where you want context attached to every request (e.g. CFO discount approvals).
- allowSelfApproval=false (default) means the submitter is excluded from the approver pool even if their userId or role matches a step. Turn this on only when you genuinely want self-approval (rare).
- Sandboxable: approval processes (and their steps) are part of the sandbox clone + diff + promote workflow. Build a new process in a sandbox, test it against cloned deals, then promote selected approval-process changes back to production.
- Quota: Pro plan caps at 10 ACTIVE approval processes (isActive=true). Inactive processes don't count. Hit the cap and you'll see a "checkApprovalProcessQuota" 402 response with the upsell.
Troubleshooting
Error messages
entity_locked— The pipeline item / quote you're editing has an active approval pending — it's hard-locked. HTTP 423.no_active_process— Submit-for-approval was called but no active ApprovalProcess matches this entity type. Configure one first.multiple_processes_match— More than one process matched the entity's triggerCriteria. The frontend chooser banner asks the user to pick one.not_authorized_for_step— The approver isn't in the current step's approverUserIds or approverRoles. Re-check happens on every action so role changes mid-flight are caught.approval_quota_exceeded— Pro plan limit of 10 active approval processes hit. Deactivate one or upgrade to Max.self_approval_forbidden— allowSelfApproval is false on the process and the actor is the same person who submitted the request. Set allowSelfApproval=true on the process if you want this behavior.
Plans
Pro plan: 10 active approval processes. Max plan: unlimited. Free / Starter: not available. Permissions: approvals:read (everyone except VIEWER limited subset), approvals:write (TEAM+ — submit + take action), approvals:admin (DIRECTOR+ — process CRUD).
Related
- CPQ — quotes with discount rules (requiresApproval=true) auto-submit through this approval system; pending_approval_choice UI lets users pick when multiple processes match
- Pipeline / Deals — PipelineItem is a top-tier lockable entityType; useful for deal-level approval (e.g. discount thresholds, contract terms)
- AI Actions & Approvals — different feature; that one gates AI-driven actions (sending emails, etc.) by risk level. This one gates human-submitted business processes
- Sandbox Environments — approval processes are sandboxable; test new chains against cloned data before promoting to production
- Notifications — approval-related events (requested, approved, rejected, escalated) fan out to user bells AND email; configurable per-user under Settings > Account > Notifications