VCCPilot API
A server-to-server API for your workspace — read your cards, transactions, cashback, and wallet, and run a handful of safe writes (request a card, fund your wallet, update your profile) from your own backend.
Last updated: 2026-06-30
Overview
The VCCPilot API lets you work with your own workspace data programmatically — pull your cards, transactions, cashback summary, and wallet balance into your internal tools (a reporting dashboard, a finance reconciliation job, a status page), and run a few safe writes from your backend.
- Reads + safe writes. Read your cards, transactions, cashback, and wallet; request cards, get a deposit address, and edit your profile. The API can not reveal full card numbers, withdraw or move funds, manage your team, or change security — card issuance still needs your operations team's approval, and deposits are credited by the network, never by the key.
- Workspace-scoped. A key only ever touches the workspace it was created in.
- Server-to-server. Authenticate with a secret key in an
Authorizationheader — never from a browser.
The base URL is https://card.vccpilot.com — the same domain you use to reach VCCPilot. All endpoints live under /v1 and return JSON.
Authentication
Authenticate every request with a Bearer token in the Authorization header. Tokens start with cbk_live_.
Authorization: Bearer cbk_live_…Create a key from your Developers settings. The full token is shown once at creation — copy it then; it can't be retrieved later. You can revoke a key at any time, which disables it immediately.
Scopes
Each key carries one or more scopes (read or write). A key can only call the endpoints its scopes allow — pick the narrowest set your integration needs. Write scopes can be granted now, but every write stays disabled (returns 503) until write access is enabled on your account.
| Scope | Grants |
|---|---|
read:cards | Read your virtual cards and card-issuance requests — the card list, per-card details (never card numbers), and your card-request status. |
read:transactions | Read your transactions — the transaction list and per-transaction details. |
read:cashback | Read your cashback analytics — earned, eligible spend, rate, and payout summary. |
read:wallet | Read your wallet — balance, holds, and deposit history. |
topup:wallet | Fund your wallet — get the BSC/ETH (BEP20/ERC20) USDT deposit address to send to. Does not move money. |
write:cards | Request virtual cards — files a card request; your operations team approves and issues. Does not issue instantly. |
read:account | Read your account — company profile, account status, verification tier, and rank. Never includes login email, team roster, or verification documents. |
write:account | Update your account — company name and contact details (phone, Telegram). Does not change team, security, or verification. |
Endpoints
All endpoints are workspace-scoped and live under https://card.vccpilot.com/v1. Each requires the scope shown; the write endpoints (POST/PUT) additionally require write access to be enabled on your account.
| Endpoint | Scope | Returns |
|---|---|---|
GET/v1/cards | read:cards | List your virtual cards (newest first). Cursor pagination, up to 200 per page. |
GET/v1/cards/{id} | read:cards | Get one card by id — status, limits, and spend (never the full card number). |
GET/v1/card-requests | read:cards | List your card-issuance requests and their status (pending, issued, or rejected). Cursor pagination, up to 200 per page. |
POST/v1/card-requests | write:cards | Submit a card-issuance request. Your operations team approves and issues — poll the request status. |
GET/v1/transactions | read:transactions | List your transactions (newest first), filterable. Page-based pagination. |
GET/v1/transactions/{id} | read:transactions | Get one transaction by id — merchant, amount, status, and cashback detail. |
GET/v1/cashback/summary | read:cashback | Your cashback summary — earned, eligible spend, current rate, and next payout. |
GET/v1/wallet | read:wallet | Your wallet overview — balance, holds, available funds, and lifetime totals. |
GET/v1/wallet/my-deposits | read:wallet | List your deposit history. Page-based pagination. |
POST/v1/wallet/topups | topup:wallet | Get your BSC/ETH (BEP20/ERC20) USDT deposit address to fund your wallet. Idempotent; moves no money. |
GET/v1/account | read:account | Your account summary — company profile, status, verification tier/status, and rank. |
PUT/v1/account | write:account | Update your company profile — company name and contact details (phone, Telegram). |
Rate limits
Each key is rate-limited per minute — by default 120 requests/minute for reads. Once write access is enabled on your account, writes get a tighter 30/minute budget. When you exceed a limit, the API responds with 429 rate_limited and a Retry-After header telling you how many seconds to wait. Back off and retry after that window.
Errors
Errors return a non-2xx status and a JSON body with a stable, machine-readable error code plus a human-readable message:
{
"error": "unauthorized",
"message": "Invalid API key."
}| Status | Code | When |
|---|---|---|
| 400 | validation_failed | A write request body is malformed or missing a required field (the message says which). |
| 401 | unauthorized | The API key is missing, malformed, expired, or revoked. |
| 403 | forbidden | The key lacks the scope for this endpoint, or the endpoint isn't available to API keys. |
| 429 | rate_limited | You exceeded the per-key request rate. Honour the `Retry-After` header, then retry. |
| 503 | service_unavailable | A transient outage (retry with backoff), or a write endpoint was called before write access is enabled on your account (it keeps returning 503 until then — do not retry in a loop). |
Pagination
List endpoints page in one of two ways:
- Cursor (
GET /v1/cards,GET /v1/card-requests) — passlimit(up to 200) and follow thenextCursorfrom each response back in ascursoruntil it'snull. - Page-based (
GET /v1/transactions,GET /v1/wallet/my-deposits) — passpage(starting at 1) andpageSize. The response includestotalso you can compute the number of pages.
The exact query parameters for each endpoint are in the OpenAPI spec.
Example: embed your data on your site
The most common use case: fetch your data from your backend and render it on your own site. Both examples below run server-side — the key stays in an environment variable and never reaches the browser. Start with a quick check from your terminal:
curl -H "Authorization: Bearer cbk_live_…" \
https://card.vccpilot.com/v1/cards// Server-side only — keep your key in an environment variable, never in the browser.
const res = await fetch("https://card.vccpilot.com/v1/cards", {
headers: { Authorization: `Bearer ${process.env.API_KEY}` },
});
if (!res.ok) throw new Error(`API error ${res.status}`);
const { cards } = await res.json();
// Render the data on your own site, e.g.:
const html = cards
.map((c) => `<li>${c.name} ····${c.last4} — ${c.status}</li>`)
.join("");<?php
// Server-side only — keep your key out of any client-visible code.
$ctx = stream_context_create([
"http" => [
"method" => "GET",
"header" => "Authorization: Bearer " . getenv("API_KEY"),
],
]);
$res = file_get_contents("https://card.vccpilot.com/v1/cards", false, $ctx);
$data = json_decode($res, true);
// Render the data on your own site:
foreach ($data["cards"] as $c) {
echo "<li>{$c['name']} ····{$c['last4']} — {$c['status']}</li>";
}Webhooks
Instead of polling, register an endpoint at /settings/webhooks and we'll POST a signed JSON event to it the moment something happens. Each event is delivered at least once; we retry with backoff for ~42 hours if your endpoint doesn't return a 2xx.
Events you can subscribe to
- Card issuance —
cards.created,cards.request_rejected,cards.issuance_enabled,cards.issuance_disabled,cards.paused,cards.resumed,cards.closed,cards.limit_changed - Top-ups —
wallet.deposit_pending,wallet.deposit_credited,wallet.topup_credited - Transactions —
transactions.settled,transactions.declined - Cashback —
cashback.payout_credited
The body is { version, id, type, createdAt, data } — data carries opaque ids + status only; fetch full detail from the read API using the id. Every request also carries Cashback-Webhook-Signature (t=…,v1=…), Cashback-Webhook-Id, Cashback-Webhook-Event, Cashback-Webhook-Version, and Cashback-Webhook-Attempt.
Verify the signature on every request
v1 is HMAC-SHA256(secret, `${t}.${rawBody}`). Compute it over the raw request body, compare in constant time, and reject anything older than 5 minutes:
// Verify EVERY webhook before trusting it (Express). The signing secret is shown ONCE when you
// add or rotate a webhook at /settings/webhooks — store it as an env var, never in client code.
import crypto from 'node:crypto';
const SECRET = process.env.WEBHOOK_SECRET;
if (!SECRET) throw new Error('WEBHOOK_SECRET is required'); // fail fast — never verify with undefined
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
const raw = req.body.toString('utf8');
const sig = req.header('Cashback-Webhook-Signature') || ''; // "t=1700000000,v1=<hex>"
const p = Object.fromEntries(sig.split(',').map((kv) => kv.split('=')));
if (!p.t || !p.v1) return res.sendStatus(400); // malformed header — reject (no empty/NaN bypass)
const expected = crypto.createHmac('sha256', SECRET)
.update(`${p.t}.${raw}`).digest('hex'); // signed over `${t}.${rawBody}`
const good = expected.length === p.v1.length
&& crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(p.v1));
if (!good || Math.abs(Date.now() / 1000 - Number(p.t)) > 300) // reject a bad sig or a >5-min-old event
return res.sendStatus(400);
const event = JSON.parse(raw); // { version, id, type, createdAt, data }
res.sendStatus(200); // ACK fast (we time out ~10s); then process ASYNC + idempotently on event.id
});Versioning & stability
The API is versioned in the path (/v1). Within v1 we make only backward-compatible changes — adding a new endpoint or a new field to a response. We won't remove a field or change its meaning under v1.
A breaking change would ship under a new version path. If an endpoint is ever retired, we'll announce it in advance and send a Sunset header on the affected responses during the deprecation window so you have time to migrate.
Machine-readable spec
The full OpenAPI 3.1 spec — every endpoint, parameter, and response schema — is available as JSON. Import it into Postman, Insomnia, or your client-code generator.
View openapi.public-api.jsonNeed a key? Create one in Developers settings.