Skip to main content

Overview

The Transaction API provides comprehensive access to card transaction history with powerful filtering, pagination, and statement generation capabilities. View detailed transaction information including merchant details, amounts, fees, and crypto funding sources.
Transactions are returned in reverse chronological order (most recent first) for easy access to recent activity.

Transaction Data Structure

Each transaction includes detailed information across multiple dimensions:

Transaction Details

ID, timestamp, merchant, type, MCC category

Financial Data

Amounts, fees, currencies, conversion rates

Funding Sources

Crypto wallets, networks, transaction hashes

Sample Transaction

{
  "id": "100a99cf-f4d3-4fa1-9be9-2e9828b20ebb",
  "cardId": "1234537292209260487",
  "panLast4": "9189",
  "transactionId": "1122334477422",
  "dateTime": "2024-10-14T10:44:36.276Z",
  "sign": "DEBIT",
  "merchantNameLocation": "WWW.ALIEXPRESS.COM, LONDON",
  "merchantType": "OutOfWalletOnline",
  "mcc": 5964,
  "mccCategory": "MISC",
  "transactionCurrency": "EUR",
  "amountInTransactionCurrency": "0.79",
  "feesInTransactionCurrency": "0",
  "originalCurrency": "USD",
  "amountInOriginalCurrency": "0.85",
  "feesInOriginalCurrency": "0",
  "billingConversionRate": "0.9294117647058824",
  "ecbRate": "0.9161704076958315",
  "status": "CONFIRMED",
  "declineReason": "",
  "fundingSources": [
    {
      "id": "3181a37a-07fa-41dc-b423-6c2db07a7ba1",
      "address": "0x3a11a86cf218c448be519728cd3ac5c741fb3424",
      "network": "linea",
      "txHash": "0xb92de09d893e8162b0861c0f7321f68df02212efbc58f208839ae3f176d89638",
      "currency": "usdc",
      "amount": "0.104201",
      "fees": "0",
      "swapFee": "0.00208",
      "sign": "DEBIT",
      "status": "CONFIRMED",
      "dateTime": "2024-10-14T10:44:36.288Z"
    }
  ]
}

Fetching Transaction History

Basic Request

const response = await fetch('https://dev.api.baanx.com/v1/card/transactions', {
  headers: {
    'X-Client-ID': 'your_client_id',
    'Authorization': `Bearer ${userAccessToken}`
  }
});

const transactions = await response.json();
console.log(`Retrieved ${transactions.length} transactions`);

With Pagination

// Fetch page 2 (0-indexed, so page=1 is actually the second page)
const response = await fetch(
  'https://dev.api.baanx.com/v1/card/transactions?page=1',
  {
    headers: {
      'X-Client-ID': clientId,
      'Authorization': `Bearer ${accessToken}`
    }
  }
);

const transactions = await response.json();
Pages are 0-indexed. page=0 is the first page, page=1 is the second page, etc. If you request a page that doesn’t exist, it returns page 0 (first page).

Filtering Transactions

By Date Range

Filter transactions within a specific time period:
const params = new URLSearchParams({
  dateFrom: '2024-10-01',
  dateTo: '2024-10-31'
});

const response = await fetch(
  `https://dev.api.baanx.com/v1/card/transactions?${params}`,
  {
    headers: {
      'X-Client-ID': clientId,
      'Authorization': `Bearer ${accessToken}`
    }
  }
);

const transactions = await response.json();
dateFrom
string
Start date in ISO 8601 format (YYYY-MM-DD). Required if dateTo is specified.Example: 2024-10-01
dateTo
string
End date in ISO 8601 format (YYYY-MM-DD). Required if dateFrom is specified.Example: 2024-10-31
Both dateFrom and dateTo must be provided together. Specifying only one will result in an error.

By Merchant Name

Search for transactions from specific merchants:
const params = new URLSearchParams({
  searchKey: 'PayPal'
});

const response = await fetch(
  `https://dev.api.baanx.com/v1/card/transactions?${params}`,
  {
    headers: {
      'X-Client-ID': clientId,
      'Authorization': `Bearer ${accessToken}`
    }
  }
);
searchKey
string
Search term to filter merchant names. Case-insensitive partial match.Example: "PayPal", "Amazon", "Starbucks"

By Category

