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:

TableWhat it holds
usersemail, stripe customer id, default policy, default PM, cardholder id (once KYB done)
kyb_profileslegal name, DOB, address, phone — one per user
payment_methodssaved Stripe PaymentMethod IDs, last4, brand
policiesapproval_required + max_amount_cents per user
cardholdersmaps users → Stripe Issuing cardholder ids on our Connect account
cardsevery issued card with stripe id, status, spending limit, agent_id metadata
card_holdsPaymentIntent auth-holds on user PMs, tracked through lifecycle
approval_requestsqueued card requests awaiting human approval
webhook_eventsidempotent record of every Stripe webhook received
api_keyshashed sk_test_/sk_live_ keys, per user
sessionsweb session cookies
magic_link_tokensone-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, Secure in 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.