Approvals: policy to enforcement
An approval request captures the proposed action, snapshots the matching policy, notifies reviewers, and records their decisions. Your server checks the result immediately before performing the protected operation.
Create and check a request
const request = await incld.approvals.create(
{
resourceType: "release",
resourceId: "rel_42",
action: "publish",
title: "Publish August release",
metadata: { environment: "production" },
expiresAt: "2026-08-25T00:00:00Z",
},
{ idempotencyKey: "release:rel_42:publish:v1" },
)
const check = await incld.approvals.check({
resourceType: "release",
resourceId: "rel_42",
action: "publish",
})
Here incld
is a server client constructed with the authenticated organization and user scope. The API supplies those trusted identities rather than accepting them from the request payload.
The check returns approved, the current status or none, and an optional approval ID.
Policies and matching
await incld.approvalPolicies.create({
resourcePattern: "release:*",
allowedApprovers: ["user_ops_1", "user_ops_2", "user_security"],
mode: "quorum",
requiredApprovals: 2,
allowSelfApproval: false,
})
| Mode | Resolution rule |
|---|---|
| any | The first valid approval resolves the request. |
| all | Every allowed approver must approve. |
| quorum | requiredApprovals valid approvals are required, capped by the allowed reviewer count. |
An organization-specific policy wins over a project-global policy with the same matching specificity. Its ID, revision, reviewer set, mode, threshold, and self-approval setting are copied into the request. Later policy edits affect only new requests. Policy organization scope is immutable. A project-global fallback can only be defined with a deliberately unscoped administrator client; tenant-scoped clients cannot mutate it.
Choose policy scope deliberately
Decisions and lifecycle commands
await incld.approvals.approve(
approvalId,
"Risk checks complete",
{ idempotencyKey: `approval:${approvalId}:user_ops_1` },
)
await incld.approvals.reject(approvalId, "Missing evidence")
await incld.approvals.cancel(approvalId, "No longer needed")
await incld.approvals.revoke(approvalId, "Conditions changed")
| State | How it is reached |
|---|---|
| pending | Request created and waiting for policy resolution. |
| approved | The policy threshold is satisfied. |
| rejected | A valid reviewer rejects the request. |
| cancelled | The requester/application cancels pending work. |
| expired | expiresAt passes while the request remains pending. |
| revoked | A previously approved request is invalidated. |
Reviewer notifications
Configure Slack Incoming Webhooks and verified Amazon SES email destinations from the Approvals area of the dashboard. New requests and lifecycle changes enqueue delivery; failed notification attempts are retried independently from approval state.
Notification sinks are project-wide
| Destination | Setup |
|---|---|
| Slack | Paste an Incoming Webhook URL. @incld probes it before marking the destination connected. |
| Add an address, deliver the signed verification link, and verify before notifications are sent. | |
| Sender | Production email requires INCLD_EMAIL_FROM and configured Amazon SES credentials/region. |