Filter by merchant category code (MCC) category:
const params = new URLSearchParams({
  mccCategories: 'FOOD,TRAVEL'
});

const response = await fetch(
  `https://dev.api.baanx.com/v1/card/transactions?${params}`,
  {
    headers: {
      'X-Client-ID': clientId,
      'Authorization': `Bearer ${accessToken}`
    }
  }
);
mccCategories
string
Comma-separated list of categories to filter by.Available Categories:
  • SUBSCRIPTIONS: Streaming services, memberships
  • FOOD: Restaurants, groceries, delivery
  • TRAVEL: Hotels, airlines, car rentals
  • ENTERTAINMENT: Movies, concerts, games
  • HEALTH: Healthcare, pharmacy, fitness
  • ATM: ATM withdrawals
  • UTILITIES: Bills, phone, internet
  • MISC: Everything else
Example: "FOOD,ENTERTAINMENT", "TRAVEL"

Combined Filters

All filters can be combined:
const params = new URLSearchParams({
  dateFrom: '2024-10-01',
  dateTo: '2024-10-31',
  searchKey: 'uber',
  mccCategories: 'TRAVEL,FOOD',
  page: 0
});

const response = await fetch(
  `https://dev.api.baanx.com/v1/card/transactions?${params}`,
  {
    headers: {
      'X-Client-ID': clientId,
      'Authorization': `Bearer ${accessToken}`
    }
  }
);

// Returns: Uber transactions in October 2024 categorized as TRAVEL or FOOD

Transaction Response Fields

Core Transaction Fields

id
string
Unique transaction identifier
cardId
string
ID of card used for transaction
panLast4
string
Last 4 digits of card number
transactionId
string
Merchant transaction ID
dateTime
string
Transaction timestamp (ISO 8601)
sign
enum
Transaction direction: DEBIT or CREDIT
  • DEBIT: Money leaving account (purchase)
  • CREDIT: Money entering account (refund, reversal)
merchantNameLocation
string
Merchant name and location (e.g., “AMAZON.COM, SEATTLE”)
merchantType
string
Type of merchant transaction (e.g., “OutOfWalletOnline”, “InStoreWithPin”)
mcc
number
Merchant Category Code (numeric code)
mccCategory
enum
Human-readable category: SUBSCRIPTIONS, FOOD, TRAVEL, ENTERTAINMENT, HEALTH, ATM, UTILITIES, MISC

Financial Fields

transactionCurrency
string
Card currency (e.g., “EUR”, “USD”)
amountInTransactionCurrency
string
Amount charged to card (decimal string)
feesInTransactionCurrency
string
Fees charged in card currency (decimal string)
originalCurrency
string
Merchant’s original currency
amountInOriginalCurrency
string
Original amount in merchant currency
feesInOriginalCurrency
string
Fees in original currency
billingConversionRate
string
Conversion rate used for billing
ecbRate
string
European Central Bank reference rate

Status Fields

status
enum
Transaction status:
  • CONFIRMED: Transaction completed successfully
  • PENDING: Transaction processing
  • DECLINED: Transaction rejected
  • REVERTED: Transaction reversed/refunded
declineReason
string
Reason for decline (only present when status: "DECLINED")Examples: “Insufficient funds”, “Invalid PIN”, “Card frozen”

Funding Source Fields

fundingSources
array
Array of crypto funding sources used for this transaction

Implementing Transaction History UI

Basic Transaction List

import { useState, useEffect } from 'react';

export function TransactionHistory({ clientId, accessToken }) {
  const [transactions, setTransactions] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [page, setPage] = useState(0);

  useEffect(() => {
    fetchTransactions();
  }, [page]);

  async function fetchTransactions() {
    setLoading(true);
    setError(null);

    try {
      const response = await fetch(
        `/v1/card/transactions?page=${page}`,
        {
          headers: {
            'X-Client-ID': clientId,
            'Authorization': `Bearer ${accessToken}`
          }
        }
      );

      if (!response.ok) {
        throw new Error('Failed to fetch transactions');
      }

      const data = await response.json();
      setTransactions(data);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }

  if (loading) return <div>Loading transactions...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div className="transaction-history">
      <h2>Transaction History</h2>

      <div className="transactions-list">
        {transactions.map((tx) => (
          <TransactionItem key={tx.id} transaction={tx} />
        ))}
      </div>

      <div className="pagination">
        <button
          onClick={() => setPage(Math.max(0, page - 1))}
          disabled={page === 0}
        >
          Previous
        </button>

        <span>Page {page + 1}</span>

        <button
          onClick={() => setPage(page + 1)}
          disabled={transactions.length === 0}
        >
          Next
        </button>
      </div>
    </div>
  );
}

