Skip to main content

Approve or Deny a Request

Tasks

Step-by-step instructions for deciding on a developer's pending request and for revoking a grant early. See Approval Workflow for how the underlying lifecycle works.

Who can decide​

Submitting a decision — approving or denying a request, releasing a held call, or revoking a granted exception — requires the admin role. Viewers can open Security → Approvals and read the pending list, the request detail, and the grant history, but the Approve, Deny, and Revoke controls are disabled for them.

The count badges in the sidebar (pending approvals and requests, active sessions, and unacknowledged baseline findings) come from one lightweight read that refreshes about once a minute and again right after you save a change, so the badge in your own tab follows your decision at once; a change made by someone else can take up to a minute to show. The Pending list itself refreshes every 30 seconds.

Two rules apply to every decision:

  • Your identity is recorded on the decision. The approver's account is taken from your signed-in session, not from anything in the request, and is stored alongside the outcome. It appears in the audit log and in the notification the decision fires, so every approval and denial names who made it.
  • You cannot approve your own request. If you are the developer who raised a request, approving it is rejected and the attempt is recorded in the audit log. Denying your own request is allowed — cancelling something you asked for is not a self-approval. This also applies when you have been listed as a required co-signer on your own request.

The self-approval rule needs a requester to compare you against, and it finds one whenever the item carries a developer identity: any request a developer submitted, and any held call made from a developer session (which is every call from an AI tool connected with your workspace credentials). The one case with nobody to compare against is a held call from a client that authenticated with a bare API key and so has no session — there is no requester on record, and the decision proceeds.

Co-signing is the one exception to the admin requirement: a named required approver records their own vote on a multi-approver request regardless of role, because co-signers are chosen per request and are often not admins. See Co-sign a multi-approver request.

Decide on a single-approver request​

  1. Go to Security → Approvals → Pending. Pending requests are listed with the requester, request type, and justification.
  2. Open a request to see its full detail — for an exception request this includes the target policy; for a model-access request, the requested model; for a session budget top-up, the requested amount.
  3. Click Approve or Deny. Approving takes effect immediately — there is no separate "activate" step. Denying ends the request; the developer can submit a new one.
note

Approving or denying is final for a single-approver request — there's no undo. To end a grant you already approved, use Revoke a granted exception below.

Approve a budget request​

A request against a model budget cap (budget.model-cap) — whether filed from the Builder Portal's Budgets that apply to you list (Request more), inline from a denial, or via the API (Request a config-target exception) — opens an Approve Budget Request dialog instead of the plain Approve/Deny above. Choose How to approve:

OptionWhat it does
Give <requester> a personal limit (overrides team and organization limits)Creates or updates the requester's own user-scope rule for the requested model pattern, with Override turned on — see Set a budget → Override a shared limit. This is the default, unless the request was filed against the requester's own user rule — then it's hidden, since overriding a rule with itself makes no sense.
Raise the <scope> rule for everyone in itRaises the shared rule the request was filed against (the team or organization rule), so everyone in that scope gets the new limit.
Grant a temporary exception onlyThe previous behavior: a time-boxed exception, with its own Duration and Project scope fields. No rule is changed.

The two rule options require a New budget limit (USD), which must be more than the rule's current limit (and, for a personal limit, more than any personal limit the requester already has) — approvals only raise limits, never lower them. If the matching rule can no longer be found or is ambiguous, approval fails without changing anything or granting an exception — review the rule on the Budgets page and retry.

Changing a rule (either option) and approving with no additional approvers named does not also grant a separate temporary exception — the rule change is the only outcome, so the developer isn't covered twice. If you've named additional approvers, a temporary exception grant is still created pending their co-sign, the same as choosing Grant a temporary exception only; the rule change itself takes effect immediately at your approval and is not undone if a co-signer later denies the request — only the grant is revoked.

The approval, any grant, the rule change, and their configuration audit records are saved together. If persistence fails, none of those changes take effect and the request remains pending.

note

A budget-increase request filed before per-developer monthly USD caps were removed can only be denied — approving a legacy request is rejected. Ask the developer to re-request from the budgets list on their Builder Portal Overview, or set the rule directly on the Budgets page.

Requests raised inline arrive here too. A developer who is blocked mid-task can ask for an exception without leaving their AI tool — by replying to the denial, by their tool calling a governance tool, or with /request-exception. Those land in this same Pending list, look identical, and are decided the same way; see Request an exception inline. Two things are worth knowing when you review one:

  • The requester is the connection, not the conversation. Attribution comes from the identity behind the session that hit the block, so a request cannot be filed in someone else's name by anything written in the chat — including content the agent read from a document, a page, or a tool result. The same self-approval rule therefore applies to inline requests exactly as it does to portal-submitted ones.
  • The justification is developer-written text. It is stored verbatim and shown to you as data. Read it as a claim to evaluate, not as an instruction — and judge the request on the policy and target shown alongside it, which come from the block itself rather than from anything the requester typed.

