Agents
An agent is a tenant-owned principal — an automation or AI agent that acts on your behalf with its own scoped API key. An agent key authenticates through the same path as a developer key but is a constrained principal: it can never hold the elevated scopes, no matter what you grant it.
Scopes an agent key can never hold (they're stripped at mint time):
agents:manage— an agent can't create or manage other agentskeys:manage— an agent can't mint keyspolicies:write— an agent can't author policytreasury:write— an agent can't author treasury rules (standing money-movement config would outlive the agent's own spend caps and key)
Suspending an agent immediately constrains it: on its next action its key is
declined with AGENT_SUSPENDED (enforced at the auth layer — the kill-switch
takes effect right away, not after a cache expiry).
On top of scopes, an agent carries spend limits — per-payment, daily,
weekly, and monthly USD caps checked on every agent-initiated payment. Over
the cap, the payment is declined with AGENT_LIMIT before it's ever proposed
on-chain.
Limits are checked at payment creation, against spend that has already
settled. That catches the common sequential case, but it is not a hard
guarantee: several payments created before any settles can collectively exceed
a cap, and a payment settled out-of-band (a wallet owner executing the proposed
Safe transaction directly) isn't yet counted. Robust enforcement (settlement
reconciliation + reserve-at-create) is an in-progress increment — until then
treat caps as a guardrail. This is why /v1/capabilities reports
agents: false.
Agents act on your existing wallets today. Agent-owned Safes (the "agent proposes, human approves" flow) and an activity feed are a later increment.
Key endpoints
| Method | Path | Scope | Purpose |
|---|---|---|---|
POST | /v1/agents | agents:manage | Create an agent. |
GET | /v1/agents | agents:read | List agents. |
GET | /v1/agents/{id} | agents:read | Get one agent. |
POST | /v1/agents/{id}/suspend | agents:manage | Suspend (decline on next action). |
POST | /v1/agents/{id}/reactivate | agents:manage | Reactivate a suspended agent. |
POST | /v1/agents/{id}/keys | agents:manage | Mint a scoped key (returned once). |
GET | /v1/agents/{id}/limits | agents:read | Read spend limits. |
PUT | /v1/agents/{id}/limits | agents:manage | Set spend limits. |
POST | /v1/agents/{id}/wallet | agents:manage + wallets:write | Provision the agent's dedicated Safe (202). |
POST | /v1/agents/{id}/cards | agents:manage + cards:issue | Issue a Safe-funded card bound to the agent. |
GET | /v1/agents/{id}/activity | agents:read | Merged audit timeline (payments + card auths + policy decisions). |
See the REST reference for full request/response schemas.
Spend limits
Set per-payment / daily / weekly / monthly USD caps; each is enforced on every
payment the agent initiates. Only the fields you send change; an explicit
null clears a cap. Caps must be positive — use suspend to stop an agent, not
a zero cap.
- curl
- TypeScript
- CLI
curl -X PUT https://api.sandbox.safebank.ai/v1/agents/ag_123/limits \
-H "X-SafeBank-Api-Key: $SAFEBANK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "perTransactionUsd": "50.00", "dailyUsd": "500.00", "monthlyUsd": "5000.00" }'
await sb.agents.setLimits(agent.id, {
perTransactionUsd: '50.00',
dailyUsd: '500.00',
monthlyUsd: '5000.00',
})
sb agents limits set ag_123 --per-txn 50 --daily 500 --monthly 5000
sb agents limits show ag_123
When an agent payment would breach a cap, POST /v1/payments is declined
(HTTP 403) with AGENT_LIMIT — before the payment is proposed on-chain. A
suspended agent is declined with AGENT_SUSPENDED. Spend counts against the
rolling windows only once a payment completes, so cancelled or failed
payments never consume budget.
The same windows gate the agent's card synchronously at authorization time — card spend and completed payments accumulate in one budget, so an agent can't route around its caps by switching rails. The card path is the hard-enforced rail; the payment pre-flight remains best-effort (async settlement caveats documented above).
Wallet + card (the synchronous spend rail)
Give an agent its own dedicated Safe and a Safe-funded card:
sb agents wallet <agentId> --owner 0xYourSignerAddress
# once DEPLOYED, fund it (faucet / on-ramp), then:
sb agents card <agentId>
- The Safe is tenant-owned (purpose
AGENT, one per agent) — its owners are your signers. The agent holds no on-chain key; a platform-managed agent signer is a separate, security-reviewed increment. agents:manageis agent-forbidden, so an agent key can never provision its own wallet or card.- Card caps omitted from the body are seeded from the agent's own limits, and card authorizations are decided synchronously by the in-prod decision engine — this card is the agent's hard-enforced spend rail (unlike the best-effort payment pre-flight).
- Agent caps are enforced live on every card authorization. The
agent's own limits (
PUT /v1/agents/{id}/limits) gate each authorization atomically — a per-agent lock serializes the read-decide-record span, so concurrent authorizations (or a racing payment) can never jointly exceed a cap. Declines surface asAGENT_LIMIT/AGENT_SUSPENDED; an error resolving the windows fails closed (AGENT_LIMIT_UNAVAILABLE); approval consumes the budget immediately. Reversals credit the original auth's window, both legs atomically. This is whatcapabilities.agents: trueclaims (sandbox — production reports false until live agent card issuance ships). - One wallet and one card per agent — a second request returns
409 AGENT_WALLET_EXISTS/409 AGENT_CARD_EXISTS. - Card-level caps seeded at issuance remain a per-card copy: changing
the agent's limits later updates the agent gate immediately but
does not rewrite the card's own
CardLimitrow (update it viaPUT /v1/cards/{id}/limits). An explicitnullcap in the card body clears that cap (it does not fall back to the agent's column). - A suspended agent can't be provisioned (
409 AGENT_SUSPENDED).
Audit trail
Every agent action is observable in one place:
sb agents activity <agentId> --limit 50
Returns a newest-first merge of the agent's payments, card authorizations (with decline reasons), and policy decisions (sha-pinned). Declines show up here too — the audit trail is how you see the engine saying no.
Create an agent and mint its key
- curl
- TypeScript
- CLI
# 1. Create the agent.
curl -X POST https://api.sandbox.safebank.ai/v1/agents \
-H "X-SafeBank-Api-Key: $SAFEBANK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "ap-bot" }'
# 2. Mint a scoped key — the plaintext is returned ONCE.
# Requesting a forbidden scope is silently dropped, not granted.
curl -X POST https://api.sandbox.safebank.ai/v1/agents/ag_123/keys \
-H "X-SafeBank-Api-Key: $SAFEBANK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "ap-bot ci", "scopes": ["wallets:read", "payments:create"] }'
import { SafeBank } from '@safebank/sdk'
const sb = new SafeBank({ apiKey: process.env.SAFEBANK_API_KEY! })
const agent = await sb.agents.create({ name: 'ap-bot' })
const key = await sb.agents.createKey(agent.id, {
name: 'ap-bot ci',
scopes: ['wallets:read', 'payments:create'],
})
console.log(key.key) // sb_test_… — store it now, shown once
// Later: park the agent.
await sb.agents.suspend(agent.id)
sb agents create --name ap-bot
sb agents key ag_123 --name "ap-bot ci" --scopes wallets:read,payments:create
sb agents suspend ag_123
sb agents reactivate ag_123
The minted key is a first-class API key — it appears in sb keys list (as a
tenant key with an agent binding) and can be revoked or rotated like any other,
but it keeps its agent constraints across rotation.