Build withCryptocardium.
Issue cards, fund accounts, fire transactions, and watch the lifecycle — from cURL, from any SDK, from your AI agent. The whole platform is one stable REST API and one native MCP server.
Welcome
Cryptocardium is a no-KYC card programme funded with crypto. Every action you can take in the panel — opening an account, topping up, issuing a card, loading it, spending, watching transactions — is available through the same REST API and MCP server.
These docs cover everything: conventions (how the API talks), resources (what you can ask for), events (how you stay in sync), and integrations (Claude, ChatGPT, Cursor, any agent).
Sign up at /account, open API Settings, click "New key". The full plaintext is shown once — copy it into a secret manager.
Quickstart
From zero to a live virtual card in three calls. The shell below uses cURL; swap for any HTTP client.
1. Authenticate
Place your API key in the Authorization header. Every endpoint requires it.
export CCM_KEY="ccm_live_a1b2c3d4..."
2. Top up the treasury
Create a USDT deposit address. Send funds; we credit on confirmation.
curl https://api.cryptocardium.com/v1/topups \
-H "Authorization: Bearer $CCM_KEY" \
-H "Idempotency-Key: top_$(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "amount_usd": 500, "asset": "USDT", "chain": "tron" }'
3. Issue a card & spend
Pick a BIN (Visa Platinum for wallets, Visa Business for ads), load it, and you're live.
curl https://api.cryptocardium.com/v1/cards \
-H "Authorization: Bearer $CCM_KEY" \
-d '{
"type": "virtual",
"bin": "489517",
"load_usd": 300,
"wallet_provision": ["apple_pay", "google_pay"]
}'
# → 201 Created · card is live, balance loaded, wallet-ready
Forty-seven seconds median, signup to first auth. The remaining sections cover everything else — error handling, idempotency, webhooks, agent setup — that you'll need in production.
Authentication
Three credential types, picked by integration shape:
| Type | Format | Use for |
|---|---|---|
| Session bearer | sess_… | Interactive scripts, testing, the panel itself. |
| API key | ccm_live_… | Production servers, CI, scheduled jobs, long-lived. |
| OAuth 2.1 + DCR | eyJh… (JWT) | Agents that enrol themselves at runtime, scoped grants. |
Bearer header
All three pass through the same header:
Authorization: Bearer ccm_live_a1b2c3d4...
Generate an API key
Keys are generated only in the panel. We never accept a key creation request over the API without an authenticated session attached to the panel session itself.
- Sign in to your account.
- Open API Settings.
- Click New key, give it a recognisable name, save the plaintext (shown once).
- Store in a secret manager. Add to your environment as
CCM_KEY.
A leaked ccm_live_ key carries account-level permission. Rotate via /api-settings if you suspect exposure — revocation is instant.
Testing & sandbox
Today, top-ups under $200 are accepted as live but routed to a sandbox card processor, so you can integrate without burning real settlement. Production accounts get sandbox-mode toggles per key.
- Top up $200+ → production rails, real settlement.
- Top up <$200 → sandbox rails, simulated authorisations.
- Cards issued from sandbox top-ups carry
"mode": "sandbox".
Base URL & versioning
Production base URL:
https://api.cryptocardium.com/v1
The v1 prefix is part of the path. v1 is LTS-forever — no breaking changes will ever be shipped under it. Additive changes (new optional fields, new endpoints) ship transparently.
Breaking changes go under a new prefix (v2) with at least six months of overlap. Deprecations are announced via webhook (system.deprecation) and the changelog.
Requests
All requests are HTTPS-only (TLS 1.3). Request bodies are JSON; nested objects are first-class. Form-encoded bodies are not supported.
- Content-Type:
application/jsonfor write endpoints. - <strong>Charset</strong>: UTF-8 always.
- HTTP methods:
GET(read),POST(create / action),PATCH(partial update),DELETE(remove). - Header limits: 8 KB total, 4 KB per value.
- Body limit: 25 MB (100 MB for dispute evidence uploads).
- Timeouts: 30 s connect, 60 s read. Long-running operations are async.
Responses
Every response is JSON. Success responses return 200/201/204 with the resource body. Errors return a structured error object — see Errors.
Standard envelope
{
"id": "card_8f3a91b7c4d2",
"object": "card",
"created_at": "2026-05-19T07:30:00Z",
"updated_at": "2026-05-19T07:30:00Z",
...
}
Timestamps
All timestamps are ISO 8601 in UTC, formatted as YYYY-MM-DDTHH:MM:SSZ. There are no other timezones in the API.
Monetary values
All amounts are USD-equivalent decimals in amount_usd fields. Two-decimal precision. Internally backed by USDT.
Pagination
List endpoints (/topups, /cards, /transactions, …) are cursor-paginated. No offset, no SQL LIMIT.
GET /v1/transactions?limit=50&cursor=tx_8f3a91b7c4d2
limit— page size, default 25, max 100.cursor— pass back thenext_cursorfrom the previous response.- Pagination follows the natural sort of each resource (typically <code>created_at</code> desc).
{
"object": "list",
"data": [ /* 50 items */ ],
"has_more": true,
"next_cursor": "tx_2bea88..."
}
Filtering & sorting
Every list endpoint supports filtering on its primary fields via query parameters:
GET /v1/transactions
?card_id=card_8f3a91b7c4d2
&status=captured
&created_after=2026-05-01T00:00:00Z
&sort=-amount_usd
The sort parameter accepts a single field; prefix with - for descending. See each resource's endpoint reference for the supported filter keys.
Idempotency
Every write request (POST, PATCH, DELETE) accepts an Idempotency-Key header. Pass a unique value per logical operation. Retries with the same key return the original response.
POST /v1/cards
Idempotency-Key: card_create_b9f1a4...
Authorization: Bearer ccm_live_...
- Keys are stored for 24 hours. After that, the same key can be reused for a fresh operation.
- Recommended format:
<operation>_<uuid-v4>. - Same key + different body →
409 Conflictwithconflict_idempotency_key. GETrequests are always idempotent and don't need (or accept) the header.
Rate limits
Per API key:
| Window | Limit | Notes |
|---|---|---|
| 1 s burst | 60 req | Short spikes tolerated. |
| 1 min | 1 000 req | Sustained ceiling. |
| 1 day | 50 000 req | Aggregate per key. |
Every response carries:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 943
X-RateLimit-Reset: 1718999999
When you exceed the limit, you'll get 429 Too Many Requests with a Retry-After header in seconds. Honour it; we exponential-backoff bad citizens.
Errors
Every error response uses the same shape:
{
"object": "error",
"type": "invalid_request_error",
"code": "missing_required_field",
"message": "Field 'bin' is required.",
"param": "bin",
"request_id": "req_a9f4b1..."
}
HTTP status mapping
| Status | Type | Meaning |
|---|---|---|
| 400 | invalid_request_error | Malformed payload, missing fields. |
| 401 | authentication_error | Missing or invalid bearer. |
| 403 | permission_error | Bearer is valid, action not allowed (e.g., balance gate). |
| 404 | not_found_error | Resource doesn't exist (or isn't yours). |
| 409 | conflict_error | State conflict (e.g., reused idempotency key). |
| 419 | session_expired | Panel session timed out. Re-auth. |
| 422 | validation_error | Payload valid JSON, business rule failed. |
| 429 | rate_limit_error | Slow down. Retry-After in seconds. |
| 500 | api_error | Our fault. We've already alerted. |
| 503 | service_unavailable | Upstream issuer down. Auto-retry recommended. |
Always log the request_id. Support tickets reference it directly.
Accounts
The account is your root resource. Everything else (top-ups, cards, transactions) belongs to an account.
Create
POST /v1/accounts
{
"email": "[email protected]",
"password": "long-random-string"
}
Returns a session bearer immediately. No KYC, no document upload, no email verification step required to start using the API.
Retrieve current
GET /v1/accounts/me
{
"id": "acc_8f3a91b7c4d2",
"email": "[email protected]",
"balance_usd": 487.20,
"twofa_enabled": true,
"created_at": "2026-05-18T20:14:00Z"
}
Balance & treasury
The account balance is the spendable USDT pool. Top-ups credit it, card loads and withdrawals debit it.
GET /v1/balance
{
"object": "balance",
"available_usd": 487.20,
"pending_usd": 200.00,
"updated_at": "2026-05-19T07:30:00Z"
}
pending_usd covers in-flight top-ups (not yet confirmed) and in-flight card loads.
Top-ups
A top-up is the asymmetric step where you commit crypto on-chain and we credit USDT after finality.
Create
POST /v1/topups
{
"amount_usd": 500,
"asset": "USDT",
"chain": "tron"
}
Returns a deposit address, QR data URI, and an expiry (default 60 min). Send the exact amount to the address; we credit on confirmation.
{
"id": "top_4e21a99c7b",
"status": "pending",
"amount_usd": 500,
"deposit_address": "T9zFR...kQp",
"qr_data_uri": "data:image/png;base64,...",
"expires_at": "2026-05-19T08:30:00Z"
}
Status lifecycle
| Status | Meaning |
|---|---|
| pending | Awaiting on-chain deposit. |
| completed | Funds credited to balance. |
| expired | Address window closed. Late deposits still auto-credit. |
| cancelled | You called POST /v1/topups/:id/cancel. |
| error | Settlement failed (rare). Auto-refunded. |
Prefer the topup.confirmed webhook over polling — it fires once and saves you 30+ polls.
Cards
Cards come in two types — virtual (live in seconds) and physical (shipped in 5–9 days, locked to the Visa Gold BIN).
Issue
POST /v1/cards
{
"type": "virtual",
"bin": "489517",
"load_usd": 300,
"wallet_provision": ["apple_pay", "google_pay"]
}
BIN catalogue
| BIN | Name | Best for | Type |
|---|---|---|---|
| 416842 | Visa Business | Ad spend (3-D Secure) | Virtual |
| 557213 | Mastercard World | Multi-currency, premium | Virtual |
| 489517 | Visa Platinum | Apple & Google Pay | Virtual |
| 472305 | Visa Corporate | SaaS subscriptions | Virtual |
| 448585 | Visa Gold | Physical only (3-D Secure) | Physical |
Reveal full PAN + CVV
The full card number, CVV and expiry are returned only on a dedicated, audited call:
GET /v1/cards/:id/pan
{
"pan": "4895 1712 ●●●● 4218",
"cvv": "347",
"expires_at": "2029-08",
"audit_id": "audit_8c2e3f..."
}
Every reveal is logged to the audit trail. Agents should reveal once per purchase, never store, and discard from memory after use.
Operations
POST /v1/cards/:id/freeze— stop authorisations.POST /v1/cards/:id/unfreeze— resume.POST /v1/cards/:id/load— add USDT to the card.POST /v1/cards/:id/unload— move unspent balance back to treasury.POST /v1/cards/:id/cancel— permanent retirement.PATCH /v1/cards/:id/limits— per-card transaction / monthly ceilings.PATCH /v1/cards/:id/mcc— MCC allow/deny lists.PATCH /v1/cards/:id/geo— country allow-lists.
Card loads & balances
Card balance is denominated in USD-equivalent USDT. A load deducts from the treasury, applies the 2% rail fee, and credits the card.
POST /v1/cards/:id/load
{ "amount_usd": 200 }
{
"card_id": "card_8f3a91b7c4d2",
"loaded_usd": 200.00,
"rail_fee_usd": 4.00,
"new_card_balance_usd": 200.00,
"new_treasury_balance_usd": 283.20
}
Loads are atomic — the call returns only after the funds have moved.
Transactions
Every authorisation, capture, refund, and decline is a transaction object. They're append-only and immutable.
GET /v1/cards/:id/transactions?status=captured&limit=50
{
"object": "list",
"data": [
{
"id": "tx_b1c2d3",
"object": "transaction",
"card_id": "card_8f3a91b7c4d2",
"status": "captured",
"amount_usd": 42.95,
"merchant": { "name": "OpenAI", "mcc": "7372" },
"auth_response_code": "00",
"created_at": "2026-05-19T03:14:00Z"
}
],
"has_more": false
}
Filter on status, card_id, mcc, merchant_name, created_after, created_before, amount_min, amount_max.
Withdrawals
Send treasury USDT back to any external wallet you control.
POST /v1/withdrawals
{
"amount_usd": 100,
"chain": "tron",
"address": "T9zFR...kQp"
}
Min $10, max your full balance. Supported chains: tron (cheapest), ethereum, bsc. Cross-network sends are unrecoverable — always verify the address.
Disputes
File a chargeback on any transaction:
POST /v1/disputes
{
"transaction_id": "tx_b1c2d3",
"reason": "duplicate",
"description": "Merchant charged twice on 2026-05-19, same order #4921."
}
Reason codes: duplicate, not_received, fraud, not_as_described, cancelled_subscription, other.
Attach evidence (receipts, screenshots, correspondence) via POST /v1/disputes/:id/evidence. We file with the issuer on your behalf — no extra fee.
Webhooks · subscribing
Webhooks push events to your HTTPS endpoint as they happen. Avoid polling; use webhooks.
POST /v1/webhooks
{
"url": "https://yourapp.example.com/webhooks/cryptocardium",
"events": [
"topup.confirmed",
"card.issued",
"transaction.captured",
"transaction.declined"
],
"description": "Production sync"
}
Response contains a signing_secret — store it; you'll need it to verify payloads. Subscribe to "*" to receive every event.
Webhooks · signature verification
Every webhook carries a HMAC-SHA256 signature in the Cryptocardium-Signature header:
Cryptocardium-Signature: t=1718999999,v1=4a8b2f0e6c9d...
Cryptocardium-Event-Id: evt_a1b2c3d4
Cryptocardium-Delivery: dlv_8f3a91...
Compute the expected signature over "{t}.{rawBody}" with your signing_secret. Compare using a constant-time function.
Node.js
import crypto from 'crypto';
export function verify(rawBody, sigHeader, secret) {
const [t, v1] = sigHeader.split(',')
.map(s => s.split('=')[1]);
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(v1), Buffer.from(expected)
);
}
Python
import hmac, hashlib
def verify(raw_body: bytes, sig_header: str, secret: str) -> bool:
parts = dict(p.split('=') for p in sig_header.split(','))
t, v1 = parts['t'], parts['v1']
expected = hmac.new(
secret.encode(),
f"{t}.{raw_body.decode()}".encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(v1, expected)
PHP
function verify($rawBody, $sigHeader, $secret): bool {
$parts = [];
foreach (explode(',', $sigHeader) as $p) {
[$k, $v] = explode('=', $p, 2);
$parts[$k] = $v;
}
$expected = hash_hmac('sha256',
"{$parts['t']}.{$rawBody}", $secret);
return hash_equals($expected, $parts['v1']);
}
Webhooks · retries & replays
At-least-once delivery. We retry on any non-2xx response or timeout (10 s).
| Attempt | Delay |
|---|---|
| 1 | immediate |
| 2 | 30 s |
| 3 | 5 min |
| 4 | 30 min |
| 5 | 2 h |
| 6 | 12 h |
| 7 | 24 h (final) |
Replay any event from the dashboard or via POST /v1/webhooks/:id/replay/:event_id. Use the Cryptocardium-Event-Id header for deduplication on your side.
Event catalog
account.created
account.signed_in
account.signed_out
account.password_changed
account.totp_enabled
account.totp_disabled
topup.created
topup.confirmed
topup.expired
topup.cancelled
topup.error
card.issued
card.loaded
card.unloaded
card.frozen
card.unfrozen
card.cancelled
card.replaced
transaction.authorized
transaction.captured
transaction.refunded
transaction.declined
transaction.reversed
dispute.opened
dispute.responded
dispute.won
dispute.lost
withdrawal.created
withdrawal.broadcasted
withdrawal.confirmed
withdrawal.failed
system.maintenance
system.deprecation
MCP server
We host a Model Context Protocol server at https://mcp.cryptocardium.com/v1. It exposes 40+ tools mapping 1:1 to REST endpoints, with agent-friendly names (create_topup, issue_card, reveal_pan, etc.).
Transport: Streamable HTTP. Auth: OAuth 2.1 with Dynamic Client Registration — agents enrol themselves on first connect, no API key needs to be pasted into the agent's config.
MCP · client setup
Claude Desktop / Claude Code
// ~/.config/claude/claude_desktop_config.json
{
"mcpServers": {
"cryptocardium": {
"url": "https://mcp.cryptocardium.com/v1",
"transport": "http"
}
}
}
Cursor / Continue / mcp-cli
# Adds the server, kicks off OAuth DCR on first connect
mcp-cli add cryptocardium \
--url https://mcp.cryptocardium.com/v1 \
--auth oauth
After authorising in your browser, the agent has access to every tool in the catalog (or just the ones you granted, if you tightened the scope).
MCP · tools catalog
Tools mirror REST endpoints. A small sample (the full list is in /api):
create_account
sign_in
get_account
enable_2fa
get_balance
create_topup
withdraw
issue_card
load_card
reveal_pan
freeze_card
set_card_limits
list_transactions
get_activity
file_dispute
MCP · OAuth 2.1 + DCR
Agents register themselves dynamically via RFC 7591. Flow:
- Agent
POST /oauth/register— receivesclient_id&client_secret. - User is prompted in-browser to authorise (one-time).
- Agent receives
access_token(scoped, time-limited). - Subsequent requests carry the JWT bearer.
Per-tool scopes
Restrict an agent to read-only, to a specific card, or to a subset of tools by passing scope= at registration time. Examples:
scope=read # list & get only, no writes
scope=cards:write # manage cards but not withdraw
scope=card:card_8f3a91b7c4d2 # single card
SDKs & examples
Official SDKs ship next. Until then, the API is a flat REST surface — every modern HTTP client handles it.
Node.js (fetch)
const res = await fetch('https://api.cryptocardium.com/v1/cards', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CCM_KEY}`,
'Idempotency-Key': `card_${crypto.randomUUID()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'virtual', bin: '489517', load_usd: 300
})
});
const card = await res.json();
Python (requests)
import requests, os, uuid
r = requests.post(
'https://api.cryptocardium.com/v1/cards',
headers={
'Authorization': f"Bearer {os.environ['CCM_KEY']}",
'Idempotency-Key': f"card_{uuid.uuid4()}",
},
json={'type': 'virtual', 'bin': '489517', 'load_usd': 300}
)
card = r.json()
PHP (curl)
$ch = curl_init('https://api.cryptocardium.com/v1/cards');
curl_setopt_array($ch, [
CURLOPT_POST => 1,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $_ENV['CCM_KEY'],
'Idempotency-Key: card_' . bin2hex(random_bytes(16)),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'type' => 'virtual',
'bin' => '489517',
'load_usd' => 300,
]),
]);
$card = json_decode(curl_exec($ch), true);
Changelog
Additive changes ship transparently. Breaking changes go under a new version prefix.
-
2026-05-19 · v1.4
Added
POST /v1/cards/:id/load&/unloadare now atomic.card.loadedwebhook event added. -
2026-05-18 · v1.3
Added
MCP server live at
mcp.cryptocardium.com. OAuth 2.1 DCR. 40+ tools mapped from REST. -
2026-05-17 · v1.2
Added
Physical Gold card programme (BIN 448585), 3-D Secure on Visa Business and Visa Gold.
-
2026-05-15 · v1.1
Changed
Top-up minimum lowered to $20 for sandbox routing, $200 for production rails.
-
2026-05-12 · v1.0
Released
v1 LTS. 50+ endpoints. Bearer auth + idempotency keys + HMAC webhooks.
Support
Stuck? Two paths:
- Browse the help center FAQ — 30+ answered Q&A, searchable.
- Open a support ticket — active cardholders only, reply within 24 h.
Include the request_id from the failing response — it speeds resolution by 10×.