ResourcesAPI ReferenceVirtual Cards API
API Reference

Virtual Cards API

Issue, top-up, freeze, unfreeze, and close Visa virtual cards programmatically. Includes card transactions and lifecycle enums.

Virtual Cards API

All write operations are asynchronous — they return 202 Accepted plus an Order. Poll the order until its status is terminal (completed / failed). All write endpoints support X-Idempotency-Key.

Required abilities: cards.read (reads) · cards.write (writes).


GET /card-products — list issuable card products

Ability: cards.read or cards.write

Before issuing a card you must call this endpoint to obtain a valid card_product_id and check fee/limit constraints. The catalogue is platform-global; each row is annotated for your workspace.

Success 200{ "data": [ ... ] } (not paginated). Each product:

FieldTypeNotes
idintPass as card_product_id to POST /cards
code / display_name / descriptionstring
currencystringProduct currency
bin / issuer_countrystringBIN prefix and issuing country
supports_custom_holder_nameboolWhether a custom cardholder name can be specified
supports_custom_billing_addressboolWhether a custom billing address can be specified
default_billing_addressobject?Used when supports_custom_billing_address is false
fees.card_issue_fixedstringOne-time issuance fee (decimal, deducted from initial top-up)
fees.topup_fixedstringFixed fee per subsequent top-up
fees.topup_percentstringPercentage fee per subsequent top-up (e.g. "0.0300" = 3 %)
min_initial_topup_amountstringMinimum initial_topup_amount for POST /cards
min_topup_amountstringMinimum amount for subsequent top-ups
required_subscription_product_codestring?Subscription prerequisite, if any
requires_subscriptionboolWhether a subscription prerequisite exists
subscription_satisfiedboolWhether this workspace meets the prerequisite (true when none)
currency_matchboolWhether product currency equals the workspace base currency (informational)
availableboolWhether this workspace may issue this product (= subscription_satisfied)

Workflow: call this endpoint to render a product picker → use the selected product's id as card_product_id, and validate the user's initial_topup_amount against min_initial_topup_amount client-side before submitting.

Errors: 401, 403


GET /cards — list cards

Ability: cards.read

QueryTypeDescription
page / per_pageintPagination
statusstringComma-separated card statuses (see Card status)
cardholder_typestringuser or staff (see Cardholder type)
searchstringKeyword search

Success 200 — paginated list. Each card row:

FieldTypeNotes
uuiduuid
statusstringCard status — see Card status
cardholder_typestringuser / staff
cardholder_first_name / cardholder_last_namestring
masked_panstringMasked card number
pan_last4 / pan_binstring
currencystring
card_balancestringDecimal string
card_product_idint
issued_at / last_active_txn_at / created_atdatetime?

Errors: 401, 403


POST /cards — issue a card

Ability: cards.write · Idempotent: yes · Async: yes (202)

Body

FieldTypeRequiredDescription
card_product_idintCard product ID
initial_topup_amountstringInitial top-up amount, e.g. "100.00"
cardholder_first_namestringCardholder first name
cardholder_last_namestringCardholder last name
billing_addressobjectBilling address (see below)
purposestringPurpose note
notesstringFree-form notes

billing_address fields: line1 (string), line2 (string?), city (string), state (string?), postal_code (string), country (2-letter ISO code).

Responses:

  • 202 — Order accepted. Encrypted card secrets are delivered separately via the card.issued.secrets webhook — they never appear in regular API responses.
  • 402 wallet.insufficient_balance
  • 401, 403, 422, 429

GET /cards/{uuid} — get one card

Ability: cards.read

Response: 200 card detail / 404 card.not_found

All fields from the list response, plus:

