API Reference

Programmatic virtual card issuance, funding, and transaction monitoring for partners.

Base URLhttps://api.uncard.cc/v1Version 1 · April 2026

Introduction

The UnCard API lets you issue virtual cards, move funds, and monitor transactions through a single REST interface. Whether you're building an expense management tool, a payouts system, or a card-as-a-service product, the API handles the card network complexity so you can focus on your product.

Authentication

Every protected request must include an API key in the X-API-Key header.

Required headers

HeaderRequiredDescription
X-API-KeyYesPlaintext API key (format: uncard_sk_<token>).
Content-TypeYes (POST)Must be application/json on POST routes.

Authentication errors

StatusError
401Missing X-API-Key header
401Invalid or expired API key

Response Envelope

All JSON responses use a consistent envelope shape, regardless of route or success state.

{ "success": true,  "data": { ... } }
{ "success": false, "error": "message" }
FieldTypeWhen present
successbooleanAlways.
dataanyOn success.
errorstringOn failure.

HTTP status codes

StatusMeaning
200OK.
201Created (returned by POST /cards/issue).
400Bad request — missing or invalid parameter.
401Missing or invalid API key.
404Resource not found.
502Upstream error.

Enumerations

Card status

ValueMeaning
activeCard is live and can be used for authorizations and top-ups.
frozenTemporarily paused — no authorizations allowed.
closedPermanently terminated; cannot be re-opened.
processingCard issuance in flight; not yet usable.

Card type

ValueMeaning
onlineCard usable for online (card-not-present) transactions.
walletCard loaded into a mobile wallet (Apple Pay / Google Pay).

Transaction status

ValueMeaning
pendingSubmitted, awaiting confirmation.
completeAccepted and settled.
declinedRejected; funds not moved.

Health

GET/health

Unauthenticated liveness probe.

Response 200

{ "status": "ok", "timestamp": "2026-04-19T12:00:00.000Z" }

Cards

List cards

GET/cards

List all cards owned by the authenticated user. Query params: none.

Response 200

{
  "success": true,
  "data": [
    {
      "id": "uuid",
      "name": "Netflix",
      "masked_number": "411111******1234",
      "card_plan": "standard",
      "balance": 2500,
      "status": "active",
      "type": "online",
      "total_deposited": 5000,
      "total_withdrawn": 2500
    }
  ]
}

List card plans

GET/cards/plans

Returns the list of card plans. Query params: none.

Response 200

{
  "success": true,
  "data": [
    { "slug": "standard", "name": "Standard", "topup_limit": 10000, "creation_fee": 1 }
  ]
}
FieldTypeDescription
slugstringIdentifier used when issuing a card.
namestringHuman-readable plan name.
topup_limitnumberMaximum top-up limit (USD).
creation_feenumberOne-time fee charged on issuance (USD).

List card transactions

GET/cards/transactions

List card transactions for the authenticated user, newest first.

Query params

ParamTypeRequiredDefaultDescription
card_iduuidNoallFilter transactions to a single card (any card owned by user).
pageintNo11-based page number (>= 1).
limitintNo20Page size (1–100).

Response 200

{
  "success": true,
  "data": {
    "data": [
      {
        "id": "uuid",
        "card_id": "uuid",
        "amount": 1000,
        "currency": "USD",
        "masked_number": "411111******1234",
        "description": "Top-up",
        "status": "complete",
        "created_at": "2026-04-19T10:00:00Z"
      }
    ],
    "total": 42,
    "page": 1,
    "limit": 20
  }
}

Get a card

GET/cards/:id

Get a single card by ID.

Path params

ParamTypeRequiredDescription
iduuidYesCard ID owned by the caller.

Response 200

{
  "success": true,
  "data": {
    "id": "uuid",
    "name": "Netflix",
    "masked_number": "411111******1234",
    "card_plan": "standard",
    "balance": 2500,
    "status": "active",
    "type": "online",
    "billing_address": { "...": "..." },
    "total_deposited": 5000,
    "total_withdrawn": 2500,
    "minimum_topup": 5,
    "min_balance": 0
  }
}

Errors: 404 if the card does not exist or is not owned by the caller.

Get sensitive card details

GET/cards/:id/details

Returns sensitive card details (PAN, CVV, expiry). Treat the response as short-lived and never log it.

Path params

ParamTypeRequiredDescription
iduuidYesCard ID owned by the caller.

Response 200

{
  "success": true,
  "data": {
    "id": "uuid",
    "number": "4111111111111234",
    "cvv": "123",
    "expiry_date": "12/29"
  }
}

Errors: 400 if id is missing; 404 if the card is not owned by the caller.

Issue a card

POST/cards/issue

Issue a new card for the authenticated user.

Body

FieldTypeRequiredDefaultDescription
bin_iduuidYesWhich BIN the card is issued on (ID from GET /bins).
plan_slugstringYesCard plan to apply (slug from GET /cards/plans).
card_namestringNo"New Card"Display name for the card (any UTF-8 string).

Request

{ "bin_id": "uuid", "plan_slug": "standard", "card_name": "Netflix" }

Response 201

{ "success": true, "data": { "cardId": "uuid" } }

Errors: 400 for invalid bin or plan.

