---
title: Approvals queue
description: Give it an owner, measure the response time, and understand why an expired approval is worse than a denied one.
---

:::warning The queue is the thing most likely to rot
`ENFORCE` with approval rules and an unwatched queue is just a slower kind of
blocked. Approvals reaching `APPROVAL_EXPIRED` are actions that silently did not
happen, and nobody gets an alert about silence.

Before you promote to `ENFORCE`, decide: who owns it, what the response-time
expectation is, and what happens out of hours.
:::

## Work it

`/approvals` in the console, with a pending-count badge.

Each card carries the full decision context, which is deliberate:

- The agent, its delegation chain, and what it is acting for
- The tool, the capability, the parameters, the amount
- The policy and rule that required approval, with the conditions that matched
- The risk level, score and every signal that fired, each with its explanation
- The agent's recent action history
- The `purpose` the agent supplied
- The blast radius, for a destructive tool

:::security An approver without context approves everything
Which is worse than having no approval step, because now there is a record
saying a human reviewed it.

If approvers are routinely approving without reading, the problem is one of two
things: the cards do not carry enough context, or the rule fires too often to be
meaningful. Both are fixable. Neither is fixed by asking people to read more
carefully.
:::

## Deny with a real reason

```bash
curl -X POST $CP/v1/approvals/apr_01ABC/deny \
  -H "Authorization: Bearer $KEY" \
  -H 'content-type: application/json' \
  -d '{ "reason": "Order ord_8812 was already refunded on 1 Oct" }'
```

The reason reaches the agent as the message on `APPROVAL_DENIED`, so it can
report something meaningful upstream instead of "the request failed". "Denied"
on its own wastes the one chance to explain.

## Measure it

```promql
histogram_quantile(0.95,
  rate(veefore_approval_decision_duration_seconds_bucket[1h]))
```

Your approval SLA, measured rather than asserted.

:::danger If p95 exceeds your rule TTLs, your approvals are expiring systematically
Default TTL is 900 seconds. A p95 of 1,400 seconds means a large share of
approvals never get decided — and each one is an action that did not happen with
nothing to indicate it.

Fix one of: the TTL, the staffing, or the rule that fires too often.
:::

Alert on it:

```yaml
- alert: VeeforeApprovalsNotBeingWorked
  expr: |
    histogram_quantile(0.95,
      rate(veefore_approval_decision_duration_seconds_bucket[1h])) > 900
  for: 30m
  labels: { severity: warning }
```

## Route it somewhere people are

Subscribe a webhook to `approval.requested` and push it into chat.

```bash
curl -X POST $CP/v1/webhooks \
  -H "Authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{
    "name": "approvals-to-chat",
    "url": "https://hooks.internal.example.com/veefore-approvals",
    "event_types": ["approval.requested"]
  }'
```

:::warning Verify the signature, then read state from the API
A webhook is a notification, not an authorization. Approving must happen through
the console or the API by an authenticated person holding `approval:approve`.

Do not build a chat bot that approves on a reaction without re-authenticating.
That turns chat membership into approval authority.
:::

## Set the TTL per rule

```json
{
  "then": {
    "decision": "REQUIRE_APPROVAL",
    "reason": "Refunds above ₹1,000 require a human approver.",
    "approval": {
      "routingMode": "SINGLE",
      "requiredApprovals": 1,
      "approverRoleKeys": ["APPROVER"],
      "ttlSeconds": 900
    }
  }
}
```

Match the TTL to how fast a human can realistically respond **to that
particular thing**, not to a round number.

| Situation | TTL |
| --- | --- |
| Business-hours refund review | 900–3600 |
| Overnight batch needing morning sign-off | 43200 |
| Production deployment during a change window | 1800 |
| Anything a customer is waiting on | as short as your staffing supports |

An approval that always expires is a rule that always blocks, dressed up as a
rule that asks.

## Routing

| Mode | Use when |
| --- | --- |
| `SINGLE` | One named person decides. The default |
| `GROUP` | Anyone on duty may decide. Best for an on-call rota |
| `QUORUM` | Two or more must agree. For irreversible or large-blast-radius actions |
| `SEQUENTIAL` | Ordered sign-off. For a genuine escalation chain |

`requireStepUp: true` forces the approver to re-authenticate at the moment of
approving. Use it for anything irreversible — it means an unattended, still-valid
browser session is not enough.

When several rules both require approval, the effects merge conservatively: the
maximum `requiredApprovals`, the minimum `ttlSeconds`, the union of approver
roles, and `requireStepUp` true if any rule asked for it.

## Separation of duties

By default `SECURITY_ADMIN` does **not** hold `approval:approve`, and neither
does `DEVELOPER`. Only `APPROVER`, `ADMIN` and `OWNER` do.

That is deliberate: the person who wrote the rule requiring approval should not
be the person satisfying it. Grant the overlap explicitly if your organization
genuinely wants it, having decided to.

## Tuning the rules themselves

Review monthly. For each approval rule ask:

**Is it firing too often?** A rule that fires on most actions is not a control,
it is a tax, and approvers will start rubber-stamping. Narrow the condition or
raise the threshold.

**Is it never firing?** Either the threshold is wrong or the rule is describing
something that does not happen. Both are worth knowing.

**Is it being worked around?** If a human is regularly doing the thing manually
after an approval is denied, the rule is wrong, not the control. Find out what
it should have said.

**Is the right person deciding?** An approver who always defers to someone else
is not the approver; the person they ask is. Change the routing.

## Related

:::cards

:::card Approvals | /concepts/approvals
Routing, quorum, expiry and the binding guarantee.
:::

:::card Handle approvals | /build/handle-approvals
The agent side.
:::

:::card Risk Center | /operate/risk-center
The usual reason an approval appears.
:::

:::card Observability | /operate/observability
The approval SLA metric.
:::

:::
