Skip to main content

Overview

The balance inquiry endpoint provides real-time visibility into your merchant account’s financial status, including available funds, pending settlements, and transaction summaries.
Use cases: Verify available funds before payouts, reconcile transactions, monitor cash flow, generate financial reports.

Balance Components

Total PAYIN

All payments received from customers (completed transactions)

Total PAYOUT

All disbursements made to recipients

Total Settled

Funds transferred to your bank account

Available Balance

Funds available for immediate withdrawal or payout

Check Balance

Endpoint

POST https://api-sandbox.88pay.io/api/balance?id={merchant_id}

Authentication

Headers:
Authorization: Bearer {access_token}
x-session-id: {session_id}
Query Parameter:
ParameterTypeRequiredDescription
idstringYour Merchant ID (e.g., MCH-COL-29ZTP4)
No request body required. Balance is fetched based on Merchant ID in query parameter and authenticated token.

Request Examples

curl -X POST "https://api-sandbox.88pay.io/api/balance?id=MCH-COL-29ZTP4" \
  -H "Authorization: Bearer eyJhbGciOiJFUzI1NiIs..." \
  -H "x-session-id: sess_53a2cfc4-441e-4554-b560-4e008784d98e"

Response Structure

Success Response (200 OK)

{
  "status": "Success",
  "code": 200,
  "message": "OK",
  "data": {
    "merchant_id": "MCH-COL-29ZTP4",
    "balance_summary": {
      "total_payin": "34490.00",
      "total_payout": "250010.00",
      "total_settled": "0.00",
      "total_available": "34490.00",
      "net_balance": -215520
    },
    "message": "Balance calculated successfully"
  }
}

Response Fields

merchant_id
string
Your unique merchant identifier
balance_summary
object
Summary of all account balances
balance_summary.total_payin
string
Total received from customersSum of all completed PAYIN transactions (deposits, payments received)Example: "34490.00" = $34,490 COP received
balance_summary.total_payout
string
Total sent to recipientsSum of all completed PAYOUT transactions (withdrawals, disbursements)Example: "250010.00" = $250,010 COP sent
balance_summary.total_settled
string
Total transferred to your bankAmount already withdrawn to your registered bank accountExample: "0.00" = No settlements yet
balance_summary.total_available
string
Available for withdrawal or payoutFunds you can immediately use for payouts or request settlementFormula: total_payin - total_payout - total_settled - fees - reservesExample: "34490.00" = $34,490 COP available
balance_summary.net_balance
number
Net account positionOverall profit/loss positionFormula: total_payin - total_payout
  • Positive = More received than sent (profit)
  • Negative = More sent than received (advance or deficit)
Example: -215520 = $215,520 COP deficit (sent more than received)

Understanding Balance Scenarios

Scenario: Profitable merchant with available funds
    {
      "total_payin": "1000000.00",
      "total_payout": "500000.00",
      "total_settled": "200000.00",
      "total_available": "300000.00",
      "net_balance": 500000
    }
Interpretation:
  • ✅ Received: $1,000,000
  • ✅ Sent out: $500,000
  • ✅ Withdrawn: $200,000
  • ✅ Available: $300,000
  • ✅ Net profit: $500,000
Actions available:
  • Request settlement of $300,000
  • Process payouts up to $300,000

Use Cases & Integration Patterns

1. Pre-Payout Validation

Check balance before processing large payouts:
async function createPayoutWithValidation(payoutAmount) {
  // 1. Check current balance
  const balance = await checkBalance(accessToken, sessionId, merchantId);
  const available = parseFloat(balance.balance_summary.total_available);
  
  // 2. Validate sufficient funds
  if (available < payoutAmount) {
    throw new Error(
      `Insufficient funds. Available: ${available}, Required: ${payoutAmount}`
    );
  }
  
  // 3. Proceed with payout
  const payout = await createPayout({
    amount: payoutAmount,
    // ... other params
  });
  
  return payout;
}

// Usage
try {
  await createPayoutWithValidation(50000);
} catch (error) {
  console.error('Payout failed:', error.message);
  // Notify admin, queue for later, etc.
}

2. Daily Balance Monitoring

