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
- Your agent makes a request that matches a policy rule with the approval gate variant.
-
Rungate returns
HTTP 202 Acceptedwith aRetry-Afterheader and a structured body describing the pending gate. - Rungate notifies the operator (Slack, email, webhook, or dashboard — wherever the policy says).
- A human approves or rejects.
-
Your agent retries the same request after
Retry-Afterseconds (or on a webhook signal). On approval, the retry succeeds and the run continues. On rejection, the retry returns403 Forbiddenwith the rule context.
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:
- Dashboard — app.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.
- Webhooks —
approval.pendingevent 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.