Agent spend mandates

Give an agent a signed authority to spend, with caps per transaction and in total, a merchant list and a time window. It is checked before every call to a tool that pays.

After reading this page you will be able to:
  • Declare which tools spend money
  • Give an agent a mandate with per-transaction and total caps
  • Limit a mandate to named merchants and a time window
  • See what an agent spent and what is still held
  • Release a payment that never went through

A spend mandate is a standing authority for one agent. For example: "The buyer agent may spend up to £50 a transaction and £500 in all, with Acme and Globex, until 31 December."

Liyagent checks the mandate before any call to a tool you have declared as paying, and records every decision on a ledger. Liyagent calls no payment rail itself. The paying tool is whatever MCP server you connect, and the mandate decides whether the agent may ask it.

Nothing changes until you declare a paying tool. Agents and tools that are not declared work exactly as before.

1. Declare the tools that pay

PUT /api/mandates-tools
{"tools": {"shop/pay": {"amount_arg": "amount", "currency_arg": "currency",
                        "merchant_arg": "merchant", "mode": "log"}}}
  • amount_arg, currency_arg and merchant_arg name the call's arguments. Dotted paths such as payment.amount work.
  • currency can replace currency_arg when the tool always pays in one currency.
  • mode: log (the default) lets calls through. Any call no mandate covers is audited as mandate.would_deny, so you can see what enforcement would stop.
  • mode: enforce refuses those calls before anything is sent.

2. Give the agent a mandate

POST /api/mandates/buyer
{"currency": "GBP", "per_txn": "50", "total": "500",
 "merchants": ["acme", "globex"], "not_after": "2026-12-31T23:59:59Z"}
  • An empty merchants list allows any merchant.
  • not_before defaults to now.
  • not_after is required, because a mandate always ends.

The terms are signed with the platform keyring (a detached JWS over their RFC 8785 form). The signature is checked again on every use. A cap changed in the database without being re-signed is refused and audited as mandate.tampered. It is never honoured.

What happens on a paying call

Before anything is sent, Liyagent looks for one of the agent's active mandates that:

  • is in the call's currency;
  • is in its time window;
  • allows the merchant;
  • covers the amount per transaction;
  • has room in its total.

The mandate row is locked while this is decided, so concurrent payments cannot overspend it together. The amount is reserved on the ledger and then resolved:

The callLedger status
Answered without an errorsettled
Not sent, or answered with an errorreleased
Cut off mid-call, or left running as a taskuncertain

An uncertain entry still counts against the total, because the payment may have gone through. Once you know it didn't, release it:

POST /api/mandates/ledger/{entry}/release

If no mandate covers the call, it is refused with the reason, for example "mdt_… allows at most 50.0000 GBP a transaction". The refusal is audited as mandate.denied.

See and revoke

GET    /api/mandates?agent=buyer           # caps, spent, held, remaining, verified
GET    /api/mandates/buyer/{ref}/ledger
DELETE /api/mandates/buyer/{ref}           # revoke

Audit

Every decision writes a row:

  • mandate.create and mandate.revoke;
  • mandate.reserved, mandate.settled, mandate.released and mandate.uncertain;
  • mandate.denied and mandate.would_deny;
  • mandate.tampered;
  • mandate.tools.save.

Each row names the agent, the tool, the amount, the currency, the merchant and the mandate.