Approving agent spending

When an agent can pay for things, have a rule or a named person agree to each purchase, keep a running limit per agent, and keep the record of who said yes.

Agents can now pay. Card networks issue stand-in card numbers to agents, payment providers publish toolkits for them, and consumer assistants ask their user to confirm each purchase with a fingerprint. That confirmation answers the right question for someone spending their own money. At work the money belongs to the organisation, so the question is different: who is allowed to agree to this purchase, and what were they shown when they did?

Deliverd answers it with three pieces you may already use, put in the order a payment needs them:

AnswersWhere
The gateMay this purchase happen, and who agrees to it?Each purchase, before it is made
A budgetIs this agent still inside what it may spend this run, day or month?The running total, after the gate and before paying
The recordWho agreed, under which rule, what they saw, and what was paidAfterwards, for finance and audit

1. Register the purchase

Name the purchase as an action, with a schema for its input. editableFields lets an approver lower the amount before agreeing; the agent then pays the corrected figure, because it pays with the input that comes back.

curl -X POST "$DELIVERD_URL/api/v1/actions" \
  -H "Authorization: Bearer $DELIVERD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "actionId": "payments.purchase",
    "name": "Buy something for the organisation",
    "displayTitle": "Pay {{amount}} {{currency}} to {{merchant}}",
    "editableFields": ["amount"],
    "inputSchema": {
      "type": "object",
      "required": ["merchant", "amount", "currency", "description"],
      "properties": {
        "merchant": { "type": "string" },
        "amount": { "type": "number", "minimum": 0 },
        "currency": { "type": "string", "enum": ["EUR", "GBP", "USD"] },
        "description": { "type": "string" }
      }
    }
  }'

2. Write the rules

Rules match on the validated amount and merchant, not on a risk label the agent chose. Above 1,000, two finance leads must agree. Small buys from suppliers you already use go through without anyone. Everything else matches no rule, and no matching rule means a person decides. Gate policy rules are applied on Business Starter, Business and Enterprise; without them every purchase goes to a person.

deliverd.policy.json

{
  "name": "Agent purchases",
  "rules": [
    {
      "name": "Large purchases need two people",
      "priority": 10,
      "effect": "require_human",
      "match": {
        "actions": ["payments.purchase"],
        "input": { "amount": { "gt": 1000 } }
      },
      "params": { "requiredApprovals": 2, "approvers": ["finance-leads"] },
      "explanation": "Over 1,000 two finance leads sign it off."
    },
    {
      "name": "Small buys from our usual suppliers go through",
      "priority": 20,
      "effect": "allow",
      "match": {
        "actions": ["payments.purchase"],
        "input": {
          "amount": { "lte": 50 },
          "merchant": { "in": ["Office Supplies Ltd", "Cloud Hosting Inc"] }
        }
      }
    }
  ]
}

Push it as a draft, try it with deliverd policy simulate, and replay it against the last 30 days of decisions before putting it in force. See Agent policy and Policy as code.

3. Set a budget

A rule looks at one purchase. A budget looks at the total, so forty small purchases that each pass a rule still stop at the limit. An administrator sets it under Agents → Platform overview, for one agent or for every agent, in EUR, GBP, USD (or tokens), per run, day, month, total.

CallScopeDoes
GET /api/v1/budgetsgates:readThe budgets that apply to this agent: switched on, and for every agent or for this one.
POST /api/v1/budgets/{id}/spendgates:write and gates:executeReport a spend before making it. Body: amount in the budget's unit, runId for a budget per run, and a note shown to the person if more is asked for.
allowed
Inside the limit. The spend is recorded; go ahead.
pending
Past the limit. Nothing is recorded, and a person is asked whether to extend the budget and by how much; approvalUrl is where they answer. Report the same amount again later.
refused
Stop. A refusal stands for the rest of that run or period.

4. Put it together

One purchase

// 1. Ask about this purchase. A rule answers, or a person does.
const decision = await deliverd.gate({
  action: "payments.purchase",
  externalId: `purchase:${order.id}`, // the same on a retry
  input: { merchant, amount, currency, description },
});
if (!decision.allowed) return decision.reason;

// 2. Report it against the agent's budget, at the amount that was agreed.
const res = await fetch(`${DELIVERD_URL}/api/v1/budgets/${budgetId}/spend`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${DELIVERD_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    amount: decision.input.amount,
    runId,
    note: `${decision.input.merchant}: ${decision.input.description}`,
  }),
});
const spend = await res.json();
if (spend.status === "pending") return `Waiting for a person: ${spend.approvalUrl}`;
if (spend.status === "refused") return spend.reason;

// 3. Pay, with the input that came back, then say what happened.
const receipt = await pay(decision.input, { decision: decision.decisionToken });
await deliverd.gates.recordExecution(decision.id, { result: { receipt: receipt.id } });
  • Ask the gate first, so the budget is charged the amount a person agreed to, not the one the agent first proposed.
  • Pay with decision.input, not with the object you sent. If an approver lowered the amount, the lower amount is the one they agreed to.
  • Pass decisionToken to the service that makes the payment and have it verify the token before it pays, so a purchase nobody agreed to is refused there too.
  • Record the execution, so the record shows what was paid against what was allowed.

From ChatGPT and other assistants

An assistant connected over MCP calls check_gate with the same action and input, and record_execution after paying; see ChatGPT and dots and MCP server. Tell it in its instructions that purchases for the organisation go through payments.purchase first.

Try it on your own agent.

Free to start, no card. Sign up with Google, Microsoft or GitHub, and the first page after hands you an API key.