Approvals

List pending Request Approval items and decide with @lunnoa/client.

Build a custom Approvals inbox that mirrors Approvals in Automate (/approvals). List pending items the authenticated user can decide, then approve or reject.

These examples assume an authenticated lunnoa client from the overview.

Permissions

The caller's role needs:

PermissionUsed for
approvals:readList the inbox and read approval state
approvals:decideSubmit approve or reject (eligibility still applies)

A 403 usually means the role lacks these permissions, or the user is not eligible for that request.

List the inbox

approvals.inbox() defaults to pending work. The default status: 'pending' also includes partially_approved (quorum not yet met). Pass status: 'done' for history (items you decided, or terminal approvals you were eligible for).

approvals-inbox.ts
const { items } = await lunnoa.approvals.inbox();const history = await lunnoa.approvals.inbox({ status: 'done' });for (const item of items) {  // item.approvalId, title, status, requiredCount, receivedApprovals,  // executionId, nodeId, workflow?, project?, expiresAt?, canDecide,  // eligibleApprovers?, myDecision?  console.log(item.title, item.status);}

Each item includes enough context to render a review row: title, quorum progress (receivedApprovals / requiredCount), workflow and project names, and links via executionId / nodeId.

eligibleApprovers lists people who can still decide for the current stage (workspace user id, email, and optional name). Role policies expand to current role members. The list is capped at 50 and excludes people who already decided.

eligible-approvers.ts
for (const person of item.eligibleApprovers ?? []) {  console.log(person.email, person.name);}

Decide

Submit approve or reject by approvalId from the inbox. Partial approvals keep the execution paused; a terminal decision (quorum met or reject that terminates) resumes the run.

approvals-decide.ts
const result = await lunnoa.approvals.decide(item.approvalId, {  decision: 'approved', // or 'rejected'  comment: 'Within policy limits.',});// result.terminal === true when the approval step is finished// result.canDecide reflects whether this caller can still decide

When you already know executionId and nodeId (for example from executionPath), you can use the execution-scoped helper instead:

submit-approval.ts
await lunnoa.executions.submitApproval(executionId, nodeId, {  decision: 'rejected',  comment: 'Amount exceeds limit.',});

Both paths use the same eligibility and quorum rules. Do not use executions.submitInput for Request Approval nodes; that webhook is for generic NEEDS_INPUT forms.

Approvals vs queues vs NEEDS_INPUT

SurfaceWhen to use
Approvals (approvals.*)Request Approval workflow nodes with eligible approvers, quorum, and stages
Queues (queueItems.*)Standalone work-item inboxes not tied to a single approval policy
NEEDS_INPUT (executions.submitInput)Generic form pauses; free-form field values, not approve/reject

Next