Skip to Content
PoliciesApprover Groups

Approver Groups

When a policy rule’s require_human_review action carries an approval_requirement, Keel treats the matching permit as a challenge: the request is held until an authorized principal approves or rejects it. Approver Groups are the named, governed surface that determines who can satisfy each requirement.

This page documents the typed ApprovalRequirement schema, the per-tier capability matrix, the EnterpriseApproverGroup model, the authorization rules that decide whether a given approver can satisfy a given requirement, and the per-approval audit record that anchors the decision in the trust stack.

For the action that produces a challenge in the first place, see require_human_review.

Typed ApprovalRequirement schema

A policy rule may carry an approval_requirement of the following shape. Only the fields relevant to the chosen type should be set.

{ "type": "<requirement_type>", "timeout_seconds": 3600, "role": "<org_role>", "group_id": "<uuid>", "group_slug": "<slug>", "team_id": "<uuid>", "team_slug": "<slug>", "user_id": "<uuid>", "service_principal_id": "<uuid>", "min_approvals": 1, "separation_of_duties": false, "delegated_approval": false, "external_workflow": {} }

Requirement types

typeDescriptionRequired field(s)Minimum plan
org_roleAny organization member holding the named role (owner, admin, member, or viewer)roleBusiness
userA specific dashboard useruser_idBusiness
approver_groupA named approver group (organization-scoped or project-scoped)group_id or group_slugEnterprise
teamAny member of a named Keel teamteam_id or team_slugEnterprise
service_principalA dedicated API key carrying the approval scopeservice_principal_idEnterprise

Each type evaluates membership at the moment of approval, not at the moment the policy was authored. If a user is removed from a group between the time the challenge was created and the time it is resolved, that user is no longer eligible to satisfy the requirement.

Common controls

These fields apply across requirement types.

FieldTypeDefaultNotes
timeout_secondsinteger (> 0)requiredThe challenge expires after this many seconds. Expired challenges resolve as denied.
min_approvalsinteger (≥ 1)1Independent approvals required before the permit clears. Values greater than 1 require Enterprise.
separation_of_dutiesbooleanfalseThe approver must differ from the permit subject. Enterprise-only.
delegated_approvalbooleanfalseMetadata flag for downstream orchestration. Enterprise-only.
external_workflowobject | nullnullArbitrary metadata for handoff to an external workflow (for example, a ticket ID). Enterprise-only.

Legacy require_attestation

Existing rules that use the older require_attestation shape continue to parse and execute unchanged. Internally, they resolve to a single-approver attestation flow. No migration is required, and there is no value to author for require_attestation in new rules — prefer approval_requirement with type: "user" or type: "org_role".

Plan tier matrix

Approval governance is gated independently from the policy authoring level. Custom approval and attestation gates require Business or higher; advanced approver-group features require Enterprise.

PlanWhat may be authored on a require_human_review rule
StarterNo approval or attestation gates in custom policies
GrowthNo approval or attestation gates in custom policies
BusinessLegacy require_attestation; typed org_role or explicit user with min_approvals = 1. No separation_of_duties, delegated_approval, external_workflow, approver groups, teams, or service principals.
Enterprise (entitlement: organization_dashboard_enabled)All requirement types; min_approvals > 1; separation_of_duties; delegated_approval; external_workflow

Authoring violations are rejected at policy write time with the approval_governance_tier_exceeded error code. The error envelope identifies the offending rule index, the requirement field that exceeded the tier, and the upgrade target plan. This is a domain-specific code with a richer envelope than the generic plan.upgrade_required.

EnterpriseApproverGroup model

Approver groups are the named, governed roster that an approver_group requirement targets. Groups are administered through the organization dashboard.

Scope

  • Organization-scoped when the project belongs to an organization. The same group can be referenced by any policy rule in any project under that organization.
  • Project-scoped for personal projects (organizations of one). The group is local to that project.

Each scope ships with a reserved default group named Compliance Officers (slug compliance-officers). The default group exists so a freshly provisioned organization or personal project can author Enterprise approval rules without first creating a group from scratch.