Automated daily balance checks:
// Run daily at 9 AM
cron.schedule('0 9 * * *', async () => {
  const balance = await checkBalance(accessToken, sessionId, merchantId);
  
  const available = parseFloat(balance.balance_summary.total_available);
  const netBalance = balance.balance_summary.net_balance;
  
  // Alert if low balance
  if (available < 10000) {
    await sendAlert({
      type: 'low_balance',
      message: `Low balance: ${available} available`,
      severity: 'warning'
    });
  }
  
  // Alert if negative net balance
  if (netBalance < 0) {
    await sendAlert({
      type: 'negative_balance',
      message: `Negative balance: ${netBalance}`,
      severity: 'error'
    });
  }
  
  // Log for records
  await db.balance_history.create({
    merchant_id: merchantId,
    available: available,
    net_balance: netBalance,
    checked_at: new Date()
  });
});

3. Financial Reporting Dashboard

Build real-time dashboard:
async function getDashboardData() {
  const balance = await checkBalance(accessToken, sessionId, merchantId);
  
  const summary = balance.balance_summary;
  
  return {
    // Revenue metrics
    totalRevenue: parseFloat(summary.total_payin),
    totalDisbursed: parseFloat(summary.total_payout),
    netProfit: summary.net_balance,
    
    // Liquidity metrics
    availableFunds: parseFloat(summary.total_available),
    settledToBank: parseFloat(summary.total_settled),
    
    // Calculated metrics
    settlementRate: (
      parseFloat(summary.total_settled) / parseFloat(summary.total_payin) * 100
    ).toFixed(2),
    
    payoutRate: (
      parseFloat(summary.total_payout) / parseFloat(summary.total_payin) * 100
    ).toFixed(2),
    
    // Status
    canProcessPayouts: parseFloat(summary.total_available) > 0,
    needsDeposit: summary.net_balance < 0
  };
}

// Display in dashboard
const dashboard = await getDashboardData();

console.log('Dashboard:');
console.log('Total Revenue:', dashboard.totalRevenue);
console.log('Available Funds:', dashboard.availableFunds);
console.log('Net Profit:', dashboard.netProfit);
console.log('Settlement Rate:', dashboard.settlementRate + '%');

4. Automatic Settlement Trigger

Request settlement when threshold reached:
async function checkAndSettleIfNeeded() {
  const balance = await checkBalance(accessToken, sessionId, merchantId);
  
  const available = parseFloat(balance.balance_summary.total_available);
  const SETTLEMENT_THRESHOLD = 100000; // $100,000
  
  if (available >= SETTLEMENT_THRESHOLD) {
    console.log(`Balance ${available} exceeds threshold. Requesting settlement...`);
    
    // Request settlement to bank account
    await requestSettlement({
      amount: available,
      merchant_id: merchantId
    });
    
    // Notify admin
    await sendNotification({
      type: 'settlement_requested',
      amount: available,
      timestamp: new Date()
    });
  } else {
    console.log(`Balance ${available} below threshold. No settlement needed.`);
  }
}

// Run every 6 hours
setInterval(checkAndSettleIfNeeded, 6 * 60 * 60 * 1000);

Error Responses

    {
      "status": "Error",
      "code": 401,
      "message": "Invalid or expired token"
    }
Cause: Token expired (60s lifetime)Fix: Generate new token and retry

Best Practices

Don’t query on every request:
    class BalanceCache {
      constructor(ttl = 60000) { // 1 minute cache
        this.cache = null;
        this.expiresAt = null;
        this.ttl = ttl;
      }
      
      async getBalance() {
        // Return cached if valid
        if (this.cache && Date.now() < this.expiresAt) {
          return this.cache;
        }
        
        // Fetch fresh balance
        const balance = await checkBalance(accessToken, sessionId, merchantId);
        
        // Cache for TTL
        this.cache = balance;
        this.expiresAt = Date.now() + this.ttl;
        
        return balance;
      }
      
      invalidate() {
        this.cache = null;
        this.expiresAt = null;
      }
    }
    
    const balanceCache = new BalanceCache(60000); // 1 min cache
    
    // Usage
    const balance = await balanceCache.getBalance(); // Cached
    
    // After payout, invalidate cache
    await createPayout(...);
    balanceCache.invalidate();
