Card issuing infrastructure
Virtual Card API — issue Visa & Mastercard cards to your customers.
ScarabPay Business issues virtual cards to your customers, funded from a merchant wallet you top up. Every movement posts balanced double-entry legs to an insert-only ledger, so the figures you report and the rows we hold are the same rows.
// Idempotency-Key: 8f3c1d90-...-b7a2
{
"product_id": "01HXYZP4K2M8N6Q1RSTVWXYZ01",
"customer_id": "01HABCD7F3G9J5K2LMNPQRSTVW",
"amount_minor": 250000
}
Ledger · insert only
01
The money path
How the virtual card API works.
Funding comes first because it has to: an issue request against an unfunded wallet is rejected, not queued. Nothing below is a stage in a marketing diagram — each one is an endpoint you call or an event you receive.
-
Fund the wallet
Your merchant wallet holds one balance per currency in integer minor units. Crypto deposits arrive at permanent per-network addresses and credit the wallet once the network confirms them.
POST /deposit-addresses
-
Issue a card to a customer
One POST carries a product, a registry customer and an initial load. The load and the product's issue fee are held against the wallet, the issuing partner is called outside the database transaction, and settlement posts the movement to the ledger. Cards that provision asynchronously return 202 with a pending card.
POST /cards
-
Receive activity as it happens
Authorizations and lifecycle changes reach you as signed webhook events — issued, authorization, recharged, frozen, unfrozen, terminated. The same activity is readable per card, cursor-paginated.
GET /cards/{card}/transactions
-
Reconcile against rows, not totals
Reconciliation is reading the ledger back. Nothing is updated and nothing is deleted, so a figure you reported last quarter still resolves to the entries that produced it.
GET /deposits
02
Issuing partners
One virtual card issuing API in front of several issuers.
You integrate once. Behind that integration each card product is routed to a licensed issuing partner chosen for its region and its product set. Adding a partner, or replacing one, is our migration rather than yours — the request you send does not change.
Partner identities are not published, and no partner name is exposed through the API or in any response you receive.
03
Developers
Virtual card API integration, written to be retried.
A financial API is judged by what happens on the second attempt. Every mutating endpoint requires an Idempotency-Key, the same key travels upstream to the issuer as the client reference, and a replay resolves to the original result rather than a second movement.
{
"data": {
"id": "01HKQW2N8V4X6Z9B3D5F7HJKMN",
"status": "active",
"masked_number": "••••••••••••4417",
"balance_minor": 250000,
"currency": "USD"
}
}
Delivered to your endpoint
Every delivery carries a timestamped HMAC signature you verify with your endpoint secret.
REST and JSON, one envelope
Predictable success, processing and error envelopes. Public identifiers are ULIDs; internal autoincrement ids never leave the system.
Idempotency required, not offered
A mutating request without an Idempotency-Key is rejected. Reusing a key with a different payload is rejected too, rather than silently accepted.
Signed webhooks, retried
Card and funding events are delivered with an HMAC signature over the timestamp and body, and retried on failure.
Integer minor units end to end
No floating-point value exists anywhere in the money path — not in a request, not in a response, not in a ledger row.
04
What exists today
Virtual card API features: fourteen operations, no roadmap.
Fourteen documented operations across eleven paths, covering issuing, the cardholder registry and funding. Anything not on this list is not something you can call.
| Cards | |
|---|---|
| POST /cards | Issues a card against a product, to a registry customer, with an initial load. Charges the load plus the product issue fee through hold → provider → settle. Returns 202 with a pending card when the issuer provisions asynchronously. |
| GET /cards/{card} | Returns the card's current state. Poll it while a new card provisions — pending resolves to active. |
| POST /cards/{card}/recharge | Loads more funds onto a live card from the wallet, as a new balanced movement. |
| POST /cards/{card}/freeze | Blocks spend on a card without moving money. Reversed by /unfreeze. |
| POST /cards/{card}/terminate | Ends a card permanently and returns its residual balance to the wallet. |
| GET /cards/{card}/transactions | Provider-reported activity for one card, cursor-paginated. |
| Cardholders | |
| POST /customers | Registers a cardholder identity once. Stored encrypted at rest and submitted to the issuer inside an issue request. |
| GET /customers/{customer} | Reads a registry customer. PAN and security code are never exposed and never persisted. |
| Funding | |
| POST /deposit-addresses | Creates a permanent per-network address that credits the merchant wallet on receipt. |
| GET /deposits | Received deposits, reconciled against the ledger. |
Unfreeze, list customers, update a customer and list deposit addresses complete the fourteen.
Every movement resolves to legs that sum to zero.
An authorization does not mutate a balance. It places a hold on the merchant wallet, calls the issuer outside the database transaction, and posts the movement as equal and opposite legs to an insert-only ledger. Corrections are posted as new entries, never as edits.
The ledger is the source of truth for every figure the platform reports, including the ones in the merchant's own dashboard.
Card number ending 4417
- Cardholder
- A. HADDAD
- Expires
- 04/29
Ledger excerpt · demonstration data
05
Shape of the work
Virtual cards for fintechs and businesses.
There is no limit field, no merchant-category rule and no channel switch in this API, and building around one would be building around something that does not exist. What you control is how much a card is funded with — set at issue, changed by recharge, swept back on terminate. Three patterns follow from that.
01
A card per customer
Your platform holds one wallet; each end customer holds a card funded from it. Their exposure is the balance you loaded, and their identity is a record in the cardholder registry rather than a payload you re-send.
02
Payouts issued as cards
Pay a seller or a contractor by issuing a card carrying the amount owed. Add to it with a recharge, and terminate it when the engagement ends — the residual returns to your wallet as a balanced movement.
03
One card per vendor
Issue a separate card per supplier or subscription and fund it with what that relationship is worth. Spend beyond the load does not authorize, and every card's activity is readable on its own.
Frequently asked questions
What is a virtual card API?
It is a REST API your platform calls to issue virtual Visa and Mastercard cards to your own customers, load them with funds, and manage them — freeze, unfreeze, recharge and terminate — without building card issuing yourself.
How are the cards funded?
Each merchant holds a USD wallet. You fund it with stablecoin deposits sent to permanent per-network addresses, credited once the network confirms them. A card's initial load and its issue fee are drawn from that wallet.
How do I control what a card can spend?
Through its balance. You set the amount when you issue the card, add to it with a recharge, block spending with a freeze, and terminate it to return the remaining balance to your wallet.
How do I know what happens on a card?
Card and funding events are delivered to your endpoint as webhooks signed with HMAC-SHA256 and retried on failure. The same activity can be read per card through the API.
Is ScarabPay a bank?
No. We are not a bank and do not hold a banking licence. Cards are issued through licensed issuing partners.
How do I get started?
Contact us to request access. Your business is verified before live use; sandbox API keys let you integrate with play money in the meantime.
Every claim on this page resolves to an operation.
Read what the API does, or write to us with what you are building and we will tell you plainly whether it fits.