Everything you need to integrate capped.ai into your AI agent stack. Issue virtual cards, set spending policies, and handle real-time authorizations.
Get running in under 5 minutes. Create an account, issue your first agent card, and attach a spending policy.
lim_.Evaluate whether an agent's spend is permitted under its current policy. Call this before your agent executes any purchase or payment.
curl -X POST https://capped.ai/api/v1/actions/check \
-H "Authorization: Bearer lim_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent_abc123",
"action_type": "purchase",
"amount_usd": 120,
"reason": "Compute instance for data pipeline"
}'| agent_id | string | Required | Your agent's ID from the dashboard |
| action_type | string | Required | purchase, transfer, or any custom string |
| amount_usd | number | Required | USD value of the spend |
| reason | string | Optional | Agent's reason — stored in audit log |
{
"status": "approved", // approved | blocked | requires_approval | reduced
"decision_id": "dec_xyz789",
"reason": "All policy checks passed",
"requires_human_approval": false,
"allowed_amount_usd": null, // set when status is "reduced"
"policy_checks": [
{ "rule": "per_txn_limit", "result": "pass", "message": "$120 ≤ $500 limit" },
{ "rule": "daily_volume", "result": "pass", "message": "$320 of $1,000 daily cap" }
]
}| Status | Meaning | |
|---|---|---|
| approved | — | Spend passes all checks. Proceed. |
| blocked | — | Spend violates a rule. Do not proceed. |
| requires_approval | — | Above approval threshold. Wait for human review in the dashboard. |
| reduced | — | Amount exceeded per-transaction limit. allowed_amount_usd has the capped value. |
After a requires_approval decision is approved by a human in the Approvals dashboard, call this to record it as executed.
curl -X POST https://capped.ai/api/v1/actions/dec_xyz789/execute \
-H "Authorization: Bearer lim_your_api_key"Use this helper to gate any spend in your agent code before the card is charged.
import requests
API_KEY = "lim_your_api_key"
BASE = "https://capped.ai"
def check_spend(agent_id, amount_usd, reason=""):
"""Check if a purchase is allowed under the agent's spending policy."""
res = requests.post(
f"{BASE}/api/v1/actions/check",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"agent_id": agent_id,
"action_type": "purchase",
"amount_usd": amount_usd,
"reason": reason,
},
)
return res.json()
result = check_spend(
"agent_abc123",
amount_usd=120,
reason="EC2 instance for data pipeline",
)
if result["status"] == "approved":
# proceed with payment / API call
pass
elif result["status"] == "requires_approval":
print("Queued for human review:", result["decision_id"])
elif result["status"] == "reduced":
print("Capped at:", result["allowed_amount_usd"])
else:
print("Blocked:", result["reason"])Wrap capped.ai as a LangChain tool so your agent automatically checks policy before any spend.
from langchain.tools import tool
import requests
@tool
def spend_with_policy(amount_usd: float, reason: str = "") -> str:
"""Gate a purchase through the capped.ai policy engine before charging the card."""
res = requests.post(
"https://capped.ai/api/v1/actions/check",
headers={"Authorization": "Bearer lim_your_api_key"},
json={
"agent_id": "agent_abc123",
"action_type": "purchase",
"amount_usd": amount_usd,
"reason": reason,
},
).json()
if res["status"] == "blocked":
return f"Spend blocked: {res['reason']}"
elif res["status"] == "requires_approval":
return f"Queued for approval (id: {res['decision_id']})"
elif res["status"] == "reduced":
return f"Capped: proceed with ${res["allowed_amount_usd"]} instead"
return f"Approved: ${amount_usd} approved"capped.ai evaluates every card authorization in real time before it clears. When an agent's card is used, the network calls our authorization webhook and capped.ai responds with approved: true or approved: false within 2 seconds based on the active policy.
# Authorization webhook (preconfigured — no setup required):
https://capped.ai/api/webhooks/authorize
# capped.ai evaluates every authorization in real time:
# 1. Check per-transaction limit
# 2. Check daily volume cap
# 3. Check approval threshold
# 4. Respond approved: true or falseNo additional code needed — the webhook is preconfigured when you create an agent card.
Configure policies in the dashboard under Policies. Each policy is attached to one agent card.
| Rule | Unit | Behaviour |
|---|---|---|
| Per-transaction limit | USD | Single card charge cannot exceed this value |
| Daily volume cap | USD | Total approved spend per calendar day |
| Requires approval above | USD | Charges above this value queue for human review |
| Max transactions/day | count | Hard cap on daily approved transaction count |
| Value | Effect |
|---|---|
| paper_only | Trade actions are simulated. No real execution. (Default) |
| live_disabled | All trade-type actions are immediately blocked. Payments still allowed. |
| blocked | Hard blocks all trade actions at the policy level, regardless of other rules. |
Get an AI agent making purchases through a capped.ai virtual card in under 15 minutes.
Before you start: Complete KYB verification in your account settings. Card provisioning is gated on approval.
export AGENT_CARD_NUMBER="4111111111111234"
export AGENT_CARD_EXPIRY="12/28"
export AGENT_CARD_CVV="123"import { LimitMD } from '@capped/sdk'
import { tool } from '@langchain/core/tools'
import { z } from 'zod'
const limitmd = new LimitMD({ apiKey: process.env.LIMIT_MD_API_KEY })
const purchaseTool = tool(
async ({ amount, merchant }) => {
const token = await limitmd.tokens.create({
cardId: process.env.LIMIT_MD_CARD_ID,
amount,
merchant,
expiresIn: 300,
})
return { token: token.value, expiry: token.expiry }
},
{
name: 'make_purchase',
description: "Make a purchase using the agent's virtual card",
schema: z.object({
amount: z.number().describe('Amount in cents'),
merchant: z.string(),
}),
}
)https://api.sandbox.capped.ai for testing. No real money moves.Fetch an agent's virtual balance and card status. Use this to display remaining budget or confirm a card is active before making purchases.
curl https://capped.ai/api/agents/agent_abc123 \
-H "Authorization: Bearer lim_your_api_key"{
"agent": {
"id": "agent_abc123",
"name": "research-agent",
"mode": "paper",
"status": "active",
"virtualBalanceUsd": 4832.50,
"cardStatus": "open",
"stripeCardId": "ic_xyz789",
"policies": [{ "perTransactionLimitUsd": 500, "dailyVolumeLimitUsd": 1000 }]
}
}| Field | Meaning |
|---|---|
| virtualBalanceUsd | Remaining paper balance in USD — reflects all approved spend against the starting budget. |
| cardStatus | open (active) · paused (frozen) · cancelled |
| mode | paper · approval_only · live_disabled — see Agent mode section |
# Freeze
curl -X PATCH https://capped.ai/api/agents/agent_abc123/card \
-H "Authorization: Bearer lim_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "action": "freeze" }'
# Unfreeze
curl -X PATCH https://capped.ai/api/agents/agent_abc123/card \
-H "Authorization: Bearer lim_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "action": "unfreeze" }'List the 50 most recent card transactions for an agent, newest first. Works in both sandbox and production.
curl https://capped.ai/api/agents/agent_abc123/transactions \
-H "Authorization: Bearer lim_your_api_key"{
"transactions": [
{
"id": "txn_abc123",
"agentId": "agent_abc123",
"amount": 12000,
"currency": "USD",
"merchant": "AWS Marketplace",
"status": "approved",
"declineReason": null,
"createdAt": "2026-06-19T10:22:14Z"
}
]
}Use the sandbox base URL (https://api.sandbox.capped.ai) and send POST /api/v1/actions/check — each call creates a synthetic transaction that appears in this list immediately. Sandbox data resets every 24 hours; no real money moves.
| Status | Meaning |
|---|---|
| approved | Authorization passed all policy checks. Card was charged. |
| declined | Authorization rejected. See declineReason for the specific code. |
| reversed | Authorization was reversed by the merchant or capped.ai. |
| settled | Authorization settled (1–3 business days after approval). |
When a card transaction is declined, the API returns a machine-readable code. Find the code in Dashboard → Transactions → [transaction] → Decline reason or in the error response body.
{
"error": {
"code": "SPEND_LIMIT_EXCEEDED",
"message": "Transaction of $150.00 exceeds the per-transaction limit of $100.00.",
"transactionId": "txn_xyz789"
}
}| Code | Meaning | Fix |
|---|---|---|
| SPEND_LIMIT_EXCEEDED | Exceeds per-transaction limit. | Raise the card limit or split the purchase. |
| CYCLE_LIMIT_EXCEEDED | Card has hit its per-cycle cap. | Increase the per-cycle limit or wait for reset (1st of month). |
| LIFETIME_LIMIT_EXCEEDED | Card exhausted its lifetime allowance. | Cancel and provision a new card with a higher limit. |
| INSUFFICIENT_FUNDS | Account balance too low. | Add funds via Dashboard → Billing. Enable auto-reload. |
| CARD_FROZEN | Card was manually frozen. | Unfreeze via Dashboard → Cards or PATCH /api/agents/{id}/card with {"action":"unfreeze"}. |
| CARD_CANCELLED | Card permanently cancelled. | Provision a new card. Cancellation is irreversible. |
| MERCHANT_BLOCKED | Merchant category blocked by card policy. | Update merchant controls in Dashboard → Cards → Merchant Controls. |
| CURRENCY_MISMATCH | Transaction currency differs from card currency. | Use a card configured for that currency. |
| CARD_NOT_ACTIVE | Card not yet activated. | Activate via Dashboard → Cards. |
| EXPIRED_CARD | Card's expiry date has passed. | Provision a replacement card. |
| INVALID_CVV | CVV does not match. | Verify the CVV matches the dashboard value. |
| PLATFORM_LIMIT_EXCEEDED | Account-level platform limit hit. | Request a limit increase via Settings → Account. |
| VELOCITY_EXCEEDED | Too many transactions in a short window. | Spread transactions over time or contact support. |
| SUSPECTED_FRAUD | Automated fraud detection flagged this. | Freeze the card and contact security@capped.ai immediately. |
| PROVIDER_ERROR | Underlying card network error. | Retry once. If persists after 5 minutes, contact support with the transaction ID. |
Every capped.ai API request requires a Bearer token in the Authorization header.
Authorization: Bearer lim_<64 hex characters>| Prefix | Environment | What it does |
|---|---|---|
| lim_ | All environments | 64-character hex string. There is no live/test prefix split — environment is determined by which base URL you call. |
Production base URL: https://api.capped.ai
Sandbox base URL: https://api.sandbox.capped.ai
A sandbox key sent to the production URL returns 401.
# Wrong — no "Bearer" prefix
curl -H "Authorization: lim_yourkey" https://capped.ai/api/v1/actions/check
# Wrong — "Token" instead of "Bearer"
curl -H "Authorization: Token lim_yourkey" https://capped.ai/api/v1/actions/check
# Correct
curl -H "Authorization: Bearer lim_yourkey" https://capped.ai/api/v1/actions/check| Error | Cause and fix |
|---|---|
| 401 Unauthorized — missing or malformed token | Authorization header is missing or incorrectly formatted. Ensure you include Bearer before the key. |
| 401 Unauthorized — invalid API key | Key was revoked, belongs to a different account, or has trailing whitespace. Copy the key fresh from Dashboard → API Keys. |
| 401 Unauthorized — environment mismatch | Calling the wrong base URL (e.g. sandbox endpoint when targeting production). Use https://capped.ai for production. |
| 403 Forbidden — insufficient permissions | Key is valid but lacks permission for this action. Generate a key with the required scopes. |
If you see transactions you did not authorize, or suspect your API key has been leaked, act immediately.
curl -X PATCH https://capped.ai/api/agents/CARD_ID/card \
-H "Authorization: Bearer lim_yourkey" \
-H "Content-Type: application/json" \
-d '{ "action": "freeze" }'capped.ai is a financial product. Before you can provision your first virtual card, your business must pass Know Your Business (KYB) verification. This is a regulatory requirement and takes 1–3 business days.
| Status | Meaning |
|---|---|
| Not started | You haven't submitted yet. |
| Pending | Under review. No action needed. |
| Approved | You can provision cards. |
| Additional info required | Check your email — we need more documentation. |
| Rejected | Contact support to understand the decision. |
While KYB is pending, you can use the sandbox environment. No KYB required for sandbox.
capped.ai gives you three independent controls over how much a virtual card can spend. All three apply simultaneously.
| Limit type | Effect | When to use |
|---|---|---|
| Per-transaction limit | Maximum a card can charge in a single transaction. Any individual charge above this is declined. | Capping what an agent can spend on any single action. |
| Per-cycle limit | Maximum spend in a billing cycle (calendar month). Resets on the 1st of each month. | Monthly budget control per agent. |
| Lifetime limit | Total the card can ever spend across its entire existence. Once hit, the card is permanently over-limit. | One-time or project-scoped agents with a fixed budget. |
A transaction is approved only if it passes all configured limits. Omitting a dimension means no limit is enforced for that dimension.
Configure all three limits in the dashboard: Dashboard → Cards → [select card] → Edit Limits. Changes take effect immediately.
capped.ai sends webhook events to your endpoint when transactions occur, cards change state, or other account events happen.
X-Capped-Signature header. The signature is HMAC-SHA256(signingSecret, requestBody), hex-encoded.import crypto from 'crypto'
function verifyWebhook(body: string, signature: string, signingSecret: string): boolean {
const expected = crypto
.createHmac('sha256', signingSecret)
.update(body, 'utf8')
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signature, 'hex')
)
}
app.post('/webhooks/limitmd', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-capped-signature'] as string
if (!verifyWebhook(req.body.toString(), sig, process.env.LIMIT_MD_WEBHOOK_SECRET!)) {
return res.status(401).send('Invalid signature')
}
const event = JSON.parse(req.body.toString())
res.status(200).send('ok')
// process event async...
})200 within 5 seconds. Move processing to a background queue. If your endpoint returns non-200 or times out, capped.ai retries with exponential backoff (5s, 30s, 2m, 10m, 1h) up to 5 attempts.| Event | Triggered when |
|---|---|
| transaction.authorized | A card authorization is approved or declined. |
| transaction.settled | An authorization settles (1–3 business days after authorization). |
| transaction.reversed | An authorization is reversed (e.g. refund or merchant void). |
| card.created | A new card is provisioned. |
| card.frozen | A card is frozen. |
| card.unfrozen | A card is unfrozen. |
| card.cancelled | A card is permanently cancelled. |
| limit.exceeded | A spend limit was exceeded (transaction declined). |
| account.funded | Account balance increased (ACH settled or card top-up). |
capped.ai operates on a prepaid model. Your account balance is drawn down as your agents make purchases.
| Method | Settlement time | Notes |
|---|---|---|
| ACH bank transfer | 1–3 business days | No fee. Go to Dashboard → Billing → Add Funds → Bank Transfer. |
| Debit or credit card | Immediate | Small processing fee applies. Go to Dashboard → Billing → Add Funds → Card. |
Auto-reload prevents your agents from stalling due to a low balance. When your balance drops below a threshold you set, capped.ai automatically pulls from your linked bank account via ACH.
Best practice: Set your auto-reload threshold to at least 3× your average daily spend so the 1–3 day ACH settlement window is covered.
// Check agent balance via GET /api/agents/:id
const res = await fetch('https://capped.ai/api/agents/agent_abc123', {
headers: { 'Authorization': 'Bearer lim_yourkey' },
})
const { agent } = await res.json()
// agent.virtualBalanceUsd — remaining paper balance in USDInvite teammates to your capped.ai account and control what each person can do using roles.
| Role | What they can do |
|---|---|
| Admin | Everything: create and cancel cards, set spend limits, manage team members, view billing, rotate API keys, initiate fund transfers. |
| Viewer | Read-only: see cards, transactions, and account balance. Cannot create cards, change limits, invite users, or access billing. |
Invite a team member via Dashboard → Settings → Team → Invite Member. Role changes take effect immediately. Removing a team member revokes their access immediately — rotate any API keys they created separately.
The mode field on an agent — and tradingMode on its attached policy — controls how the policy engine treats every incoming action. Set it when you create an agent or update one at any time.
| Value | When to use | Effect |
|---|---|---|
| paper | Development and simulation | All policy checks run; no real card charges are executed. Paper-trade positions track simulated P&L. |
| approval_only | Maximum human oversight | Every action is queued for human approval regardless of amount or policy thresholds. |
| live_disabled | Temporarily suspend trading | trade actions are blocked immediately. payment, api_spend, and wallet_transfer continue unaffected. |
| blocked | Hard trading suspension (policy-level) | All trade actions rejected immediately. Set on policy.tradingMode; the agent mode field does not accept this value. |
live_disabled enforcement: when policy.tradingMode is live_disabled, the policy engine returns status: "blocked" for any request where action_type is trade. Non-trade actions — payment, api_spend, wallet_transfer — bypass this check and are evaluated normally against all other policy rules.
const BASE = 'https://capped.ai'
const API_KEY = process.env.CAPPED_API_KEY
// Create an agent in paper mode (safe default for development)
const res = await fetch(`${BASE}/api/agents`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'research-agent',
mode: 'paper', // paper | approval_only | live_disabled
virtualBalanceUsd: 5000,
}),
})
const { agent } = await res.json()
// agent.id, agent.mode === "paper"
// Gate every trade behind a human before going live
await fetch(`${BASE}/api/agents/${agent.id}`, {
method: 'PATCH',
headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ mode: 'approval_only' }),
})
// Immediately halt all trading while keeping payments live
await fetch(`${BASE}/api/agents/${agent.id}`, {
method: 'PATCH',
headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ mode: 'live_disabled' }),
})| Plan | Limits | API burst |
|---|---|---|
| Free | 1 card · 500 transactions/mo | — |
| Builder | 10 cards · unlimited | 100 req/min |
| Enterprise | Unlimited cards | Custom |
Questions, bugs, or feedback — email hello@capped.ai. Enterprise support includes a dedicated Slack channel.
For billing questions: billing@capped.ai. For security incidents: security@capped.ai.