Multi-Card Cardholder Management

A single approved cardholder can hold multiple cards simultaneously — each with its own balance, transaction limit, deposit addresses, and status. This recipe covers the patterns for listing, managing, and presenting a consolidated view of a multi-card cardholder.


Listing all cards for a cardholder

const { api } = require('./client');

async function getCardsForCardholder(cardholderId, statusFilter = null) {
  const params = new URLSearchParams({ cardholderId });
  if (statusFilter !== null) {
    params.set('issuerCardStatus', statusFilter);
  }

  const result = await api('GET', `/cards?${params}`);
  return result.items ?? [];
}

// Example: get only active cards
const activeCards = await getCardsForCardholder(10532, 1);

Consolidated balance view across all cards

async function consolidatedBalance(cardholderId) {
  const cards = await getCardsForCardholder(cardholderId);

  const balances = await Promise.all(
    cards.map(async card => {
      const bal = await api('GET', `/cards/${card.cardId}/balance`);
      return {
        cardId:           card.cardId,
        alias:            card.alias   ?? `Card ${card.cardId}`,
        type:             card.type,
        maskedPan:        card.maskedCardNumber,
        issuerCardStatus: card.status, // from list response = issuerCardStatus
        ledgerBalance:    bal.ledgerBalance,
        availableBalance: bal.availableBalance,
        currency:         bal.currency,
      };
    })
  );

  const totals = balances.reduce((acc, b) => ({
    totalLedger:    acc.totalLedger    + (b.ledgerBalance    ?? 0),
    totalAvailable: acc.totalAvailable + (b.availableBalance ?? 0),
  }), { totalLedger: 0, totalAvailable: 0 });

  return { cards: balances, totals };
}

Example output:

{
  "cards": [
    { "cardId": 8821, "alias": "Personal Spending", "type": "virtual",
      "issuerCardStatus": 1, "ledgerBalance": 50000, "availableBalance": 45000, "currency": 0 },
    { "cardId": 9104, "alias": "Travel Card", "type": "physical",
      "issuerCardStatus": 1, "ledgerBalance": 150000, "availableBalance": 150000, "currency": 0 }
  ],
  "totals": { "totalLedger": 200000, "totalAvailable": 195000 }
}

Disambiguating deposits across multiple cards

When a cardholder holds multiple cards, it's important to route deposits to the correct card. Each card has its own independent cryptoAddresses — there's no ambiguity on the crypto rail. On the bank rail, the payment reference must identify the specific cardId:

async function getFundingDetails(cardId) {
  const card = await api('GET', `/cards/${cardId}`);

  return {
    cardId:          card.cardId,
    alias:           card.alias,
    cryptoAddresses: card.cryptoAddresses,
    // Bank deposit reference: the cardId is used as the reference
    // (confirm reference format with your Axys account team)
    bankReference:   `CARD-${card.cardId}`,
    // Reminder: bank inbound credentials come from Account Management
    bankNote:        'Inbound bank account details available via Account Management only.',
  };
}

Per-card limit management

Transaction limits cascade Program → Cardholder → Card. To adjust a specific card's limit without affecting others:

async function setCardLimit(cardId, newLimitDecimalImplied) {
  // Validate: cannot exceed cardholder's limit
  // (you'd need to GET /cardholders/{id} and check their transactionLimit first)
  const result = await api('PUT', `/cards/${cardId}/update`, {
    transactionLimit: newLimitDecimalImplied,
  });
  console.log(`Card ${cardId} limit updated to ${newLimitDecimalImplied}`);
  return result;
}

// Example: set card 8821 to $300.00 max
await setCardLimit(8821, 30000);

Bulk block / unblock

To temporarily suspend all cards for a cardholder (e.g. suspected fraud while investigating):

async function bulkSetCardStatus(cardholderId, newStatus) {
  // Only toggle between Active (1) and On Hold (2)
  if (![1, 2].includes(newStatus)) {
    throw new Error('newStatus must be 1 (Active) or 2 (On Hold)');
  }

  const cards = await getCardsForCardholder(cardholderId);

  // Only update cards currently in a toggleable status
  const eligible = cards.filter(c => c.status === 1 || c.status === 2);

  const results = await Promise.allSettled(
    eligible.map(card =>
      api('PUT', `/cards/${card.cardId}/status`, { newStatus })
        .then(() => ({ cardId: card.cardId, success: true  }))
        .catch(e  => ({ cardId: card.cardId, success: false, error: e.message }))
    )
  );

  const succeeded = results.filter(r => r.value?.success).length;
  const failed    = results.filter(r => !r.value?.success).length;

  console.log(`Bulk status update: ${succeeded} succeeded, ${failed} failed`);
  return results.map(r => r.value);
}

// Block all active/on-hold cards for cardholder 10532
await bulkSetCardStatus(10532, 2);
🚧

To permanently close a card or report it lost/stolen, raise a support ticketissuerCardStatus = 3 (Closed) is not reachable via PUT /cards/{cardId}/status. See Support Tickets.


Funding multiple cards from a wallet

The corporate pattern: distribute from a central Wallet to multiple Cards in one pass:

async function distributeFromWallet(walletId, distributions) {
  // distributions: [{ cardId, amount, description }, ...]
  const results = [];

  for (const { cardId, amount, description } of distributions) {
    try {
      const result = await api('POST', '/wallets/transfer', {
        sourceWalletId: walletId,
        destinationType: 1,           // 1 = card
        destinationId:   cardId,
        amount,
        description:     description ?? `Allocation to card ${cardId}`,
      });

      results.push({ cardId, success: true, transId: result.transId,
                     transStatus: result.transStatus });

      // Brief pause between transfers to avoid rate limiting
      await sleep(200);
    } catch (err) {
      results.push({ cardId, success: false, error: err.message });
    }
  }

  return results;
}

// Example: fund three cards from wallet 3301
await distributeFromWallet(3301, [
  { cardId: 8821, amount: 25000, description: 'Q3 allocation — Jordan Reyes'  },
  { cardId: 9104, amount: 50000, description: 'Q3 allocation — Alex Chen'     },
  { cardId: 9201, amount: 25000, description: 'Q3 allocation — Sam Okafor'    },
]);
🚧

No idempotency key available. If a transfer call times out, verify via GET /cards/{cardId}/balance before retrying — do not blindly retry a transfer that may have succeeded. See Transfer Internal Fiat Funds.


What's next