Give your agent a card
The safest way to let an agent spend is a single-use card capped at exactly what the purchase should cost. Three calls, and the card cannot be reused or over-spent.
Issue the card
Set amount to the cap and leave single_use at its default of true. Name the merchant so the card shows up correctly in the console.
curl https://api.joomjoo.com/v1/cards \
-H "Authorization: Bearer $AGENTPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"amount": 20, "merchant": "namecheap", "single_use": true, "card_name": "domain renewal"}'const card = await agentpay.cards.create({
amount: 20,
merchant: "namecheap",
single_use: true,
card_name: "domain renewal",
});card = agentpay.cards.create(amount=20, merchant="namecheap", single_use=True, card_name="domain renewal")Keep id and spend_request_id from the response. A 402 insufficient_funds means the cap exceeds the wallet's available: fund the wallet and retry.
Reveal the number at the moment of use
GET /v1/cards/{id} returns the full number, the security code and the expiry. Call it right before typing the card, and never log or store what comes back.
curl https://api.joomjoo.com/v1/cards/card_8f2a... \
-H "Authorization: Bearer $AGENTPAY_API_KEY"const full = await agentpay.cards.reveal(card.id);
// full.number, full.cvc, full.exp ("04/33")full = agentpay.cards.reveal(card["id"])
# full["number"], full["cvc"], full["exp"]Check the result
GET /v1/spend/{spend_request_id} says whether the card was charged.
curl https://api.joomjoo.com/v1/spend/spr_1c9d... \
-H "Authorization: Bearer $AGENTPAY_API_KEY"const spend = await agentpay.spend.get(card.spend_request_id);
// spend.status: not_started | authorized | cleared | declinedspend = agentpay.spend.get(card["spend_request_id"])status | Meaning |
|---|---|
not_started | No charge yet. |
authorized | The merchant charged the card. The money is held and settled is false. |
cleared | The network settled the charge, usually by the next day. settled is true and amount is the final amount. |
declined | The charge was refused. decline_reason says why. A second charge on a used single-use card is the guard working. |
Close what you no longer need
A card you issued and did not use keeps its cap reserved. POST /v1/cards/{id}/close releases the unused reserve back to available.
Rules worth building in
- One card per purchase. Never reuse a card across merchants or orders.
- Cap at the expected total. A single-use card takes exactly one charge; a cap far above the price only ties up the wallet.
- Pay only with the AgentPay card. If the merchant offers a saved card, do not use it. Confirm the last four digits before paying.
- 3DS is handled by AgentPay. If a merchant shows a bank challenge, it is cleared for you; never ask the person for a code.
- Prefer complete a checkout when you can. The card number then never touches your agent at all.
Teach the agent in one paste
Ready tool files for Claude, OpenAI, Gemini and LangChain carry these three calls as tools (issue_card, get_spend_status, plus the checkout tools). See agent skills.