Cardholder, issue a Card against it, set spending_controls,
and watch authorizations and transactions stream in. It is a genuinely powerful card platform, and
the API is excellent. But run directly, it is also a separate vendor account:
- Cards live behind your Stripe secret key, Stripe dashboard, and Stripe’s
Cardholder/Cardobject graph — funded from a pooled Issuing/Treasury balance you top up yourself. - A Stripe
Cardholderscopes cards and nothing else. It is a KYC entity for issuing — it has no idea about the agent’s inbox, its vault secrets, its OAuth connections, or any other KYC you ran. - “Who let this agent issue a $5k card, and what else can this agent touch?” is answered in Stripe for cards, and in unrelated systems for everything else. There is no shared accountability.
/cards primitive gives the agent the same capability —
full Visa/Mastercard virtual cards via the managed_virtual provider — but rooted in
one identity:
- The tenant user that holds a card is the same user that owns its email inbox, its vault secrets, its KYC, and its connections.
- Whether an agent may issue or fund a card — and up to what total — is decided by that user’s Account Kit at execution time, not by trusting your own code to check first.
- Every cardholder create, card issue, top-up, and transaction lands in the same per-user activity log as everything else the agent does.
Stripe and related marks are trademarks of Stripe, Inc., used here for identification only. Naive is not affiliated with or endorsed by Stripe; card funding may use hosted checkout, but this guide does not describe Stripe’s internal Issuing implementation.
Tested against: Stripe Node SDK
stripe v17 (Issuing
API, versions 2025-12-15 / 2026-04-22, per docs.stripe.com/api/issuing,
snapshot June 2026) and the Naive API (base https://api.usenaive.ai/v1, docs snapshot June 2026)
plus the Naive Node SDK @usenaive-sdk/server.Version notes:- Naive’s
managed_virtualprovider issues a full Visa/Mastercard virtual card (cardholder required, no spending cap); the defaultprepaid_giftprovider is a prepaid Visa gift card (capped at $150, no cardholder). This guide targetsmanaged_virtual, the closest match to a direct Stripe Issuing card. - Stripe Issuing card credentials (
number,cvc) are retrieved viacards.retrieve(id, { expand })and require your platform to be PCI-compliant or to use Stripe Elements. Naive returns them fromGET /v1/cards/:id/detailsonce the card isactive. - Both are evolving APIs — verify method names and the funding model against your installed versions.
Concept map
| Stripe Issuing | Naive | Notes |
|---|---|---|
new Stripe(secretKey) | new Naive({ apiKey }) | Server-side key in both cases |
Cardholder (ich_…) scopes cards only | tenant user via naive.forUser(id) — scopes every primitive | The core consolidation win |
stripe.issuing.cardholders.create({ name, billing, … }) | POST /v1/cards/cardholder (firstName, lastName, billing…, dob…) | One cardholder per identity; required for managed_virtual |
stripe.issuing.cardholders.retrieve(id) | GET /v1/cards/cardholder | Reads the current identity’s cardholder |
stripe.issuing.cards.create({ cardholder, type:"virtual", currency, spending_controls }) | POST /v1/cards with { name, spending_limit_cents, provider:"managed_virtual" } | Naive funds via hosted checkout → returns checkout_url |
stripe.issuing.cards.retrieve(id, { expand:["number","cvc"] }) | GET /v1/cards/:id/details | Returns number, cvc, exp_month, exp_year once active |
stripe.issuing.cards.list() | GET /v1/cards / client.cards.list() | List cards for the identity |
| Fund from Issuing/Treasury balance (top up the balance) | POST /v1/cards/:id/top-up { amount_cents } → checkout_url | Naive funds per card via checkout, not a pooled balance |
stripe.issuing.cards.update(id, { status:"canceled" }) | POST /v1/cards/:id/cancel | Deactivate a card |
stripe.issuing.transactions.list({ card }) | GET /v1/cards/transactions?card_id= | Settled spend per card |
spending_controls.spending_limits[] (per-interval, per-MCC) | spending_limit_cents (single funded cap per card) | Partial — see gaps; no MCC / interval controls |
issuing_authorization.request webhook → approve/decline per swipe | Account Kit gating + Approvals on issue / top-up | Different layer — Naive governs issuance & funding, not each authorization. See gaps |
| Stripe Connect / per-account Issuing | Account Kit primitives_config.cards per tenant user | Execution-time policy, per identity — the governance win |
| Physical cards, disputes, multi-currency | — | Not on /cards today — see gaps |
Before / after: the core path
The path that matters for almost every agent that spends money is register a cardholder, issue a card, then read its credentials. Here it is on both platforms.- Funding model. Stripe Issuing typically draws from a pooled Issuing/Treasury balance you
top up. Naive funds each card through a hosted checkout (
checkout_url) for the card’s amount, then issues it oncheck-payment. There is no shared balance to manage. - Spend limits. Stripe’s
spending_controlscan include per-interval and per-MCC rules. Naive’sspending_limit_centsis a single funded cap per card — simpler, but not category- or interval-aware. See the gaps. - The cardholder is your identity, not a standalone KYC object. In Stripe a
Cardholderexists only for issuing. In Naive the cardholder hangs off a tenant user that also owns the agent’s inbox, vault, connections, and KYC.
Funding and issuing
Both platforms separate “create the card” from “the card can spend.” Stripe does it with a balance; Naive does it with a per-card checkout.- On Naive, issuing a card, creating a cardholder, and topping up are sensitive and may be
approval-gated by the Account Kit — the call can return
202 { status: "pending_approval", approval_id }until a human approves, then the API replays it. - A card’s status moves
pending_payment → activeaftercheck-paymentconfirms funding. See Card statuses.
Minimal viable migration
The smallest swap that keeps a spending agent running is cardholder → issue → reveal. You do not need to recreate Treasury, per-MCC controls, or Account Kits to make your first card.Install the SDK and set your key
NAIVE_API_KEY (a server-side key from the dashboard).Map your users
A Stripe
Cardholder becomes a Naive tenant user with a cardholder
attached. Reuse your own database id as external_id so the mapping is 1:1, or pass it straight
into naive.forUser(id).Recreate the cardholder once per identity
Replace
stripe.issuing.cardholders.create({ name, billing }) with
POST /v1/cards/cardholder (firstName, lastName, billing…, dob…). One cardholder per
tenant user covers all of that user’s managed_virtual cards.Swap card issuance
Replace
stripe.issuing.cards.create with client.cards.create({ name, spending_limit_cents, provider: "managed_virtual" }), open the returned checkout_url, then check-payment to issue.Consolidate further once you’re on Naive
This is where the migration pays for itself. With Stripe Issuing direct, aCardholder isolates
cards and nothing else. On Naive, the unit of isolation is a tenant user, and it isolates
the agent’s entire footprint.
Gain #1 — one identity across primitives
- With Stripe Issuing, the agent’s cards are an island. With Naive,
naive.forUser(acme.id)is a single handle to cards and email and vault and connections and KYC. - You provision a customer’s whole agent footprint from one identity, and tear it down from one place. Sibling tenants can never read each other’s cards, secrets, or inboxes.
Gain #2 — execution-time permission enforcement
- Whether an agent may issue or fund a card is policy on the Account Kit — not a check you hand-write, and not “we trust the agent to call the right function.”
- The agent’s code is identical for every tier —
client.cards.create({ … }). Whether the call runs is decided by the caller’s kit at execution time:primitives_config.cards.enabled: false→ the exact same line returnsforbidden, no code change.requiresApproval: true→ issuing, cardholder creation, and top-ups freeze as a pending approval; the API replays them only after a human approves.- The funded
spending_limit_centscaps how much was loaded onto the card — spend is generally bounded by that funded amount, though treat issuer/network behavior as authoritative for edge cases.
- Stripe gates who has Issuing access at the Connect-account level and per-authorization via your own webhook logic. Naive additionally gates the issuance and funding decision per tenant user, declaratively, with no per-swipe handler to run. (Per-authorization MCC control is still a Stripe strength — see gaps.)
Gain #3 — unified accountability
- Every cardholder create, card issue, top-up, and transaction for a customer lands in one per-user activity log — alongside their email, vault, and connection events, not in a separate Stripe dashboard:
- That is the question that is hard to answer when cards live in Stripe, email lives in another vendor, and secrets live somewhere else. Under Naive it is a single query.
What does not map yet
A migration guide that hides gaps is worse than none. The core path (cardholder → issue → reveal → transactions → cancel) maps cleanly, but Stripe Issuing has real capabilities Naive’s/cards does
not match today. Check this list against your app before you commit — some of these are why you
might stay on Stripe Issuing direct.
| Stripe Issuing feature | Status on Naive | Workaround |
|---|---|---|
Real-time authorization control (issuing_authorization.request webhook → approve/decline each swipe within ~2s) | Not available | Naive enforces at issuance/funding time (Account-Kit + Approvals) and via the hard funded cap; there is no per-authorization decision hook |
spending_controls — per-interval limits, allowed_categories / blocked_categories (MCC), blocked_merchant_countries | Partial — only a single funded spending_limit_cents cap | Issue narrowly-funded, single-purpose cards; gate issuance via the Account Kit; reconcile MCCs from the transactions feed |
| Pooled Issuing / Treasury balance funding | Not exposed — each card is funded by its own checkout | Fund per card via checkout_url; top up with POST /cards/:id/top-up |
| Physical cards (shipping, activation, PIN) | Not available — virtual only (managed_virtual / prepaid_gift) | Use virtual cards; no physical issuance today |
Disputes (issuing.disputes.*) | Not available | Handle disputes out of band; Naive refund covers failed-payment refunds only |
| Multi-currency issuing | Not exposed — USD checkout funding | USD only today |
| Stripe Connect (issue on behalf of connected accounts) | Different model — per-tenant-user Account Kits | Map each connected account to a Naive tenant user |
| Card programs / commercial financing, rewards | Not available | Out of scope for /cards |
Where to go next
/cardsprimitive — full cardholder/issue/fund/transaction lifecycle- Cards API reference — exact request/response shapes
cardsSDK sub-client — typed method signatures- Account Kits and Approvals — the policy model that makes execution-time issuance governance real
- Tenant users — the identity that the consolidation hangs off