Virtual Cards API
All write operations are asynchronous — they return
202 Acceptedplus an Order. Poll the order until its status is terminal (completed/failed). All write endpoints supportX-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:
| Field | Type | Notes |
|---|---|---|
id | int | Pass as card_product_id to POST /cards |
code / display_name / description | string | |
currency | string | Product currency |
bin / issuer_country | string | BIN prefix and issuing country |
supports_custom_holder_name | bool | Whether a custom cardholder name can be specified |
supports_custom_billing_address | bool | Whether a custom billing address can be specified |
default_billing_address | object? | Used when supports_custom_billing_address is false |
fees.card_issue_fixed | string | One-time issuance fee (decimal, deducted from initial top-up) |
fees.topup_fixed | string | Fixed fee per subsequent top-up |
fees.topup_percent | string | Percentage fee per subsequent top-up (e.g. "0.0300" = 3 %) |
min_initial_topup_amount | string | Minimum initial_topup_amount for POST /cards |
min_topup_amount | string | Minimum amount for subsequent top-ups |
required_subscription_product_code | string? | Subscription prerequisite, if any |
requires_subscription | bool | Whether a subscription prerequisite exists |
subscription_satisfied | bool | Whether this workspace meets the prerequisite (true when none) |
currency_match | bool | Whether product currency equals the workspace base currency (informational) |
available | bool | Whether this workspace may issue this product (= subscription_satisfied) |
Workflow: call this endpoint to render a product picker → use the selected product's
idascard_product_id, and validate the user'sinitial_topup_amountagainstmin_initial_topup_amountclient-side before submitting.
Errors: 401, 403
GET /cards — list cards
Ability: cards.read
| Query | Type | Description |
|---|---|---|
page / per_page | int | Pagination |
status | string | Comma-separated card statuses (see Card status) |
cardholder_type | string | user or staff (see Cardholder type) |
search | string | Keyword search |
Success 200 — paginated list. Each card row:
| Field | Type | Notes |
|---|---|---|
uuid | uuid | |
status | string | Card status — see Card status |
cardholder_type | string | user / staff |
cardholder_first_name / cardholder_last_name | string | |
masked_pan | string | Masked card number |
pan_last4 / pan_bin | string | |
currency | string | |
card_balance | string | Decimal string |
card_product_id | int | |
issued_at / last_active_txn_at / created_at | datetime? |
Errors: 401, 403
POST /cards — issue a card
Ability: cards.write · Idempotent: yes · Async: yes (202)
Body
| Field | Type | Required | Description |
|---|---|---|---|
card_product_id | int | ✓ | Card product ID |
initial_topup_amount | string | ✓ | Initial top-up amount, e.g. "100.00" |
cardholder_first_name | string | ✓ | Cardholder first name |
cardholder_last_name | string | ✓ | Cardholder last name |
billing_address | object | — | Billing address (see below) |
purpose | string | — | Purpose note |
notes | string | — | Free-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 thecard.issued.secretswebhook — they never appear in regular API responses.402 wallet.insufficient_balance401,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:
| Field | Type | Notes |
|---|---|---|
workspace_id | int | |
card_balance | string | Raw card balance |
held_amount | string | Amount held in pending authorization |
available_balance | string | card_balance − held_amount |
lifetime_topup_amount / lifetime_spend_amount / lifetime_refund_amount | string | Cumulative amounts |
avs_billing_address | object? | line1, line2, city, state, postal_code, country |
supports_freeze / supports_unfreeze / supports_balance_query | bool | Capability flags — show/hide action buttons based on these |
appeal_terminated | bool | Whether appeals are permanently closed |
closed_at / updated_at | datetime? |
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:
| Field | Type | Notes |
|---|---|---|
id | int | Bill identifier |
kind | string | purchase / refund / decline / chargeback / other |
status | string | authorized / settled / reversed / expired / declined / refunded / chargeback_opened / chargeback_resolved |
amount | string | Base amount |
currency | string | Settlement currency (e.g. USD) |
authorized_amount | string? | Amount authorized (available when status is authorized) |
settled_amount | string? | Amount settled (available when status is settled / refunded) |
transaction_amount | string? | Original presentment amount |
transaction_currency | string? | Original presentment currency (e.g. HKD) |
fx_rate | string? | FX rate applied |
merchant | object? | raw_name, canonical_name, mcc, country |
auth_code | string? | |
decline_reason | string? | See Decline reason |
occurred_at | datetime | Time of most recent event on this bill |
settled_at | datetime? | Settlement time |
lines | array | Raw transaction rows included in this bill |
Display rule: show
authorized_amountwhenstatus === "authorized"; showsettled_amountwhen settled or refunded.transaction_amount+transaction_currencyshow 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).
| Field | Type | Notes |
|---|---|---|
uuid | uuid | |
type | string | See Transaction type |
status | string | See Transaction status |
amount / currency | string | |
fx_amount / fx_currency / fx_rate | string? | FX detail |
merchant | object | raw_name, canonical_merchant_id, canonical_name, mcc, country |
auth_code | string? | |
decline_reason | string? | Null unless declined — see Decline reason |
occurred_at / posted_at / created_at | datetime? |
Errors: 404 card.not_found
POST /cards/{uuid}/topup — top up a card
Ability: cards.write · Idempotent: yes · Async: yes
| Field | Required | Description |
|---|---|---|
amount | ✓ | Top-up amount, e.g. "50.00" |
notes | — | Free-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
| Value | Meaning |
|---|---|
pending_issue | Issuance queued, not yet active |
active | Active and usable |
frozen | Temporarily frozen |
pending_close | Closure in progress |
closed | Closed (terminal) |
issue_failed | Issuance failed (terminal) |
Cardholder type
| Value | Meaning |
|---|---|
user | Held by a main-account user |
staff | Held by a staff member |
Transaction status
| Value | Meaning |
|---|---|
pending | Pending |
posted | Posted / settled |
reversed | Reversed |
failed | Failed (e.g. a decline row) |
Transaction type
| Value | Meaning |
|---|---|
authorization | Authorization |
capture | Capture |
refund | Refund |
reversal | Reversal |
decline | Decline |
chargeback_opened | Chargeback opened |
chargeback_resolved | Chargeback resolved |
Decline reason
| Value | Meaning |
|---|---|
insufficient_funds | Insufficient funds |
card_frozen | Card frozen |
avs_mismatch | Billing-address (AVS) mismatch |
cvv_mismatch | CVV mismatch |
risk_blocked | Blocked by risk engine |
other | Other (catch-all for unmapped vendor reasons) |