Skip to main content

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 agents
  • keys:manage — an agent can't mint keys
  • policies:write — an agent can't author policy
  • treasury: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.

Spend limits are a pre-flight guardrail, not a hard cap (yet)

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.

Agent-owned wallets

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

MethodPathScopePurpose
POST/v1/agentsagents:manageCreate an agent.
GET/v1/agentsagents:readList agents.
GET/v1/agents/{id}agents:readGet one agent.
POST/v1/agents/{id}/suspendagents:manageSuspend (decline on next action).
POST/v1/agents/{id}/reactivateagents:manageReactivate a suspended agent.
POST/v1/agents/{id}/keysagents:manageMint a scoped key (returned once).
GET/v1/agents/{id}/limitsagents:readRead spend limits.
PUT/v1/agents/{id}/limitsagents:manageSet spend limits.
POST/v1/agents/{id}/walletagents:manage + wallets:writeProvision the agent's dedicated Safe (202).
POST/v1/agents/{id}/cardsagents:manage + cards:issueIssue a Safe-funded card bound to the agent.
GET/v1/agents/{id}/activityagents:readMerged 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 -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" }'

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:manage is 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 as AGENT_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 what capabilities.agents: true claims (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 CardLimit row (update it via PUT /v1/cards/{id}/limits). An explicit null cap 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

# 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"] }'

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.