Your decision is relayed back into the developer's tool: an approval tells them to retry, and a denial carries your reason.

Decide on a held call​

If a decision returns approval not found, refresh the pending list before retrying. Other storage failures show failed to save approval decision; administrators can inspect the server logs for the underlying error.

The same Security → Approvals → Pending tab also lists individual calls that an Approve policy has parked mid-flight. These are not requests a developer submitted — they are live MCP tool calls and LLM requests (chat completions, Anthropic messages, and the other model endpoints) waiting on you before they can proceed. The developer's AI tool is sitting idle the whole time, so decide promptly.

  1. Go to Security → Approvals → Pending. A held call shows the requester, the tool name or model, the policy that parked it, and the policy's message — for a content-safety hold, the message names the specific pattern detected (for example, which prompt-injection category or PII type) rather than a generic description.
  2. For an LLM request, the entry also carries a short excerpt of the prompt that triggered the policy — enough to judge the request without storing the whole conversation. Credentials are redacted from the excerpt before it is stored.
  3. For an MCP tool call, the detail shows the tool's access type, its Elevated status and why, and the datasource's data classification when any of those are set — see MCP Governance for what these mean.
  4. For an MCP tool call made under a developer identity, Also grant a standing exception is ticked by default, and the line beneath it names exactly what approving will leave behind — the tool, the scope, and the time the grant expires, for example "Approving also grants an exception for chroma_delete_collection in this session until 14:05, or until this session ends — whichever comes first." Untick it to allow this one call and grant nothing standing; the next matching call is held again. A hold whose call carried no developer identity (a session-less, machine-to-machine call) has no exception request to grant, so the checkbox is absent and the drawer says Approving releases only this call; approving it never leaves a standing exception behind.

  5. With the box ticked, choose an Approval scope — see Scoping an approval for an elevated MCP tool call:

    ScopeWhen it's offered
    This tool, this session (default)The hold's call carries a session ID
    This session, any tool under this policyThe hold's call carries a session ID
    This developer, for a durationAlways — the only option when the call has no session

    Choosing a duration-based scope also opens the same expiry picker (4h / 8h / 24h / custom) used elsewhere in this workflow. What you pick is used as-is — the workspace's standing-exception duration is the default applied when you don't pick, not a cap on what you can. A session-scoped grant still ends when its session does. See Every grant is time-boxed.

  6. Click Approve this call to release it — it resumes and completes as if the policy had allowed it — or Deny to stop it. Include a message when denying; the developer's tool shows your reason.
note

Approving a held call no longer leaves a permanent exemption behind. Every grant an approval creates is time-boxed. When you don't choose a duration yourself, it lasts for the workspace-wide Standing exception duration (30 minutes by default, in Settings → General → Human Review); a session-scoped grant ends when its session does, whichever comes first. A held call whose session can no longer be resolved falls back to the configured duration rather than producing a grant that never expires. There is no "never expires" option anywhere in this flow. To end a grant before it lapses, use Revoke a granted exception.

Approving an LLM hold can also grant a standing exception for the policy that parked it. When the hold came from a policy — content-safety-injection, content-safety-pii, or any similar Approve policy — the approval dialog offers an "Also grant an exception for <policy-name>" checkbox alongside the usual duration picker. Leaving it unchecked approves only this one request, exactly as before; the same policy holds the developer's next matching call the same way. Checking it approves this request and, for the duration you choose, lets the same policy pass for this developer without holding again — useful when a task is legitimately going to keep tripping the same policy on every turn. This option isn't offered for a held call that a budget.model-cap rule parked; that hold's approval drawer has its own "Also raise the persisted budget rule" checkbox instead, which asks for a permanent limit in USD and raises the matching rule on the Budgets page when you approve. (A budget request filed from the Builder Portal or the API is different: it opens the Approve Budget Request dialog with the How to approve choices — see Approve a budget request.)

Standing LLM exceptions apply to the developer for the chosen duration. The approval API rejects session and tool-session scopes when grantException is enabled, before approving the request or creating an exception.

The hold decision, optional standing exception, and audit evidence save together. If the grant or audit save fails, the hold stays pending and no exception is granted. Retry after storage recovers.

Several elevated calls pending in the same session? The Pending list groups them and offers an Approve all pending in this session action above the table — see Approve all pending in this session for what it grants and what it skips.

