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:
Parameter Type Required Description idstring ✅ Your 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
JavaScript
Python
PHP
Go
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
Your unique merchant identifier
Summary of all account balances
balance_summary.total_payin
Total received from customers Sum of all completed PAYIN transactions (deposits, payments received) Example: "34490.00" = $34,490 COP received
balance_summary.total_payout
Total sent to recipients Sum of all completed PAYOUT transactions (withdrawals, disbursements) Example: "250010.00" = $250,010 COP sent
balance_summary.total_settled
Total transferred to your bank Amount already withdrawn to your registered bank account Example: "0.00" = No settlements yet
balance_summary.total_available
Available for withdrawal or payout Funds you can immediately use for payouts or request settlement Formula: total_payin - total_payout - total_settled - fees - reserves Example: "34490.00" = $34,490 COP available
balance_summary.net_balance
Net account position Overall profit/loss position Formula: 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
Scenario: Merchant using 88Pay credit facility {
"total_payin" : "34490.00" ,
"total_payout" : "250010.00" ,
"total_settled" : "0.00" ,
"total_available" : "34490.00" ,
"net_balance" : -215520
}
Interpretation:
✅ Received: $34,490
✅ Sent out: $250,010
⚠️ Advance used: $215,520 (88Pay fronted this)
✅ Available: $34,490 (new deposits)
What this means:
You’ve sent more than you’ve received
88Pay provided a credit advance
New deposits reduce the advance balance
Cannot request settlement until advance is repaid
Negative net balance indicates you owe 88Pay. Future deposits automatically reduce this debt.
Scenario: All funds settled or used {
"total_payin" : "500000.00" ,
"total_payout" : "100000.00" ,
"total_settled" : "400000.00" ,
"total_available" : "0.00" ,
"net_balance" : 400000
}
Interpretation:
✅ Received: $500,000
✅ Sent out: $100,000
✅ Withdrawn: $400,000
⚠️ Available: $0 (nothing left)
✅ Net profit: $400,000
Actions:
Cannot process new payouts
Cannot request settlement
Wait for new deposits
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
401 Unauthorized
403 Forbidden
404 Not Found
{
"status" : "Error" ,
"code" : 401 ,
"message" : "Invalid or expired token"
}
Cause: Token expired (60s lifetime)Fix: Generate new token and retry {
"status" : "Error" ,
"code" : 403 ,
"message" : "Merchant ID does not match authenticated user"
}
Cause: Query parameter id doesn’t match token’s merchant IDFix: Ensure id parameter matches your Merchant ID {
"status" : "Error" ,
"code" : 404 ,
"message" : "Merchant not found"
}
Cause: Invalid Merchant IDFix: Verify Merchant ID in dashboard
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:
Timeframe Limit Per minute 60 requests Per hour 1,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
Balance shows zero but transactions exist
Possible causes:
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` );
All funds settled:
if ( parseFloat ( balance . total_settled ) === parseFloat ( balance . total_payin )) {
console . log ( 'All funds have been settled to bank' );
}
Negative balance from payouts:
if ( balance . net_balance < 0 ) {
console . log ( 'Payouts exceeded deposits' );
}
Available balance less than expected
Check for:
Pending settlements:
const settled = parseFloat ( balance . total_settled );
if ( settled > 0 ) {
console . log ( ` ${ settled } already withdrawn to bank` );
}
Reserved funds: Some payment methods have holding periods
Fees deducted: Transaction fees reduce available balance
Recent payouts:
const payouts = parseFloat ( balance . total_payout );
console . log ( ` ${ payouts } sent in payouts` );
Negative net balance explanation
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