Top up a card

POST/cards/topup

Load funds from the user's wallet onto a card. A top-up fee applies.

Body

FieldTypeRequiredDescription
card_iduuidYesTarget card. Must be owned by caller with status = active.
amountnumberYesAmount in USD to top up (> 0, up to the plan's topup_limit).

Request

{ "card_id": "uuid", "amount": 100 }

Response 200

{ "success": true, "data": { "success": true } }

Errors: 400 when the wallet has insufficient balance or the amount is invalid; 502 when the top-up is declined.

Withdraw from a card

POST/cards/withdraw

Withdraw funds from a card back to the user's wallet.

Body

FieldTypeRequiredDescription
card_iduuidYesSource card. Must be owned by caller with status = active.
amountnumberYesAmount in USD to withdraw (> 0, <= card.balance).

Request

{ "card_id": "uuid", "amount": 50 }

Response 200

{ "success": true, "data": { "success": true } }

Errors: 400 when amount exceeds the card balance; 502 when the withdrawal is declined.

Close a card

POST/cards/close

Permanently close a card. Remaining balance is returned to the wallet. This action is irreversible — the card's status becomes closed.

Body

FieldTypeRequiredDescription
card_iduuidYesCard to close. Must be owned by caller, not already closed.

Request

{ "card_id": "uuid" }

Response 200

{ "success": true, "data": { "success": true } }

Errors: 400 if the card is already closed.

BINs

List BINs

GET/bins

List enabled BINs available for issuing cards. The first 4 digits of bin are returned in the clear; the rest is masked with **. Query params: none.

Response 200

{
  "success": true,
  "data": [
    {
      "id": "uuid",
      "bin": "4111**",
      "type": "online",
      "country": "US",
      "initial_balance": 0,
      "minimum_topup": 5,
      "min_balance": 0
    }
  ]
}
FieldTypeDescription
iduuidBIN ID — pass as bin_id to POST /cards/issue.
binstringFirst 4 digits + ** (rest masked).
typestringCard type that BINs of this series produce (same values as card type).
countrystringISO-3166 alpha-2 country code.
initial_balancenumberBalance seeded at issuance (USD).
minimum_topupnumberMinimum single top-up amount (USD).
min_balancenumberMinimum balance that must remain on the card (USD).

Wallet

Get wallet balance

GET/wallet

Return the authenticated user's wallet balance. Query params: none.

Response 200

{ "success": true, "data": { "balance": 12500 } }

Errors: 404 if the user has no wallet.

Webhooks

UnCard sends HTTP POST requests to your configured endpoint whenever an event occurs on one of your cards (e.g. a transaction). This lets you react to card activity in real time instead of polling.

Configuration

Webhooks are configured per-user from your dashboard.

FieldRequiredDescription
webhook_urlYesHTTPS URL that receives event deliveries.
webhook_secretNoSigning secret. If set, deliveries include an HMAC signature header.

If no webhook_url is set, no events are delivered.

Delivery

PropertyValue
MethodPOST
Content-Typeapplication/json
Timeout10 seconds

Your endpoint should acknowledge receipt with a 2xx response as quickly as possible.

Request headers

HeaderExampleNotes
Content-Typeapplication/json
User-AgentUnCard-Webhook/1.0
X-UnCard-Eventtransaction.createdThe event type.
X-UnCard-Timestamp2026-07-29T12:34:56.000ZISO 8601 delivery time.
X-UnCard-Signaturesha256=<hex>Present only if webhook_secret is configured.

Payload

Every delivery has the same envelope:

{
  "type": "transaction.created",
  "data": { ... }
}

Events

Event type (type / X-UnCard-Event)When it fires
transaction.createdA new card transaction is recorded — includes purchases, plus Transaction fee and Decline fee entries.

transaction.created — data object

FieldTypeDescription
idstringUnCard transaction UUID.
card_idstringUnCard card UUID.
amountnumberAmount in major units (e.g. dollars, not cents).
currencystringISO currency code, e.g. USD.
masked_numberstring | nullMasked card number.
descriptionstring | nullHuman-readable description (Transaction fee, Decline fee, or the merchant description).
statusstringcomplete, declined, reversed, or pending.
created_atstringISO 8601 timestamp.

Example

{
  "type": "transaction.created",
  "data": {
    "id": "a1b2c3d4-0000-4444-8888-abcdef012345",
    "card_id": "f0e1d2c3-1111-4444-8888-abcdef543210",
    "amount": 12.50,
    "currency": "USD",
    "masked_number": "411111******1111",
    "description": "COFFEE SHOP",
    "status": "complete",
    "created_at": "2026-07-29T12:34:56.000Z"
  }
}

Note: fee transactions (Transaction fee, Decline fee) are delivered as separate transaction.created events, each with its own id.

Verifying signatures

If you set a webhook_secret, verify the X-UnCard-Signature header before trusting a payload. The signature is sha256= followed by the hex HMAC-SHA256 of the raw request body using your secret.

const crypto = require('crypto');

function verify(rawBody, signatureHeader, secret) {
  const expected =
    'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(expected)
  );
}

Compute the HMAC over the exact bytes received — the relay forwards the body byte-for-byte so the signature stays valid.