Get started
Architecture
How AgentPay is wired under the hood. Useful if you're evaluating compliance, security, or planning a production rollout.
The card-issuance sequence
Agent AgentPay API Stripe Merchant
│ │ │ │
│ POST /v1/cards (amount) │ │ │
│──────────────────────────────▶│ │ │
│ │ resolve policy │ │
│ │ (check KYB + PM) │ │
│ │ │ │
│ │ PaymentIntent.create │ │
│ │ off_session, │ │
│ │ capture_method=manual │ │
│ │─────────────────────────▶│ │
│ │ │ auth-hold on │
│ │ │ user's saved card │
│ │◀─────────────────────────│ (requires_capture) │
│ │ │ │
│ │ Issuing.Card.create │ │
│ │ spending_limit=amount │ │
│ │─────────────────────────▶│ │
│ │◀─────────────────────────│ card minted │
│ 201 { id, last_four, exp } │ │ │
│◀──────────────────────────────│ │ │
│ │ │ │
│ (agent uses card at merchant) │ │ │
│ │ │ authorize $X │
│ │ │◀─────────────────────│
│ │ │ approve (from │
│ │ │ Issuing balance) │
│ │ │──────────────────────▶│
│ │ │ │
│ │ webhook: issuing_ │ │
│ │ authorization.created │ │
│ │◀─────────────────────────│ │
│ │ capture PaymentIntent │ │
│ │─────────────────────────▶│ │
│ │ │ charge user's card │
│ │ │ for the held amount │
│ │ │ │Data model
PostgreSQL via Drizzle ORM. Core tables:
| Table | What it holds |
|---|---|
| users | email, stripe customer id, default policy, default PM, cardholder id (once KYB done) |
| kyb_profiles | legal name, DOB, address, phone — one per user |
| payment_methods | saved Stripe PaymentMethod IDs, last4, brand |
| policies | approval_required + max_amount_cents per user |
| cardholders | maps users → Stripe Issuing cardholder ids on our Connect account |
| cards | every issued card with stripe id, status, spending limit, agent_id metadata |
| card_holds | PaymentIntent auth-holds on user PMs, tracked through lifecycle |
| approval_requests | queued card requests awaiting human approval |
| webhook_events | idempotent record of every Stripe webhook received |
| api_keys | hashed sk_test_/sk_live_ keys, per user |
| sessions | web session cookies |
| magic_link_tokens | one-time tokens for passwordless signin (hashed) |
Security boundaries
- No raw PAN / CVV on our servers. We use Stripe's ephemeral-key mechanism to let clients reveal card details via Stripe.js. Our API never stores or forwards the full number.
- API keys hashed at rest. We store only sha256(key). The full secret is shown to you once on creation and never again.
- Magic-link tokens hashed at rest. Same pattern. Tokens expire in 15 minutes and are single-use with a 2-minute prefetch-tolerance window.
- Session cookies are
httpOnly,SameSite=Lax,Securein production, 30-day expiry. - Webhook signatures verified via the Stripe signing secret; events are stored with a unique Stripe event id so duplicate deliveries short-circuit.
Compliance posture
- We are not a money transmitter in v0 — we never take custody of user funds. Money moves from the user's card directly to Stripe for card issuance.
- We are the issuer of record's partner via Stripe Issuing. The partner bank (currently Celtic Bank via Stripe) is the legal issuer.
- KYB data is collected per user as required by Stripe Issuing. Addresses are passed to Stripe for cardholder creation and AVS matching.
- US-only in v0. Multi-currency / international is a v1+ consideration.