Monitor critical thresholds:
    async function monitorBalance() {
      const balance = await checkBalance(accessToken, sessionId, merchantId);
      
      const available = parseFloat(balance.balance_summary.total_available);
      const netBalance = balance.balance_summary.net_balance;
      
      // Critical: No funds available
      if (available <= 0) {
        await sendAlert({
          severity: 'critical',
          title: 'Zero Balance',
          message: 'No funds available for payouts'
        });
      }
      
      // Warning: Low balance
      else if (available < 10000) {
        await sendAlert({
          severity: 'warning',
          title: 'Low Balance',
          message: `Only ${available} available`
        });
      }
      
      // Error: Negative net balance
      if (netBalance < 0) {
        await sendAlert({
          severity: 'error',
          title: 'Negative Balance',
          message: `Advance of ${Math.abs(netBalance)} needs repayment`
        });
      }
    }
Track balance over time:
    async function logBalance() {
      const balance = await checkBalance(accessToken, sessionId, merchantId);
      
      await db.balance_snapshots.create({
        merchant_id: merchantId,
        total_payin: balance.balance_summary.total_payin,
        total_payout: balance.balance_summary.total_payout,
        total_settled: balance.balance_summary.total_settled,
        total_available: balance.balance_summary.total_available,
        net_balance: balance.balance_summary.net_balance,
        snapshot_at: new Date()
      });
    }
    
    // Run every hour
    cron.schedule('0 * * * *', logBalance);
    
    // Query historical data
    const history = await db.balance_snapshots
      .where('merchant_id', merchantId)
      .where('snapshot_at', '>=', startDate)
      .orderBy('snapshot_at', 'asc');
    
    // Generate chart/report
Verify balance matches expected:
    // After webhook confirms transaction
    app.post('/webhooks/88pay', async (req, res) => {
      const webhook = req.body;
      
      // Process webhook
      await processWebhook(webhook);
      
      // Fetch updated balance
      const balance = await checkBalance(accessToken, sessionId, merchantId);
      
      // Verify expected vs actual
      const expected = await calculateExpectedBalance();
      const actual = parseFloat(balance.balance_summary.total_available);
      
      const diff = Math.abs(expected - actual);
      
      if (diff > 0.01) { // Allow 1 cent rounding
        await sendAlert({
          severity: 'warning',
          title: 'Balance Mismatch',
          message: `Expected: ${expected}, Actual: ${actual}, Diff: ${diff}`
        });
      }
      
      res.status(200).json({ received: true });
    });

Rate Limits

Balance inquiry endpoint has the following rate limits:
TimeframeLimit
Per minute60 requests
Per hour1,000 requests
Implement caching (1-5 minute TTL) to stay well below these limits. Balance doesn’t change frequently enough to warrant real-time queries.

Troubleshooting

Possible causes:
  1. Transactions not completed yet:
    // Check transaction statuses
    const transactions = await getTransactionHistory();
    const pending = transactions.filter(t => t.status === 'PENDING');
    
    console.log(`${pending.length} transactions still pending`);
  1. All funds settled:
    if (parseFloat(balance.total_settled) === parseFloat(balance.total_payin)) {
      console.log('All funds have been settled to bank');
    }
  1. Negative balance from payouts:
    if (balance.net_balance < 0) {
      console.log('Payouts exceeded deposits');
    }
Check for:
  1. Pending settlements:
    const settled = parseFloat(balance.total_settled);
    if (settled > 0) {
      console.log(`${settled} already withdrawn to bank`);
    }
  1. Reserved funds: Some payment methods have holding periods
  2. Fees deducted: Transaction fees reduce available balance
  3. Recent payouts:
    const payouts = parseFloat(balance.total_payout);
    console.log(`${payouts} sent in payouts`);
This is normal if:You’re using 88Pay’s credit facility to process payouts before receiving deposits.Example scenario:
    Day 1: Process $100K payout (net balance: -$100K)
    Day 2: Receive $50K deposit (net balance: -$50K)
    Day 3: Receive $60K deposit (net balance: +$10K)
Action required: None. Future deposits automatically reduce the advance.
If you don’t expect negative balance, contact support immediately.

Next Steps