Skip to main content
CONCEPTS

Approval gates

When a tool call matches a policy rule that requires human review, Rungate returns HTTP 202 and pauses the run. The agent retries the same request later; on approval the retry succeeds.

Some tool calls should require human sign-off before they execute — a refund over $500, a destructive database operation, an email to a regulator. Rungate's approval gates are the canonical way to enforce that without your agent needing webhook handlers or websocket connections.

The flow

  1. Your agent makes a request that matches a policy rule with the approval gate variant.
  2. Rungate returns HTTP 202 Accepted with a Retry-After header and a structured body describing the pending gate.
  3. Rungate notifies the operator (Slack, email, webhook, or dashboard — wherever the policy says).
  4. A human approves or rejects.
  5. Your agent retries the same request after Retry-After seconds (or on a webhook signal). On approval, the retry succeeds and the run continues. On rejection, the retry returns 403 Forbidden with the rule context.
The agent doesn't change its code path. Same request, same headers, same SDK call. The only branch is "got 202 → wait → retry."

The 202 response

HTTP/1.1 202 Accepted
Content-Type: application/json
Retry-After: 5

{
  "status": "awaiting_approval",
  "context": {
    "gate_id": "gate_8xk2m",
    "run_id": "run_customer_refund_2026_04_26",
    "rule": "refund:over-$500",
    "proposed_action": {
      "tool": "issue_refund",
      "args": { "order": "ord_2H4p", "amount_usd": 1240.00 }
    },
    "approver_channel": "slack://#customer-ops",
    "expires_at": "2026-04-26T15:48:12Z"
  }
}

Store the gate_id if you want to query the gate's state directly (e.g. to abort early on a long-pending gate). The agent doesn't need it for the retry — Rungate matches the retry to the gate via the run + request fingerprint.

Retrying

Send the same request, same headers. Do not re-engineer a new request on retry — Rungate fingerprints the paused request and the retry must match.

Wait at least Retry-After seconds before the first retry. After that, exponential backoff is fine. Rungate doesn't rate-limit retries against an awaiting gate — checks are cheap. Suggested cadence: 5s, 10s, 30s, 60s, then every 5 minutes until expires_at.

Resolution outcomes

Approved

Retry returns 200 OK with the provider's native response. The run continues as if the gate had never been there. The audit trail records the gate, who approved, when.

Rejected

Retry returns 403 Forbidden:

{
  "error": {
    "code": "approval_rejected",
    "message": "Approval gate rejected by reviewer.",
    "context": {
      "gate_id": "gate_8xk2m",
      "rule": "refund:over-$500",
      "rejected_by": "[email protected]",
      "rejected_at": "2026-04-26T15:30:00Z",
      "reason": "Amount exceeds standard limit; route to manager."
    }
  }
}

Not retryable. The agent should surface the rejection to its caller and stop the workflow (or branch — e.g. propose a smaller amount).

Expired

Gates have an expiration (default 1 hour, configurable per rule). Retries after expiration return 410 Gone:

{
  "error": {
    "code": "gate_expired",
    "message": "Approval gate expired without resolution.",
    "context": {
      "gate_id": "gate_8xk2m",
      "expired_at": "2026-04-26T15:48:12Z"
    }
  }
}

The agent can either give up or start a new gate by issuing a fresh request (which will trigger a new 202 + a new gate).

Operator side

Operators see pending gates in:

  • Dashboardapp.rungate.dev/approvals lists all pending gates. Click in to see the proposed action, approve or reject with optional reason.
  • Slack — gates can be configured to post into a Slack channel with approve/reject buttons inline.
  • Email — same content, with magic-link buttons.
  • Webhooksapproval.pending event fires; build your own UI / on-call integration. See Webhooks.

Multiple notification channels can fire for the same gate. The first reviewer to act resolves it; subsequent attempts get a "gate already resolved" response.

Configuring a gate rule

Approval gates are policy rules. In a policy:

{
  "rule": "refund:over-$500",
  "match": {
    "tool": "issue_refund",
    "args.amount_usd": { "$gte": 500 }
  },
  "action": "gate",
  "approver_channel": "slack://#customer-ops",
  "expires_in_seconds": 3600
}

The matcher language supports literal equality, comparison operators ($gt, $gte, $lt, $lte), regex ($regex), and array containment ($in). See the policy reference in the dashboard for the full schema.

Idempotency

From the agent's perspective, gate resolution is idempotent: the same request returns the same outcome. If you accidentally retry after approval, you get the response. If you retry after rejection, you get the rejection. There's no "the gate fired twice" failure mode.