Virtual Card API: How to Automate Card Issuance with Code
A virtual card API lets you issue, fund, freeze and close cards from your own code. See how the card issuing API works, with real endpoints and webhooks.
A virtual card API lets your software issue, fund, freeze and close payment cards over HTTPS instead of clicking through a dashboard. With the USDT Crypto Card REST API, you authenticate with a secret sk_live_ key, create Visa or Mastercard cards loaded from your USDT balance, read transactions, and receive signed webhooks when something happens. It's built for teams that issue many cards, such as ad agencies, SaaS operators and finance tools.
What a card issuing API is for
Issuing a handful of cards by hand is fine. Issuing a card for every new client, campaign or vendor, and keeping each one funded, quickly becomes repetitive. A card issuing API turns those steps into code:
- Create a card per entity automatically, for example when a new client signs up in your CRM.
- Top up cards on a schedule or when their balance drops.
- Freeze cards instantly when a budget is hit or a contract ends.
- Sync transactions into your accounting or reporting tools.
- React to events in real time through webhooks instead of polling.
The API does what the dashboard does, from your server. Every card you issue and every load you make is paid from your account balance, which you fund with crypto deposits and which is always held in USDT.
Getting access to the virtual card API
Access takes three steps:
- Create a key. Open API access in your dashboard and generate a key. Keys start with
sk_live_. - Get it switched on. Confirm your email and make a first deposit, then ask support (through a ticket in the dashboard) to enable the API for your account.
- Make a call. Check your balance, then issue your first card.
Until your account is ready, a valid key gets a 403 response. You'll see the error code onboarding_incomplete until your email is confirmed and you've made a first deposit, then api_not_enabled until API access is switched on.
There is no separate sandbox environment and no official SDK packages. You call the REST API directly with any HTTP client, which keeps integration simple but means every call is live. Test with small amounts.
Basics: base URL, auth and format
- Base URL:
https://usdtcryptocard.com/api/v1 - Authentication: send your key as a Bearer token in the
Authorizationheader of every request. - Format: JSON over HTTPS. Amounts are in US dollars with two decimals. Timestamps are ISO 8601 in UTC.
- Pagination: list endpoints take
pageandper_page.
Keep the key on your server. Anyone holding it can move money on your account, so load it from an environment variable and never ship it in browser or mobile code.
Your first call
Checking your balance is a safe first request:
curl https://usdtcryptocard.com/api/v1/wallet/balance \
-H "Authorization: Bearer sk_live_..."
The response shows your USDT balance, pending deposits and what's sitting on your cards:
{
"balance": 1312.5,
"currency": "USDT",
"pending_deposits": 500.0,
"cards_active": 8,
"cards_total_balance": 2450.0
}
The endpoints
The API has 15 endpoints across six resources.
| Resource | Method and path | What it does |
|---|---|---|
| Cards | POST /cards |
Issue a card and load it in one call |
| Cards | GET /cards |
List cards, filterable by status and BIN |
| Cards | GET /cards/{card_id} |
Card summary without number or CVV |
| Cards | GET /cards/{card_id}/details |
Full card number, CVV and expiry |
| Cards | POST /cards/{card_id}/freeze |
Temporarily suspend a card |
| Cards | POST /cards/{card_id}/unfreeze |
Re-enable a frozen card |
| Cards | DELETE /cards/{card_id} |
Permanently terminate a card |
| Funding | POST /cards/{card_id}/fund |
Move money from your balance onto a card |
| Funding | POST /cards/{card_id}/withdraw |
Move a card's balance back to your wallet |
| Transactions | GET /transactions |
Authorizations, settlements, refunds and declines |
| Wallet | GET /wallet/balance |
Balance and card totals |
| Wallet | GET /wallet/deposit-address |
Crypto deposit addresses |
| BINs | GET /bins |
The BIN catalog |
| 3D Secure | GET /3ds |
Pending 3D Secure challenges with codes |
| Webhooks | POST /webhooks |
Register an endpoint for events |
Full parameters and example responses are in the API reference.
Issuing a card from code
Creating a card picks a BIN and loads the card from your balance in the same request. The fields are:
bin_id(required): the BIN to issue on. List the options withGET /bins.amount(required): the initial load in USD, taken from your balance. The minimum is $50, and the $1 issuance fee is charged on top.label: your own name for the card.spending_limit: a spending limit in USD.allowed_categories: merchant categories the card may be used in, such as advertising or software. Anything else is declined.auto_freeze_at: freeze the card automatically when its balance drops below this amount.metadata: key-value pairs for your own tracking.
Here's the same request in Node.js:
const res = await fetch('https://usdtcryptocard.com/api/v1/cards', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.USDTCC_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
bin_id: '491653',
amount: 500,
label: 'Meta Ads - Campaign 12',
spending_limit: 2000,
allowed_categories: ['advertising'],
metadata: { campaign: 'fall_2026' },
}),
});
const card = await res.json();
A successful call returns 201 with the card object: its id, BIN, network, last four digits, status, balance and your label and metadata. The summary deliberately leaves out the full number and CVV.
Getting the card number safely
GET /cards/{card_id}/details returns the full card number, CVV and expiry. Treat it as sensitive: never log the response or store it unencrypted, and fetch it only when you actually need to hand the details to a person or a checkout. This endpoint has a tighter rate limit than the rest.
Funding, freezing and closing
- Top up:
POST /cards/{card_id}/fundwith anamountmoves money from your balance onto the card. It's spendable right away. - Pull funds back:
POST /cards/{card_id}/withdrawreturns the card's balance to your wallet. - Pause:
POST /cards/{card_id}/freezesuspends all transactions while keeping the balance.unfreezere-enables the card. - Close:
DELETE /cards/{card_id}terminates the card permanently and returns its remaining balance to your wallet. It can't be undone.
The usual fees apply to API activity, exactly as in the dashboard: $1 per card issued, $0.30 per approved transaction (declines free), and free card top-ups from your balance. Deposits under $300 carry a 2% fee; $300 or more carries none. See the pricing section.
Webhooks
Instead of polling, register an HTTPS endpoint with POST /webhooks, listing the events you want (or ["*"] for all). Available events include:
card.created,card.frozen,card.unfrozen,card.terminated,card.fundedtransaction.authorized,transaction.settled,transaction.declined,transaction.refunded3ds.challengedeposit.pending,deposit.confirmed
Every delivery is signed with HMAC-SHA256 using your endpoint's secret, which is generated for you if you don't supply one. Recompute the signature over the raw request body and compare in constant time before trusting the payload:
import crypto from 'node:crypto';
export function verify(rawBody, signature, secret) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
Parse the JSON only after verification passes, and make your handler idempotent in case an event arrives more than once.
Errors and rate limits
Anything other than a 2xx comes with a JSON body containing a stable error code you can branch on and a human-readable message. The status codes you'll meet are 400 (invalid parameters), 401 (missing or invalid key), 403 (account not ready or API not enabled), 404 (not found) and 429 (rate limit exceeded).
Limits are counted per API key:
| Limit | Value |
|---|---|
| Requests per minute | 1,000 |
| Card creations per minute | 50 |
| Requests per hour | 10,000 |
If you get a 429, wait and retry with exponential backoff.
Design tips for a reliable integration
- Store the card
id, not the card number. Fetch details on demand. - Use
metadataandlabelto link cards to your own records, such as a client ID or campaign. - Prefer webhooks to polling for transactions and 3D Secure challenges.
- Keep a buffer on cards that pay recurring charges, or use
auto_freeze_atandfundto manage balances automatically. - Plan for limits. Account plan limits still apply: the Virtual plan allows $5,000 per transaction and $20,000 per month, while Platinum, unlocked by a single deposit of $300 or more, has no spending limits.
If you're managing cards for a team rather than writing code, see virtual cards for teams. For ad-account use cases, read virtual cards for ads. Ready to start? Create an account.
Frequently asked questions
What is a virtual card API?
It's an interface that lets software create and manage payment cards automatically. With USDT Crypto Card, it's a REST API using JSON over HTTPS, authenticated with a secret sk_live_ key.
Is there a sandbox or SDK?
No. There's no sandbox environment and no official SDK packages. You call the REST API directly with any HTTP client, and every call is live, so test with small amounts.
Why does my new API key return 403?
The account isn't ready yet. onboarding_incomplete means you still need to confirm your email and make a first deposit. api_not_enabled means support still needs to switch API access on.
How are webhooks secured?
Each delivery is signed with HMAC-SHA256 using your endpoint's secret. Recompute the signature over the raw body and compare it in constant time before trusting the payload.
Do API cards cost the same as dashboard cards?
Yes. The same fees apply: $1 per card, $0.30 per approved transaction, free top-ups from your balance, and a 2% deposit fee below $300.