FieldTypeNotes
workspace_idint
card_balancestringRaw card balance
held_amountstringAmount held in pending authorization
available_balancestringcard_balance − held_amount
lifetime_topup_amount / lifetime_spend_amount / lifetime_refund_amountstringCumulative amounts
avs_billing_addressobject?line1, line2, city, state, postal_code, country
supports_freeze / supports_unfreeze / supports_balance_queryboolCapability flags — show/hide action buttons based on these
appeal_terminatedboolWhether appeals are permanently closed
closed_at / updated_atdatetime?

GET /cards/{uuid}/consumption-bills — consumption bills (merged view)

Ability: cards.read

Unlike /transactions which returns one row per network event, this endpoint returns one item per purchase, merging the Authorization → Capture / Refund / Reversal lifecycle into a single bill. Use this for end-user transaction history displays.

Query: from / to (datetime), page / per_page

Each bill:

FieldTypeNotes
idintBill identifier
kindstringpurchase / refund / decline / chargeback / other
statusstringauthorized / settled / reversed / expired / declined / refunded / chargeback_opened / chargeback_resolved
amountstringBase amount
currencystringSettlement currency (e.g. USD)
authorized_amountstring?Amount authorized (available when status is authorized)
settled_amountstring?Amount settled (available when status is settled / refunded)
transaction_amountstring?Original presentment amount
transaction_currencystring?Original presentment currency (e.g. HKD)
fx_ratestring?FX rate applied
merchantobject?raw_name, canonical_name, mcc, country
auth_codestring?
decline_reasonstring?See Decline reason
occurred_atdatetimeTime of most recent event on this bill
settled_atdatetime?Settlement time
linesarrayRaw transaction rows included in this bill

Display rule: show authorized_amount when status === "authorized"; show settled_amount when settled or refunded. transaction_amount + transaction_currency show the original presentment currency for cross-border transactions.

Errors: 404 card.not_found


GET /cards/{uuid}/transactions — card transactions

Ability: cards.read

Query: page / per_page; optional type (see Transaction type), status (see Transaction status), from / to (datetime).

FieldTypeNotes
uuiduuid
typestringSee Transaction type
statusstringSee Transaction status
amount / currencystring
fx_amount / fx_currency / fx_ratestring?FX detail
merchantobjectraw_name, canonical_merchant_id, canonical_name, mcc, country
auth_codestring?
decline_reasonstring?Null unless declined — see Decline reason
occurred_at / posted_at / created_atdatetime?

Errors: 404 card.not_found


POST /cards/{uuid}/topup — top up a card

Ability: cards.write · Idempotent: yes · Async: yes

FieldRequiredDescription
amountTop-up amount, e.g. "50.00"
notesFree-form notes

Responses: 202 Order / 404 / 422


POST /cards/{uuid}/freeze — freeze a card

POST /cards/{uuid}/unfreeze — unfreeze a card

POST /cards/{uuid}/close — close a card

Ability: cards.write · Idempotent: yes · Async: yes

Body (optional): { "reason": "up to 500 chars, nullable" }

Responses: 202 Order / 404 card.not_found / 409 (state conflict, e.g. double freeze)


Enum reference

Card status

ValueMeaning
pending_issueIssuance queued, not yet active
activeActive and usable
frozenTemporarily frozen
pending_closeClosure in progress
closedClosed (terminal)
issue_failedIssuance failed (terminal)

Cardholder type

ValueMeaning
userHeld by a main-account user
staffHeld by a staff member

Transaction status

ValueMeaning
pendingPending
postedPosted / settled
reversedReversed
failedFailed (e.g. a decline row)

Transaction type

ValueMeaning
authorizationAuthorization
captureCapture
refundRefund
reversalReversal
declineDecline
chargeback_openedChargeback opened
chargeback_resolvedChargeback resolved

Decline reason

ValueMeaning
insufficient_fundsInsufficient funds
card_frozenCard frozen
avs_mismatchBilling-address (AVS) mismatch
cvv_mismatchCVV mismatch
risk_blockedBlocked by risk engine
otherOther (catch-all for unmapped vendor reasons)