AgentCard API
Direct REST reference for AgentCard user accounts — everything the agentcard CLI does, callable from your own code. If you're a platform issuing cards on behalf of your customers with an ak_live_ API key, use the Partner API instead.
Base URL: https://agentcard.ai
The model
Request a card credential for a purchase, choosing the payment source:
- Issued card (default): AgentCard mints a single-use virtual Visa card capped to the requested amount, funded by a hold on your saved payment method. Instant, live for 7 days; you're charged only if a merchant uses it, and unused holds release automatically.
- Connected card (experimental): a one-time, merchant-scoped credential minted from one of your own enrolled Visa/Mastercard cards via network tokenization (Visa Intelligent Commerce first). Each purchase is approved in the browser with a passkey. You can enroll multiple cards and choose one per purchase.
The two sources have separate account-level spend limits, plus a shared hard daily ceiling ($200/day for normal direct accounts, UTC).
Authentication
All endpoints take a session token as a bearer header:
Authorization: Bearer <session-token>
Get a token by logging in with the CLI (npx agentcard signup --email you@example.com); it's stored at ~/.agentcard/config.json as token. Sessions expire — re-run signup to refresh. Requests from locked accounts return 401.
Conventions
- All bodies and responses are JSON. Send
Content-Type: application/json. - Errors are
{ "error": "<message>" }, usually with a machinecodefield. Branch oncodewhere present. - Amounts are integer cents unless a field says otherwise.
- Endpoints under
/api/agentic/*are feature-flagged; they return404when the connected-card program is disabled.
Common error codes on spend attempts (both sources unless noted):
| HTTP | code | Meaning |
|---|---|---|
| 400 | amount_over_max | Over the per-card cap ($150 during beta) |
| 400 | no_payment_method | No saved funding card (issued source only) — run agentcard setup |
| 400 | over_spend_limit | Account budget exceeded; payload has spendLimitCents/spentCents/remainingCents, plus limitSource: "connected" for connected purchases |
| 403 | account_blocked | Account blocked by fraud controls |
| 403 | account_locked | Account locked — contact support |
| 403 | identity_verification_required | Complete KYC first (see Identity below) |
| 403 | daily_spend_limit_exceeded | Shared daily ceiling reached; resets at UTC midnight |
| 402 | hold_failed | Your bank declined the hold (issued source only) |
| 500 | issue_failed | Hold succeeded but minting failed; the hold is released automatically |
| 503 | platform_unavailable | Issuance temporarily paused |
Account
GET /api/me
Profile and setup state: email, paymentMethod ({id, last4, brand, expMonth, expYear} or null), spendLimitCents, agentEmail, walletAddress, locked/lockedReason, and identity fields.
GET /api/limits
Both spend budgets:
{
"spendLimitCents": 10000,
"spendLimitPeriod": "all_time",
"spentCents": 3300,
"remainingCents": 6700,
"connected": {
"spendLimitCents": 10000,
"spentCents": 900,
"remainingCents": 9100
}
}
Top-level fields are the issued cards + wallet budget; connected is the connected-card budget. Issued usage counts captured spend plus live unexpired card exposure; connected usage counts retrieved one-time credentials plus live unexpired approvals.
POST /api/limits
{ "amountCents": 20000, "source": "issued" }
source is "issued" (default) or "connected". Lowering a limit applies immediately (as long as it isn't below current spend). Raising it emails the account owner an approval link (expires in 24h; a newer request for the same bucket supersedes older links). Response status is "updated", "unchanged", or "pending" and always carries limitSource.
Identity (KYC)
New or non-exempt accounts must verify identity before spending. POST /api/identity/verification-session returns a hosted verification url to hand to the user; spend endpoints return identity_verification_required until it's done.
Purchases (unified, read-only)
One read model across both purchase kinds. Writes stay on the kind-specific endpoints below.
GET /api/purchases
Query params: limit (1–100, default 50), before (ISO date cursor), kind (issued_card | connected).
{
"purchases": [
{
"id": "cmc...",
"kind": "issued_card",
"providerId": null,
"status": "ready",
"sourceStatus": "issued",
"amountCents": 2500,
"currency": "USD",
"merchant": null,
"item": null,
"last4": "4242",
"network": "visa",
"capturedAmountCents": null,
"createdAt": "...",
"expiresAt": "...",
"completedAt": null,
"links": {}
}
]
}
Connected purchases additionally carry merchant ({name, url}), item, providerId (the provider instruction id), and links.approval / links.credentials.
Unified status vocabulary (raw kind-specific status is always in sourceStatus):
| Status | Meaning |
|---|---|
pending | Being set up (hold placed / card minting) |
awaiting_approval | Connected: waiting for the browser passkey approval |
ready | Usable now — live issued card, or approved unclaimed connected purchase |
completed | Money moved or the one-time credential was released |
expired | Passed expiresAt unused |
canceled | Closed before use |
declined | Pre-authorization rejection; no money moved |
failed | Post-authorization failure |
GET /api/purchases/:id
Single purchase in the same shape. :id can be an issued-card request id, a connected purchase id, or the provider instruction id. Card credentials are never served here — use the kind-specific endpoints below.
Issued cards (default source)
POST /api/spend-requests
{ "amountCents": 2500 }
Runs the spend gate (KYC, budgets, daily ceiling), places a hold on your saved payment method, mints the card, and returns it in full — the only time issued-card details are generated:
{
"id": "cmc...",
"amountCents": 2500,
"expiresAt": "...",
"pan": "4242424242424242",
"cvv": "123",
"last4": "4242",
"expiryMonth": 6,
"expiryYear": 2029,
"zip": "94108",
"cardholderName": "Alchemy Agentcard",
"billingAddress": { "line1": "77 Geary St", "city": "San Francisco", "state": "CA", "postalCode": "94108", "country": "US" }
}
Use the cardholder name and billing address exactly as returned — mismatches cause declines. One approved authorization up to amountCents, then the card locks.
GET /api/spend-requests · GET /api/spend-requests/:id
List your issued-card requests / fetch one. While a card is live (status: "issued", unexpired), the detail endpoint re-fetches and includes pan/cvv/expiry/billing.
POST /api/spend-requests/:id/cancel
Cancel an unused issued card: closes the card and releases the hold immediately. Idempotent once canceled; used/expired cards return 409 with code: "not_cancelable" and the current status; a 502 cancel_failed means the card is already unusable but the hold release needs a retry of the same call.
GET /api/3ds-codes
{ "codes": [{ "code": "123456" }] } — verification codes received in the last 5 minutes, newest first. Call only after a merchant checkout asks for a 3DS code; match by charge amount.
Connected cards (experimental source)
Requires the connected-card program flag; endpoints return 404 when disabled.
Enrollment
POST /api/agentic/browser-tokenwith{ "purpose": "connect-card" }→{ token, expiresAt }. Openhttps://agentcard.ai/connect-card#agentic_token=<token>in the user's browser; they enter their card there (never collect card numbers yourself).GET /api/agentic/payment-methods→{ paymentMethods: [...] }(poll to observe the enrollment;statusbecomesactive/approved).POST /api/agentic/payment-methods/:id/default— set the default card.DELETE /api/agentic/payment-methods/:id— remove a card.
POST /api/agentic/instructions — create a purchase
{
"paymentMethodId": "cm...", // optional; default card used otherwise
"network": "auto", // auto | visa | mastercard
"merchant": { "name": "Asha Tea House", "url": "https://order.example.com" },
"item": "Jasmine tea tin",
"amount": { "value": "24.50", "currency": "USD" },
"expiresInMinutes": 15, // 1–1440, default 15
"idempotencyKey": "order-8861" // optional; also accepted as Idempotency-Key header
}
Response includes the instruction (with provider instructionId), the selected paymentMethod, and approvalUrl / statusUrl / credentialsUrl. Replaying the same idempotencyKey with the same body returns the original instruction (idempotentReplay: true); a different body returns 409.
Approval
POST /api/agentic/browser-token with { "purpose": "instruction-approval", "instructionId": "<providerId>" }, then open <approvalUrl>#agentic_token=<token> for the user — they approve with a passkey. Poll GET /api/agentic/instructions/:instructionId until status is approved (terminal failures: declined, failed, expired, cancelled).
POST /api/agentic/instructions/:instructionId/credentials
One-time credential retrieval for an approved, unexpired instruction — returned exactly once:
{
"ok": true,
"credentials": {
"card": { "number": "...", "expiration_month": "...", "expiration_year": "...", "cvc": "..." },
"expires_at": "..."
}
}
Browser action tokens cannot call this endpoint; it requires the session token.
GET /api/agentic/instructions
List recent purchase instructions (most recent 100).
Funding payment method
GET /api/payment-methods→ your saved funding card summary (ornull).POST /api/payment-methods→{ "url": "<stripe-checkout-setup-url>" }— open it in the user's browser to save a card. AgentCard never sees the card number; Stripe does.
Spend controls, in one place
| Control | Scope | Default |
|---|---|---|
| Per-card cap | Each issued card / connected purchase | $150 |
| Issued account budget | Issued cards + wallet spend | $100, raiseable |
| Connected account budget | Connected-card purchases | $100, raiseable |
| Daily ceiling | Everything, per UTC day | $200 (hard) |
| KYC | All spend for new/non-exempt accounts | Required |
Budget raises are approved by the account owner over email (POST /api/limits). The daily ceiling cannot be raised through the API.