function TransactionItem({ transaction }) {
  const isCredit = transaction.sign === 'CREDIT';
  const statusIcon = {
    CONFIRMED: '✅',
    PENDING: '⏳',
    DECLINED: '❌',
    REVERTED: '↩️'
  }[transaction.status];

  return (
    <div className="transaction-item">
      <div className="transaction-merchant">
        <span className="merchant-name">
          {transaction.merchantNameLocation}
        </span>
        <span className="transaction-date">
          {new Date(transaction.dateTime).toLocaleDateString()}
        </span>
      </div>

      <div className="transaction-details">
        <span className="category-badge">
          {transaction.mccCategory}
        </span>
        <span className={`amount ${isCredit ? 'credit' : 'debit'}`}>
          {isCredit ? '+' : '-'}
          {transaction.transactionCurrency} {transaction.amountInTransactionCurrency}
        </span>
        <span className="status">{statusIcon}</span>
      </div>
    </div>
  );
}

With Filters

import { useState, useEffect } from 'react';

export function TransactionHistoryWithFilters({ clientId, accessToken }) {
  const [transactions, setTransactions] = useState([]);
  const [loading, setLoading] = useState(false);
  const [filters, setFilters] = useState({
    dateFrom: '',
    dateTo: '',
    searchKey: '',
    mccCategories: [],
    page: 0
  });

  useEffect(() => {
    fetchTransactions();
  }, [filters]);

  async function fetchTransactions() {
    setLoading(true);

    try {
      const params = new URLSearchParams();

      if (filters.dateFrom) params.append('dateFrom', filters.dateFrom);
      if (filters.dateTo) params.append('dateTo', filters.dateTo);
      if (filters.searchKey) params.append('searchKey', filters.searchKey);
      if (filters.mccCategories.length > 0) {
        params.append('mccCategories', filters.mccCategories.join(','));
      }
      params.append('page', filters.page.toString());

      const response = await fetch(
        `/v1/card/transactions?${params}`,
        {
          headers: {
            'X-Client-ID': clientId,
            'Authorization': `Bearer ${accessToken}`
          }
        }
      );

      const data = await response.json();
      setTransactions(data);
    } catch (err) {
      console.error('Failed to fetch transactions:', err);
    } finally {
      setLoading(false);
    }
  }

  function updateFilter(key, value) {
    setFilters(prev => ({ ...prev, [key]: value, page: 0 }));
  }

  function toggleCategory(category) {
    setFilters(prev => ({
      ...prev,
      mccCategories: prev.mccCategories.includes(category)
        ? prev.mccCategories.filter(c => c !== category)
        : [...prev.mccCategories, category],
      page: 0
    }));
  }

  const categories = [
    'FOOD', 'TRAVEL', 'ENTERTAINMENT', 'SUBSCRIPTIONS',
    'HEALTH', 'UTILITIES', 'ATM', 'MISC'
  ];

  return (
    <div className="transaction-history-with-filters">
      <div className="filters">
        <h3>Filters</h3>

        <div className="date-range">
          <input
            type="date"
            placeholder="From"
            value={filters.dateFrom}
            onChange={(e) => updateFilter('dateFrom', e.target.value)}
          />
          <input
            type="date"
            placeholder="To"
            value={filters.dateTo}
            onChange={(e) => updateFilter('dateTo', e.target.value)}
          />
        </div>

        <div className="search">
          <input
            type="text"
            placeholder="Search merchant..."
            value={filters.searchKey}
            onChange={(e) => updateFilter('searchKey', e.target.value)}
          />
        </div>

        <div className="categories">
          {categories.map(category => (
            <button
              key={category}
              onClick={() => toggleCategory(category)}
              className={filters.mccCategories.includes(category) ? 'active' : ''}
            >
              {category}
            </button>
          ))}
        </div>

        <button onClick={() => setFilters({
          dateFrom: '', dateTo: '', searchKey: '',
          mccCategories: [], page: 0
        })}>
          Clear Filters
        </button>
      </div>

      <div className="transactions">
        {loading ? (
          <div>Loading...</div>
        ) : transactions.length > 0 ? (
          transactions.map(tx => (
            <TransactionItem key={tx.id} transaction={tx} />
          ))
        ) : (
          <div>No transactions found</div>
        )}
      </div>
    </div>
  );
}

