Use case
Let your agent ask before it spends money: approvals by link
Oct 27, 2026 · 6 min read
Agents shouldn’t approve their own refunds
Once an agent can issue refunds, buy things or grant access, someone will ask “who said yes?”. The answer can’t be “the model decided”. You want a person to approve, you want to know which person, and you want the agent to wait, not guess, when nobody answers.
The people who should approve are often not in your chat tool: a finance lead on their phone, a client’s manager, the owner of a small business. They need a link they can open anywhere, not a seat in your software.
One request, several approvers
An approval is a createRequest with mode: "approval", a
reason, and the people who decide. Each person gets their own link, so every decision
is attributed to them:
{
"mode": "approval",
"title": "Refund #4821",
"requester": "Ava (the Acme refunds agent)",
"audience": { "kind": "named", "recipients": [
{ "name": "John Smith", "email": "john@acme.com" },
{ "name": "Martin Carpenter", "email": "martin@acme.com" },
{ "name": "Emily Lee", "email": "emily@acme.com" }
] },
"approval": {
"reason": "Refund order #4821, $1,200",
"detail": "Customer reported a damaged item. Photos in ticket T-1042.",
"policy": "majority",
"showOthers": "names",
"verify": "email"
},
"expiresIn": "24h",
"webhookUrl": "https://example.com/hooks/runitby",
"metadata": { "orderId": "4821" }
}
The options that matter
-
policydecides how many approvals it takes:"any","all","majority"or a number. The verdict arrives as soon as it’s decided, without waiting for everyone. -
verify: "email"makes each person confirm a 6-digit code sent to their own address before the buttons work. A forwarded link can’t decide for them. Use it for money, access or deletion. -
showOtherslets each approver see where the others stand, by name or as counts. -
optionsreplaces Approve and Decline with up to four labels, such as “Approve with changes”. The first one counts as approval. -
allowCommentlets people explain their decision; comments come back with it.
Act on the verdict, never on silence
When the policy is met, your webhook gets request.completed with a
verdict: "approved", "declined" or
"expired" (getStatus returns the same). If nobody decides
before the request expires, the verdict is expired. There is no path
where no answer turns into a yes.
// Your webhook handler, after checking the X-Runitby-Signature header
if (event.event === 'request.completed') {
if (event.verdict === 'approved') await refund(event.metadata.orderId)
else await tellCustomer(event.metadata.orderId, event.verdict)
}
Always verify the webhook signature before acting on it: it’s an HMAC-SHA256 of the raw body with your account’s signing secret. The docs have a ready-made check.
Good habits
-
Put the amount and the order in
reason. That’s what people read first, on a small screen. -
Set an
expiresInthat matches the decision. A refund can wait a day; a deploy approval probably can’t. -
Use
metadatato carry your own IDs, so the webhook handler knows what to do without a lookup. -
Call
remindif a deadline is close. It emails pending people again, at most once a day each.
Comparing this with Slack approvals or review inboxes? See Run it by vs approval tools.
Try it with your agent
Free for 100 requests a month, no card. Add the MCP server from Claude Code or Codex with one command, or use the REST API.