VCCPilot
Developers

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 Authorization header — 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_.

HTTP
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.

Keep your key server-side. A key reads your whole workspace. Store it in an environment variable or a secret manager and call the API from your backend — never embed it in a web page, mobile app, or anything a user can inspect.

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.

ScopeGrants
read:cardsRead your virtual cards and card-issuance requests — the card list, per-card details (never card numbers), and your card-request status.
read:transactionsRead your transactions — the transaction list and per-transaction details.
read:cashbackRead your cashback analytics — earned, eligible spend, rate, and payout summary.
read:walletRead your wallet — balance, holds, and deposit history.
topup:walletFund your wallet — get the BSC/ETH (BEP20/ERC20) USDT deposit address to send to. Does not move money.
write:cardsRequest virtual cards — files a card request; your operations team approves and issues. Does not issue instantly.
read:accountRead your account — company profile, account status, verification tier, and rank. Never includes login email, team roster, or verification documents.
write:accountUpdate 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.

EndpointScopeReturns
GET/v1/cardsread:cardsList your virtual cards (newest first). Cursor pagination, up to 200 per page.
GET/v1/cards/{id}read:cardsGet one card by id — status, limits, and spend (never the full card number).
GET/v1/card-requestsread:cardsList your card-issuance requests and their status (pending, issued, or rejected). Cursor pagination, up to 200 per page.
POST/v1/card-requestswrite:cardsSubmit a card-issuance request. Your operations team approves and issues — poll the request status.
GET/v1/transactionsread:transactionsList your transactions (newest first), filterable. Page-based pagination.
GET/v1/transactions/{id}read:transactionsGet one transaction by id — merchant, amount, status, and cashback detail.
GET/v1/cashback/summaryread:cashbackYour cashback summary — earned, eligible spend, current rate, and next payout.
GET/v1/walletread:walletYour wallet overview — balance, holds, available funds, and lifetime totals.
GET/v1/wallet/my-depositsread:walletList your deposit history. Page-based pagination.
POST/v1/wallet/topupstopup:walletGet your BSC/ETH (BEP20/ERC20) USDT deposit address to fund your wallet. Idempotent; moves no money.
GET/v1/accountread:accountYour account summary — company profile, status, verification tier/status, and rank.
PUT/v1/accountwrite:accountUpdate 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:

JSON
{
  "error": "unauthorized",
  "message": "Invalid API key."
}
StatusCodeWhen
400validation_failedA write request body is malformed or missing a required field (the message says which).
401unauthorizedThe API key is missing, malformed, expired, or revoked.
403forbiddenThe key lacks the scope for this endpoint, or the endpoint isn't available to API keys.
429rate_limitedYou exceeded the per-key request rate. Honour the `Retry-After` header, then retry.
503service_unavailableA 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) — pass limit (up to 200) and follow the nextCursor from each response back in as cursor until it's null.
  • Page-based (GET /v1/transactions, GET /v1/wallet/my-deposits) — pass page (starting at 1) and pageSize. The response includes total so 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
curl -H "Authorization: Bearer cbk_live_…" \
  https://card.vccpilot.com/v1/cards
Node.js
// 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
<?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:

Node.js
// 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.json

Need a key? Create one in Developers settings.

Questions about the API? Reach us through your account support channel.