Available

Ask before a sensitive action

Bind a token to one action, ask AuthFI whether the agent may do it, and wait for a person to approve when a rule requires one.

Before an AI agent does something that matters, such as sending money or merging code, it asks AuthFI. The answer is allow, deny or approval_required. Use this guide to add that question to your agent, and to wait for a person when the answer is approval_required.

Before you begin

You need:

Bind a token to the action

Exchange the agent's access token for a transaction token. A transaction token names one action and its details, and it lasts 60 seconds.

POST

/apis/regional/v1/ai/auth/transaction-token
curl https://acme.authfi.io/apis/regional/v1/ai/auth/transaction-token \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "transfer:accounts",
    "resource": "account:4821",
    "context": { "amount": 1200, "currency": "USD", "account_type": "business" }
  }'

$ACCESS_TOKEN is the access token the agent received when it signed in.

{
  "data": {
    "transaction_token": "eyJhbGciOiJFZERTQSIs…",
    "txn": "0b7d6a52-…",
    "token_type": "N_A",
    "expires_in": 60
  }
}

The action, resource and context are frozen inside the token. It cannot be reused for a different amount or a different account.

Before it binds a token, AuthFI checks the details against the limits on the agent's capability for the action. Details outside a limit are refused with 403 and constraint_violated, and no token is issued. See How a limit is evaluated.

export TRANSACTION_TOKEN=<the transaction_token from the response>

Ask whether the action is allowed

Send the transaction token with the same action, resource and context.

POST

/apis/regional/v1/agents/authorize
curl https://acme.authfi.io/apis/regional/v1/agents/authorize \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agt_7f3a9c21",
    "transaction_token": "'"$TRANSACTION_TOKEN"'",
    "action": "transfer:accounts",
    "resource": "account:4821",
    "context": { "amount": 1200, "currency": "USD", "account_type": "business" }
  }'

agt_7f3a9c21 is the agent ID. The console shows it as "Client id" on the agent's Connect tab.

With the rule from Limit what an agent may do, a transfer of 1,000 or more waits for a person:

{ "data": { "decision": "approval_required", "approval_id": "3f0c2b6e-…", "reason": "policy_step_up" } }
DecisionWhat the agent does
allowGoes ahead
denyStops. reason says why.
approval_requiredWaits for a person, and polls with approval_id

The limits on the capability are checked here again. If a limit was lowered after the token was bound, the answer is deny. Each reason is listed in The decision and its reason.

Wait for a person

When the decision is approval_required, the agent's owner or its approval group is asked. The person answers in the console:

  1. Open Approvals in the sidebar.
  2. Find the request under "Agents waiting to do something". The card shows the agent that asked and the details of the action.
  3. Choose Allow to approve, or Refuse to deny.

Allow asks for confirmation: the agent is told yes and goes ahead straight away, with the arguments shown. The console then shows "Approved" or "Refused".

Meanwhile the agent polls for the result, using the approval_id from the previous step.

GET

/apis/regional/v1/agents/approvals/{approval_id}
{
  "data": {
    "approval_id": "3f0c2b6e-…",
    "status": "approved",
    "requesting_action": "transfer:accounts",
    "approver_user_id": "5a1e…",
    "created_at": "2026-10-09T10:02:11Z",
    "resolved_at": "2026-10-09T10:02:48Z",
    "expires_at": "2026-10-09T10:17:11Z"
  }
}
StatusMeaningWhat the agent does
pendingNobody has answered yetKeeps polling
approvedA person approvedGoes ahead
deniedA person deniedStops
expiredNobody answered in timeStops

Check that it works

Run the three steps with an action that one of your project rules sends to a person. That is a policy with the effect "Require step-up".

  1. The authorize response has "decision": "approval_required" and an approval_id.
  2. Poll the approval. Its status is pending.
  3. In the console, open Approvals. Under "Agents waiting to do something", choose Allow on the request and confirm.
  4. Poll again. The status is approved, and approver_user_id and resolved_at are set.

Open an approval request directly

An agent can also open an approval request without the authorize step. Use this for an action that your own code decides is sensitive.

POST

/apis/regional/v1/agents/request-approval
FieldMeaning
agent_idThe agent
client_assertion, client_assertion_typeA fresh signed assertion, as at sign-in
requesting_actionWhat it wants to do, in words the approver will read
contextDetails to show the approver
timeout_secondsHow long to wait, up to 3600. The default is 15 minutes.

A client assertion is the short statement the agent signs with its private key to prove who it is. Build one as you do in Give an AI agent an identity.

The response is 202 Accepted with the approval, in the pending status. Poll it as in Wait for a person.

How an approval is decided

  • A person decides. The decision is made by a signed-in person on the Approvals page. An agent cannot approve through its own credentials.
  • A policy decides when to ask. An action needs approval when a project policy with the effect "Require step-up" matches it.
  • No answer is a denial. A request that nobody answers expires. An expired request can never be approved.
  • Everything is recorded. The request, who decided, and when are in the audit record.

Troubleshooting

What you seeCauseFix
400 with invalid_bodyThe request body is missing a field or is not valid JSONSend every field shown in the step, as JSON
401 with unauthorizedThe access token is missing, expired or revokedSign in again and send the new access token
401 from the authorize endpointThe agent is unknown or revokedCheck agent_id. A revoked agent stays revoked.
deny with a reasonA limit, a rule or a missing grant stops the actionLook up the reason in the list of reasons
The approval's status is expiredNobody answered in timeStart again from Bind a token to the action

Next steps