Transaction Statements

Generate downloadable transaction statements in CSV or PDF format.

Generate Statement

// Generate CSV statement
const response = await fetch(
  'https://dev.api.baanx.com/v1/card/transactions/statement?dateFrom=2024-10-01&dateTo=2024-10-31',
  {
    headers: {
      'Accept': 'text/csv',
      'X-Client-ID': clientId,
      'Authorization': `Bearer ${accessToken}`
    }
  }
);

const csvData = await response.text();

// Download as file
const blob = new Blob([csvData], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'transactions-october-2024.csv';
link.click();

Statement Parameters

dateFrom
string
Start date for statement (ISO 8601). If omitted, includes all transactions from the beginning.
dateTo
string
End date for statement (ISO 8601). If omitted, includes all transactions up to now.
Accept
string
required
Format for statement:
  • text/csv: CSV format
  • application/pdf: PDF format
Unlike the transactions endpoint, you can specify dateFrom without dateTo (or vice versa) for open-ended date ranges.

CSV Format

Timestamp,Merchant,Merchant Type,Transaction Currency,Transaction currency amount,Card currency,Card currency amount,Funding Tokens,Funding Addresses
2024-10-14T10:44:36.276Z,"WWW.ALIEXPRESS.COM, LONDON",OutOfWalletOnline,USD,0.85,EUR,0.79,"usdc","0x3a11a86cf218c448be519728cd3ac5c741fb3424"
2024-10-13T15:22:11.143Z,"UBER TRIP, NEW YORK",OutOfWalletOnline,USD,15.50,EUR,14.32,"usdc","0x3a11a86cf218c448be519728cd3ac5c741fb3424"

Statement Generation Component

import { useState } from 'react';

export function StatementGenerator({ clientId, accessToken }) {
  const [loading, setLoading] = useState(false);
  const [dateFrom, setDateFrom] = useState('');
  const [dateTo, setDateTo] = useState('');
  const [format, setFormat] = useState('csv');

  async function generateStatement() {
    setLoading(true);

    try {
      const params = new URLSearchParams();
      if (dateFrom) params.append('dateFrom', dateFrom);
      if (dateTo) params.append('dateTo', dateTo);

      const response = await fetch(
        `/v1/card/transactions/statement?${params}`,
        {
          headers: {
            'Accept': format === 'csv' ? 'text/csv' : 'application/pdf',
            'X-Client-ID': clientId,
            'Authorization': `Bearer ${accessToken}`
          }
        }
      );

      if (!response.ok) {
        throw new Error('Failed to generate statement');
      }

      // Download file
      const blob = await response.blob();
      const url = URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.href = url;
      link.download = `statement-${dateFrom || 'all'}-${dateTo || 'latest'}.${format}`;
      link.click();
      URL.revokeObjectURL(url);

    } catch (err) {
      console.error('Statement generation failed:', err);
      alert('Failed to generate statement');
    } finally {
      setLoading(false);
    }
  }

  return (
    <div className="statement-generator">
      <h3>Generate Statement</h3>

      <div className="date-range">
        <label>
          From:
          <input
            type="date"
            value={dateFrom}
            onChange={(e) => setDateFrom(e.target.value)}
          />
        </label>

        <label>
          To:
          <input
            type="date"
            value={dateTo}
            onChange={(e) => setDateTo(e.target.value)}
          />
        </label>
      </div>

      <div className="format-selector">
        <label>
          <input
            type="radio"
            value="csv"
            checked={format === 'csv'}
            onChange={(e) => setFormat(e.target.value)}
          />
          CSV
        </label>

        <label>
          <input
            type="radio"
            value="pdf"
            checked={format === 'pdf'}
            onChange={(e) => setFormat(e.target.value)}
          />
          PDF
        </label>
      </div>

      <button
        onClick={generateStatement}
        disabled={loading}
        className="btn-generate"
      >
        {loading ? 'Generating...' : 'Download Statement'}
      </button>

      {!dateFrom && !dateTo && (
        <p className="info-text">
          No date range specified - will include all transactions
        </p>
      )}
    </div>
  );
}

Transaction Analytics

Spending by Category

function calculateSpendingByCategory(transactions) {
  const spending = {};

  transactions.forEach(tx => {
    if (tx.status === 'CONFIRMED' && tx.sign === 'DEBIT') {
      const category = tx.mccCategory;
      const amount = parseFloat(tx.amountInTransactionCurrency);

      spending[category] = (spending[category] || 0) + amount;
    }
  });

  return spending;
}

// Usage
const categorySpending = calculateSpendingByCategory(transactions);
// { FOOD: 150.50, TRAVEL: 450.00, ENTERTAINMENT: 75.25, ... }

Monthly Spending Trend

function calculateMonthlySpending(transactions) {
  const monthlySpend = {};

  transactions.forEach(tx => {
    if (tx.status === 'CONFIRMED' && tx.sign === 'DEBIT') {
      const date = new Date(tx.dateTime);
      const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
      const amount = parseFloat(tx.amountInTransactionCurrency);

      monthlySpend[monthKey] = (monthlySpend[monthKey] || 0) + amount;
    }
  });

  return monthlySpend;
}

// Usage
const monthlyTrend = calculateMonthlySpending(transactions);
// { '2024-10': 675.75, '2024-09': 542.30, ... }

Top Merchants

function getTopMerchants(transactions, limit = 5) {
  const merchantSpend = {};

  transactions.forEach(tx => {
    if (tx.status === 'CONFIRMED' && tx.sign === 'DEBIT') {
      const merchant = tx.merchantNameLocation;
      const amount = parseFloat(tx.amountInTransactionCurrency);

      merchantSpend[merchant] = (merchantSpend[merchant] || 0) + amount;
    }
  });

  return Object.entries(merchantSpend)
    .sort(([, a], [, b]) => b - a)
    .slice(0, limit)
    .map(([merchant, amount]) => ({ merchant, amount }));
}

// Usage
const topMerchants = getTopMerchants(transactions);
// [
//   { merchant: 'AMAZON.COM, SEATTLE', amount: 350.00 },
//   { merchant: 'UBER TRIP, NEW YORK', amount: 125.50 },
//   ...
// ]

Troubleshooting

Cause: Invalid page number or filters too restrictiveSolution: Reset to page 0 and verify filters
// Start from first page
const response = await fetch('/v1/card/transactions?page=0');

// Remove filters
const response = await fetch('/v1/card/transactions');
Cause: Only one of dateFrom/dateTo specifiedSolution: Provide both dates or neither
// ❌ Bad: Only one date
?dateFrom=2024-10-01

// ✅ Good: Both dates
?dateFrom=2024-10-01&dateTo=2024-10-31

// ✅ Good: No dates (all transactions)
// (no date parameters)
Cause: Missing or incorrect Accept headerSolution: Set proper Accept header for desired format
// For CSV
headers: {
  'Accept': 'text/csv'
}

// For PDF
headers: {
  'Accept': 'application/pdf'
}
Cause: Transaction not yet settled or failedSolution: Check transaction status
if (transaction.status === 'PENDING') {
  console.log('Funding sources not yet finalized');
} else if (transaction.status === 'DECLINED') {
  console.log('Transaction declined - no funding occurred');
}

Best Practices

Performance

Implement pagination for large transaction lists
Cache recent transactions locally with reasonable TTL
Use date filters to limit data transfer
Lazy load transaction details on demand

User Experience

Show loading states during fetch
Display transaction status clearly (confirmed, pending, declined)
Group transactions by date for readability
Provide search and filter capabilities
Allow statement downloads for record keeping

Data Display

Format amounts with proper decimal places
Show currency symbols clearly
Display timestamps in user’s timezone
Use color coding for transaction types (debit/credit)
Show funding source details for transparency

Error Handling

async function fetchTransactions(filters) {
  try {
    const response = await fetch(buildTransactionUrl(filters));

    if (!response.ok) {
      if (response.status === 404) {
        return []; // No transactions yet
      }
      throw new Error('Failed to fetch transactions');
    }

    return await response.json();
  } catch (error) {
    console.error('Transaction fetch error:', error);
    // Show user-friendly error message
    throw error;
  }
}

Next Steps