How to give an AI agent a payment card
Your agent needs to pay for API calls. You want it to pay on its own, only the merchants you allow, only the amounts you allow, with a record of what it meant to pay for.
Cards run with test money, and you try payments in the Try a test payment panel on the card's dashboard page.
How it fits together
Your agent
├─ signs a payment intent with its Ed25519 key
│ → POST /api/agents/{id}/intents (checked and logged)
└─ pays with its card
→ each payment is checked against the card's limits and merchants
The two are separate today: the intent log records what the agent meant to pay for, and the card's rules decide each payment. A card payment doesn't check for a matching intent yet.
Step 1: Get an API key
Sign up at sorenpay.com/signup → Business. Then create a key
in Developers → API keys with write:agents, read:agents,
write:cards and read:cards.
Step 2: Register the agent
Generate the key pair yourself, so the private key never leaves your environment, and send only the public key:
openssl genpkey -algorithm Ed25519 -out agent.key
openssl pkey -in agent.key -pubout -out agent.pub
curl -X POST https://sorenpay.com/api/agents \
-H "Authorization: Bearer $SOREN_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d "$(jq -n --arg pem "$(cat agent.pub)" \
'{agentName: "research-assistant", protocols: ["TAP"], publicKeyPem: $pem}')"Step 3: Give it a card with limits
const res = await fetch("https://sorenpay.com/api/cards", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SOREN_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
holderKind: "agent",
holderLabel: "research-assistant",
monthlyLimitUsdCents: 100_00, // $100 a month
perTxnLimitUsdCents: 10_00, // $10 a payment
allowedMccs: ["5734", "7372"], // software, computer services
allowedMerchants: ["openai", "anthropic"],
}),
});
const { card } = await res.json();
console.log("card ending", card.last4);Step 4: Log a signed intent before paying
The intent is canonical JSON (keys sorted, no whitespace) signed with the agent's Ed25519 key. The protocols page has the format. A valid intent answers:
{ "accepted": true, "log_id": "…", "intent_id": "…", "next": { "message": "…" } }A bad or expired signature answers 401 signature_invalid (with a reason),
and a reused intent_id answers 422 intent_replay. Invalid submissions are
logged too.
Step 5: Review the log
GET /api/agents/{id}/intents returns the agent's intents, including
rejected ones. The dashboard shows them on the agent's page.
What this gives you today
- One card per agent, each with its own merchants and limits, checked on every payment.
- A signed record of what each agent meant to pay for, with replays refused.
Not built yet: card payments that require a matching intent, and webhooks for intents or card payments.
Related
- Agents overview
- Protocols: TAP / MPP / ACP wire format
- Card capabilities
- Disputes + chargebacks