Members

Each group can hold:

  • Explicit user members — dashboard users named directly
  • Service-principal members — API keys with the approval scope
  • Mapped members — users imported from an SSO/SCIM group, a WorkOS group, or an existing Keel team via the group-mapping table

Mapped membership lets identity-provider push or polling jobs keep group membership current without manual edits. When a user is removed upstream, the next sync removes them from the Keel group, and they become ineligible to satisfy approval requirements that target the group.

Group administration

Groups are managed through the organization dashboard surface. Listing, creating, updating, and deleting groups all require an authenticated dashboard session with organization-level authorization. Group membership is checked at approval resolution time, not at challenge creation time.

Authorization model

When a permit challenge is resolved — via POST /v1/permits/{permit_id}/attest or the dashboard attest action — Keel evaluates whether the acting principal satisfies the typed requirement. Two principal types can resolve a challenge:

  • Dashboard session. Used to satisfy org_role, user, approver_group, and team requirements. The session’s user identity, organization role, and group/team memberships are evaluated against the requirement.
  • API key with the approval scope. Used to satisfy service_principal requirements and to act as a service-principal member of an approver_group. Execution-scope and client-scope keys do not satisfy approval requirements — only keys with the dedicated approval scope.

Two important rules tighten the model:

  • Broad write access does not satisfy a typed requirement. A dashboard user with project-write access cannot resolve an approver_group challenge unless they are a member of that group. Authorization evaluates against the requirement type and its target, not against general write permission.
  • Separation of duties is enforced at resolution. When separation_of_duties is true, the service layer asserts that the approver’s identity differs from the permit subject before recording the approval. A database constraint provides a belt-and-braces guarantee for the same condition.

For dashboard sign-off, the legacy attestor field is treated as the policy requirement label, not as client-supplied proof. The dashboard route resolves the approving or rejecting user from the authenticated dashboard session and authorizes that user against the typed requirement.

Audit record per approval

Every approval or rejection records the following fields:

  • the actor’s user ID and email, or the service-principal API key ID
  • the authorization basis and subject (for example, “member of group compliance-officers”)
  • the rationale and any evidence URL provided
  • the request IP address and user agent
  • the policy ID and version captured at sign-off time
  • the typed approval requirement snapshot
  • the governance event with hash-chain sequence number and record hash

These fields appear on the permit record itself and in the access-review and incident-evidence signed exports. They are part of the tamper-evident governance record for the permit.

Sample challenge flow

1. A request matches a policy rule with action: "require_human_review" and an approval_requirement of type: "approver_group", group_slug: "compliance-officers", min_approvals: 2, separation_of_duties: true. 2. Keel issues the permit with decision: "challenge" and reason_code: "policy.review_required". The permit is held; no provider call is made. 3. Two distinct members of the "compliance-officers" group, neither of whom is the permit subject, each call POST /v1/permits/{permit_id}/attest to record their approval. 4. After the second approval, Keel records the approval audit and transitions the permit to allow. The application proceeds with its provider call (permit-first) or Keel dispatches (execution-backed surfaces). 5. If the timeout_seconds elapses before two approvals are recorded, the challenge expires as denied and the permit captures the timeout in its audit record.

What this surface does and does not claim

  • Approval requirements gate human review on a single permit. They do not gate write operations on the rest of Keel — for example, policy edits or signing-key rotations.
  • Group membership is evaluated at approval-resolution time. A user who was a member at challenge creation but was removed before resolution is ineligible.
  • Service-principal keys must carry the dedicated approval scope to resolve challenges. Execution and client keys cannot.
  • Separation-of-duties enforcement compares the approver identity to the permit subject. It does not enforce broader role separation across the wider workflow.
  • Expanded approval workflows are an active area of work. Customers using approver groups today will see the expanded surface ship without breaking changes — the typed requirement schema, the audit record fields, and the existing approver-group model are stable.
Last updated on Edit this page on GitHub