Reconcile Transactions & Fees

A complete picture of financial activity in your Program requires combining three non-overlapping datasets: card transactions, card fees, and wallet transactions. This guide explains how each dataset relates to the others, how to correlate them, and how to verify that your reconciliation balances.

The three datasets

flowchart LR
    subgraph Card["Per-card activity"]
        TXN["GET /cards/transactions\nSpend events, auth holds,\nclearing, settlement, declines"]
        FEE["GET /cards/fees\nFX fees, off-ramp fees,\nATM fees, card fees, etc."]
    end
    subgraph Wallet["Wallet activity"]
        WAL["GET /wallets/transactions\nBank deposits, wallet-to-card\ntransfers, wallet-to-wallet"]
    end

    TXN -.->|"linked via\nauthRefNum"| FEE
DatasetWhat it containsWhat it doesn't contain
GET /cards/transactionsAll card-level spend events (authorized, cleared, settled, declined)Fees, wallet movements
GET /cards/feesAll fee events associated with card activityTransaction spend amounts, wallet movements
GET /wallets/transactionsAll wallet-level movements (bank deposits, wallet→card transfers, wallet→wallet transfers)Card spend transactions, card fees

A single real-world event may produce records in more than one dataset — for example, a foreign currency purchase produces both a transaction record (in GET /cards/transactions) and an FX fee record (in GET /cards/fees). The two records share an authRefNum for correlation.


Step 1: Define your reconciliation window

Choose a date range and the scope — by cardId, cardholderId, or across the whole Program:

ScopeParameters
Single cardcardId filter on all three endpoints
Single cardholder (all their cards)cardholderId filter
Whole Program for a periodbefore / after timestamp filters only

All three endpoints support before and after Unix timestamp parameters. Use the same values across all three queries so the windows align.


Step 2: Query all three datasets

Query each endpoint for your window, paginating as needed (limit / offset):

GET /cards/transactions?cardId={cardId}&after={startTs}&before={endTs}&limit=200&offset=0
GET /cards/fees?cardId={cardId}&after={startTs}&before={endTs}&limit=200&offset=0
GET /wallets/transactions?cardId={cardId}&after={startTs}&before={endTs}
📘

Paginate until total is exhausted. Each response includes a total field (for transactions and fees). Repeat with incrementing offset until you've retrieved all records — don't assume a single page covers the full window. Wallet transactions may not be paginated; retrieve all and filter client-side. See Pagination, Filtering & Rate Limits.


Step 3: Correlate fees to transactions

Use authRefNum to join fee records to their associated transaction:

transactions WHERE authRefNum = "AUTH123"  →  fees WHERE authRefNum = "AUTH123"

This link is the key to attributing fees to the specific transaction that incurred them. For reconciliation, build a composite event record — one transaction entry plus its associated fee entries — per authRefNum.

Fee accumulation rules to keep in mind:

  • A foreign-currency transaction may carry both an FX fee and an off-ramp fee (if funded via digital assets)
  • A foreign-currency transaction carries a foreign transaction fee instead of (not in addition to) a standard transaction fee — they're disjunctive
  • Some fees (card issuance, monthly maintenance, ATM balance enquiry) may have no associated authRefNum

See Transactions & Fees for the full fee accumulation rules.


Step 4: Reconcile wallet movements separately

Wallet transactions are independent of card transactions — they don't appear in GET /cards/transactions or GET /cards/fees. Query them separately and reconcile against the card's or wallet's balance movements:

  • A wallet-to-card transfer (POST /wallets/transfer, destinationType = 1) should produce an entry in GET /wallets/transactions and be reflected in GET /cards/{cardId}/balance as an availableBalance increase
  • An inbound wallet bank deposit should appear in wallet transactions and increase the wallet balance

Step 5: Balance verification

Verify your reconciled dataset against the actual balance:

GET /cards/{cardId}/balance
→ { "ledgerBalance": 125000, "availableBalance": 112500, "currency": 0 }

The reconciliation check:

Opening balance
+ Sum of inbound card transactions (credits)
+ Sum of wallet-to-card transfers
- Sum of outbound card transactions (debits)
- Sum of fees
= Closing availableBalance
🚧

ledgerBalance vs availableBalance timing. ledgerBalance includes pending amounts (authorized but not yet cleared, or in-process deposits). If your reconciliation window spans a period with pending items, the ledgerBalance will be higher than your reconciled figure. Use availableBalance as the reconciliation target for fully settled activity.


Reconciliation flow diagram

flowchart TD
    A["Define window:\ndate range + cardId/cardholderId"]
    B["GET /cards/transactions\n(paginate to total)"]
    C["GET /cards/fees\n(paginate to total)"]
    D["GET /wallets/transactions"]
    E["Join fees to transactions\nvia authRefNum"]
    F["Compile wallet movements\nseparately"]
    G["Sum net card activity:\ncredits - debits - fees"]
    H["Sum wallet movements\naffecting card balance"]
    I["Opening balance +\nnet card activity +\nwallet movements\n= Closing balance"]
    J["Compare to\nGET /cards/{id}/balance\navailableBalance"]
    K{"Match?"}
    L["✅ Reconciled"]
    M["❌ Investigate discrepancy\n(pending items? missing page? timing?)"]

    A --> B & C & D
    B & C --> E
    E --> G
    D --> F
    F --> H
    G & H --> I
    I --> J
    J --> K
    K -- "Yes" --> L
    K -- "No" --> M

Common discrepancy causes

SymptomLikely cause
Calculated balance higher than availableBalancePending outbound authorization holds exist (ledgerBalance would match)
Calculated balance lower than availableBalanceA deposit or transfer is reflected in availableBalance but not yet in a transaction record
Fee record without a matching transactionStand-alone fee (e.g., monthly maintenance, issuance) — no authRefNum to join on
Transaction without a fee recordNormal for domestic, same-currency transactions
transStatus = 1 (Pending) transactions in windowAuthorization holds that haven't cleared yet — exclude from settled reconciliation or include in a separate pending summary

What's next