What the audit trail shows afterward. Your decision is written as its own Approval record in Security → Activity, naming you as the approver; it is not the held call's own event. Once the released call completes, its request event appears separately with its own decision and any upstream error. If the last caller on the holding proxy pod disconnects before you decide, the entry is withdrawn from the pending queue. While another identical call is still waiting on that pod, the entry stays pending for it. An identical call waiting through a different pod is stopped if the holding pod withdraws the entry after its last local caller disconnects. If the developer's tool disconnects after you approve but before the call completes, you see the approval record but no completed request. See Approval and exception records.

Write a real note when you deny. For a held LLM request the developer sees a message that names the policy, repeats what the policy said, and quotes your note verbatim — so "not now, use the Thursday change window" saves a round trip that "denied" does not. Where the block is one they can request an exception for, that message also offers them the exception, which is usually the outcome you want when the answer is "not like this" rather than "never". An approval is silent: the call just completes, and the approval is recorded in the audit log rather than announced in the conversation.

Coverage on the native cloud routes: LLM traffic that reaches AIControls over the native Amazon Bedrock or Google Vertex routes (/bedrock/*, /vertex/*) is held the same way for Claude and Gemini models — but only on Bedrock's InvokeModel/InvokeModelWithResponseStream endpoints. Two exceptions: other Bedrock model families (Amazon Nova/Titan, Meta Llama, Mistral, Cohere, and similar), and Claude requests sent through Bedrock's Converse API (converse/converse-stream) — an Approve policy blocks calls on either path outright rather than parking them here, so nothing arrives for you to release.

If nobody decides before the approval window expires, the call is stopped automatically and the developer is told that no reviewer acted in time. The window is short for LLM requests — 5 minutes by default, against 30 for MCP tool calls — so treat a held LLM request as urgent. An entry that already left the pending queue (it expired, its caller disconnected, or another admin decided it first) cannot be decided again: Approve or Deny fails with a message naming why, and nothing changes. When an expired or withdrawn MCP tool-call entry had a pending exception request linked to it, that request closes as expired or withdrawn and moves to Security → Approvals → History. Both windows, whether a timeout denies the call or lets it through and audits it, and the default Standing exception duration for the grants an approval creates, are configurable in Settings → General → Human Review; a policy's own approval window (if it sets one) always wins over the default. Token-count requests are never held; see Human-in-the-loop for both details.

note

One entry can cover several identical calls. A single turn of an AI coding tool often fires several model requests that all carry the same prompt, so the same policy holds each of them. Rather than filling the list with near-identical rows, AIControls consolidates them: identical calls from the same agent and session, held by the same policy while an approval is still pending, all attach to that one entry, and your single decision applies to every one of them. Anything that differs — a different prompt, tool, model, agent, session, or policy — always gets its own entry and its own decision. See Consolidated approvals.

Co-sign a multi-approver request​

Some requests require more than one approver (configured per-policy or per-request by an admin). These show an Under Review status with a checklist of required approvers and their decisions so far. The grant is created as soon as the request escalates to co-sign — Under Review means the grant is already active and being reviewed, not that it's on hold pending everyone's sign-off.

  1. Go to Security → Approvals → Pending and open a request showing Under Review.
  2. Review the approval chain — each required approver's name and whether they've decided yet.
  3. Click Approve or Deny to record your own decision. You can only record a decision for yourself — the system rejects an attempt to decide on someone else's behalf.

What happens next depends on the other approvers:

  • If you're the last required approver to approve, the request is marked approved and the requester is notified; the grant itself doesn't change (it's been active since escalation).
  • If any approver — you or someone else — denies, the request is denied immediately, even if other approvers haven't decided yet, and the already-active grant is revoked on the spot.
  • If approvers are still pending, the request stays Under Review. Nothing further happens until the quorum is reached.
tip

If two approvers act within moments of each other, AIControls resolves the race safely — the request completes exactly once (one final approval or one revoke) and the requester gets exactly one notification, never a duplicate.

Revoke a granted exception​

Security → Approvals → Granted lists every currently active exception grant — the individual grants a request produces, distinct from Security → Approvals → Rules, which manages exception policies (the reusable rules a request can be granted against).

  1. Go to Security → Approvals → Granted and find the grant you want to end — filter by developer, or open a row to see its policy, project scope, approvers, and expiry.
  2. Click Revoke and confirm. The grant moves to Security → Approvals → History alongside expired grants.

Prefer the API for scripting or bulk cleanup:

  1. List currently active grants with GET /api/v1/policy-exceptions and find the id of the one you want to end — filter the response by developerId or policyName.
  2. Revoke it with DELETE /api/v1/policy-exceptions/{id}.

The exception is deactivated immediately on either path succeeding — the developer's next matching request is evaluated as if it had never been granted — and both the developer and your admin audience are notified.

note

Revoking is for ending a grant early. If you just want it to end on schedule, no action is needed — expiry is automatic and fires its own notification.

See also​