Skip to main content

List Balance Transfers

Retrieve a list of balance_transfer resources for your application. This endpoint allows you to view the history of fund movements between internal accounts and track the status of all balance transfers.

Use Cases

1. List Recent Balance Transfers

Retrieve the most recent balance transfers with default pagination:

curl https://api.ahrvo.network/payments/na/balance_transfers \
-u username:password \
-H "Content-Type: application/json"

2. List All Transfers with Custom Pagination

Retrieve transfers with custom page size:

curl "https://api.ahrvo.network/payments/na/balance_transfers?limit=50&offset=0" \
-u username:password \
-H "Content-Type: application/json"

3. Sort Transfers by Creation Date

Get transfers sorted by creation date in descending order (newest first):

curl "https://api.ahrvo.network/payments/na/balance_transfers?sort=created_at,desc&limit=100" \
-u username:password \
-H "Content-Type: application/json"

4. Paginate Through All Transfers

Navigate through large result sets using offset:

async function getAllBalanceTransfers() {
const allTransfers = [];
let offset = 0;
const limit = 100;
let hasMore = true;

while (hasMore) {
const response = await fetch(
`https://api.ahrvo.network/payments/na/balance_transfers?limit=${limit}&offset=${offset}`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
}
}
);

const data = await response.json();
const transfers = data._embedded?.balance_transfers || [];

allTransfers.push(...transfers);

// Check if there are more results
hasMore = transfers.length === limit;
offset += limit;

console.log(`Fetched ${transfers.length} transfers, total: ${allTransfers.length}`);
}

return allTransfers;
}

5. Filter Failed Transfers

Find all failed transfers for investigation:

async function getFailedTransfers() {
const response = await fetch(
'https://api.ahrvo.network/payments/na/balance_transfers?limit=100',
{
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
}
}
);

const data = await response.json();
const allTransfers = data._embedded?.balance_transfers || [];

// Filter client-side for failed transfers
const failedTransfers = allTransfers.filter(t => t.state === 'FAILED');

console.log(`Found ${failedTransfers.length} failed transfers`);

// Analyze failure patterns
failedTransfers.forEach(transfer => {
console.log('Failed Transfer:', {
id: transfer.id,
amount: transfer.amount,
source: transfer.source,
destination: transfer.destination,
created_at: transfer.created_at,
description: transfer.description
});
});

return failedTransfers;
}

6. Calculate Transfer Statistics

Generate statistics from transfer history:

async function calculateTransferStats() {
const allTransfers = await getAllBalanceTransfers();

const stats = {
total_count: allTransfers.length,
total_amount: 0,
succeeded_count: 0,
succeeded_amount: 0,
failed_count: 0,
failed_amount: 0,
pending_count: 0,
pending_amount: 0,
to_fbo_count: 0,
to_fbo_amount: 0,
from_fbo_count: 0,
from_fbo_amount: 0
};

allTransfers.forEach(transfer => {
stats.total_amount += transfer.amount;

// By state
if (transfer.state === 'SUCCEEDED') {
stats.succeeded_count++;
stats.succeeded_amount += transfer.amount;
} else if (transfer.state === 'FAILED') {
stats.failed_count++;
stats.failed_amount += transfer.amount;
} else if (transfer.state === 'PENDING') {
stats.pending_count++;
stats.pending_amount += transfer.amount;
}

// By direction
if (transfer.destination === 'FOR_BENEFIT_OF_ACCOUNT') {
stats.to_fbo_count++;
stats.to_fbo_amount += transfer.amount;
} else if (transfer.source === 'FOR_BENEFIT_OF_ACCOUNT') {
stats.from_fbo_count++;
stats.from_fbo_amount += transfer.amount;
}
});

// Calculate success rate
stats.success_rate = (stats.succeeded_count / stats.total_count * 100).toFixed(2);

// Calculate net FBO flow
stats.net_fbo_flow = stats.to_fbo_amount - stats.from_fbo_amount;

return stats;
}

7. Generate Monthly Transfer Report

Create a comprehensive monthly report:

async function generateMonthlyReport(year, month) {
const allTransfers = await getAllBalanceTransfers();

// Filter transfers for the specified month
const monthlyTransfers = allTransfers.filter(transfer => {
const date = new Date(transfer.created_at);
return date.getFullYear() === year && date.getMonth() === month - 1;
});

const report = {
period: `${year}-${String(month).padStart(2, '0')}`,
total_transfers: monthlyTransfers.length,
transfers_by_state: {
succeeded: 0,
failed: 0,
pending: 0
},
amounts_by_state: {
succeeded: 0,
failed: 0,
pending: 0
},
transfers_by_direction: {
to_fbo: 0,
from_fbo: 0
},
amounts_by_direction: {
to_fbo: 0,
from_fbo: 0
},
daily_breakdown: {}
};

monthlyTransfers.forEach(transfer => {
const date = new Date(transfer.created_at).toISOString().split('T')[0];

// By state
const state = transfer.state.toLowerCase();
report.transfers_by_state[state]++;
report.amounts_by_state[state] += transfer.amount;

// By direction
if (transfer.destination === 'FOR_BENEFIT_OF_ACCOUNT') {
report.transfers_by_direction.to_fbo++;
report.amounts_by_direction.to_fbo += transfer.amount;
} else {
report.transfers_by_direction.from_fbo++;
report.amounts_by_direction.from_fbo += transfer.amount;
}

// Daily breakdown
if (!report.daily_breakdown[date]) {
report.daily_breakdown[date] = {
count: 0,
amount: 0,
to_fbo: 0,
from_fbo: 0
};
}
report.daily_breakdown[date].count++;
report.daily_breakdown[date].amount += transfer.amount;

if (transfer.destination === 'FOR_BENEFIT_OF_ACCOUNT') {
report.daily_breakdown[date].to_fbo += transfer.amount;
} else {
report.daily_breakdown[date].from_fbo += transfer.amount;
}
});

// Calculate net flow
report.net_fbo_flow = report.amounts_by_direction.to_fbo - report.amounts_by_direction.from_fbo;

return report;
}

8. Export Transfers to CSV

Export transfer history to CSV format:

async function exportTransfersToCSV() {
const allTransfers = await getAllBalanceTransfers();

// CSV header
const headers = [
'ID',
'Created At',
'Amount',
'Currency',
'Source',
'Destination',
'State',
'Description',
'Reference ID',
'Processor Type'
];

// Build CSV rows
const rows = allTransfers.map(transfer => [
transfer.id,
transfer.created_at,
(transfer.amount / 100).toFixed(2), // Convert cents to dollars
transfer.currency,
transfer.source,
transfer.destination,
transfer.state,
`"${transfer.description || ''}"`, // Quote description to handle commas
transfer.reference_id,
transfer.processor_type
]);

// Combine header and rows
const csv = [
headers.join(','),
...rows.map(row => row.join(','))
].join('\n');

// Save to file or return
return csv;
}

9. Monitor Pending Transfers

Track pending transfers and check their status:

async function monitorPendingTransfers() {
const response = await fetch(
'https://api.ahrvo.network/payments/na/balance_transfers?limit=100',
{
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
}
}
);

const data = await response.json();
const allTransfers = data._embedded?.balance_transfers || [];

// Find pending transfers
const pendingTransfers = allTransfers.filter(t => t.state === 'PENDING');

if (pendingTransfers.length === 0) {
console.log('No pending transfers');
return [];
}

console.log(`Found ${pendingTransfers.length} pending transfers`);

// Check age of pending transfers
const now = Date.now();
const alertThreshold = 30 * 60 * 1000; // 30 minutes

const stalePending = pendingTransfers.filter(transfer => {
const age = now - new Date(transfer.created_at).getTime();
return age > alertThreshold;
});

if (stalePending.length > 0) {
console.warn(`ALERT: ${stalePending.length} transfers pending for over 30 minutes`);
stalePending.forEach(transfer => {
const ageMinutes = Math.floor((now - new Date(transfer.created_at).getTime()) / 60000);
console.warn(`Transfer ${transfer.id} pending for ${ageMinutes} minutes`);
});

// Send alert
await sendAlert('Stale pending transfers detected', stalePending);
}

return pendingTransfers;
}

10. Find Transfers by Tag

Search for transfers with specific tags:

async function findTransfersByTag(tagKey, tagValue) {
const allTransfers = await getAllBalanceTransfers();

// Filter transfers by tag
const matchingTransfers = allTransfers.filter(transfer => {
return transfer.tags && transfer.tags[tagKey] === tagValue;
});

console.log(`Found ${matchingTransfers.length} transfers with tag ${tagKey}=${tagValue}`);

return matchingTransfers;
}

// Example usage:
// Find all daily reconciliation transfers
const reconciliationTransfers = await findTransfersByTag('purpose', 'daily_reconciliation');

// Find transfers from a specific date
const dateTransfers = await findTransfersByTag('date', '2025-12-10');

Query Parameters

ParameterTypeRequiredDefaultDescription
limitintegerNo20Number of items to return (max typically 100)
offsetintegerNo0Number of items to skip before returning results
sortstringNo-Sort order (e.g., "created_at,desc" or "amount,asc")

Response Structure

The response contains:

  • _embedded.balance_transfers: Array of balance transfer objects
  • _links: HATEOAS navigation links
  • page: Pagination information
    • offset: Current offset
    • limit: Items per page
    • count: Total number of items in this response

Transfer Fields

Each balance transfer in the array contains:

FieldTypeDescription
idstringUnique identifier for the balance transfer
created_atstringISO 8601 timestamp of creation
updated_atstringISO 8601 timestamp of last update
amountintegerAmount transferred in cents
currencystring3-letter ISO 4217 currency code
descriptionstringTransfer description
sourcestringSource account type
destinationstringDestination account type
statestringTransfer state: SUCCEEDED, FAILED, or PENDING
processor_typestringProcessor used for the transfer
reference_idstringInternal reference ID
external_reference_idstringExternal reference ID (if any)
tagsobjectCustom metadata

Best Practices

1. Use Pagination Wisely

Don't fetch more data than you need:

// Good: Fetch only what you need
async function getRecentTransfers(count = 20) {
const response = await fetch(
`https://api.ahrvo.network/payments/na/balance_transfers?limit=${count}&sort=created_at,desc`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);

const data = await response.json();
return data._embedded?.balance_transfers || [];
}

// Bad: Always fetching maximum
// Don't do this unless you really need all transfers

2. Cache Appropriately

Cache transfer lists when appropriate to reduce API calls:

class BalanceTransferCache {
constructor(ttl = 5 * 60 * 1000) { // 5 minute default TTL
this.cache = null;
this.cacheTime = null;
this.ttl = ttl;
}

async getTransfers(forceRefresh = false) {
const now = Date.now();

if (!forceRefresh && this.cache && (now - this.cacheTime) < this.ttl) {
console.log('Returning cached transfers');
return this.cache;
}

console.log('Fetching fresh transfers');
const response = await fetch(
'https://api.ahrvo.network/payments/na/balance_transfers?limit=100',
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);

const data = await response.json();
this.cache = data._embedded?.balance_transfers || [];
this.cacheTime = now;

return this.cache;
}

invalidate() {
this.cache = null;
this.cacheTime = null;
}
}

const transferCache = new BalanceTransferCache();

3. Handle Empty Results

Always check for empty result sets:

async function getTransfers() {
const response = await fetch(
'https://api.ahrvo.network/payments/na/balance_transfers',
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);

const data = await response.json();
const transfers = data._embedded?.balance_transfers || [];

if (transfers.length === 0) {
console.log('No balance transfers found');
return [];
}

console.log(`Found ${transfers.length} transfers`);
return transfers;
}

4. Filter Client-Side

Since the API doesn't support filtering by state or account type, filter on the client:

async function getTransfersByState(state) {
const allTransfers = await getTransfers();
return allTransfers.filter(t => t.state === state);
}

async function getTransfersToFBO() {
const allTransfers = await getTransfers();
return allTransfers.filter(t => t.destination === 'FOR_BENEFIT_OF_ACCOUNT');
}

async function getTransfersFromFBO() {
const allTransfers = await getTransfers();
return allTransfers.filter(t => t.source === 'FOR_BENEFIT_OF_ACCOUNT');
}

5. Track Net Flow

Calculate net fund movement:

async function calculateNetFlow() {
const allTransfers = await getAllBalanceTransfers();

const succeededTransfers = allTransfers.filter(t => t.state === 'SUCCEEDED');

const toFBO = succeededTransfers
.filter(t => t.destination === 'FOR_BENEFIT_OF_ACCOUNT')
.reduce((sum, t) => sum + t.amount, 0);

const fromFBO = succeededTransfers
.filter(t => t.source === 'FOR_BENEFIT_OF_ACCOUNT')
.reduce((sum, t) => sum + t.amount, 0);

const netFlow = toFBO - fromFBO;

return {
total_to_fbo: toFBO,
total_from_fbo: fromFBO,
net_flow: netFlow,
net_flow_formatted: `$${(Math.abs(netFlow) / 100).toFixed(2)} ${netFlow >= 0 ? 'to FBO' : 'from FBO'}`
};
}

6. Set Up Automated Reporting

Generate regular reports from transfer history:

async function dailyTransferReport() {
const today = new Date().toISOString().split('T')[0];
const allTransfers = await getAllBalanceTransfers();

// Filter today's transfers
const todayTransfers = allTransfers.filter(transfer => {
return transfer.created_at.startsWith(today);
});

const report = {
date: today,
total_count: todayTransfers.length,
total_amount: todayTransfers.reduce((sum, t) => sum + t.amount, 0),
succeeded: todayTransfers.filter(t => t.state === 'SUCCEEDED').length,
failed: todayTransfers.filter(t => t.state === 'FAILED').length,
pending: todayTransfers.filter(t => t.state === 'PENDING').length,
to_fbo_amount: todayTransfers
.filter(t => t.destination === 'FOR_BENEFIT_OF_ACCOUNT')
.reduce((sum, t) => sum + t.amount, 0),
from_fbo_amount: todayTransfers
.filter(t => t.source === 'FOR_BENEFIT_OF_ACCOUNT')
.reduce((sum, t) => sum + t.amount, 0)
};

console.log('Daily Transfer Report:', report);

// Send report via email or notification
await sendDailyReport(report);

return report;
}

// Schedule daily at midnight
// scheduleCronJob('0 0 * * *', dailyTransferReport);

Common Workflows

Reconciliation Workflow

async function reconcileBalanceTransfers() {
// 1. Get all successful transfers
const allTransfers = await getAllBalanceTransfers();
const successfulTransfers = allTransfers.filter(t => t.state === 'SUCCEEDED');

// 2. Calculate total movements
const toFBO = successfulTransfers
.filter(t => t.destination === 'FOR_BENEFIT_OF_ACCOUNT')
.reduce((sum, t) => sum + t.amount, 0);

const fromFBO = successfulTransfers
.filter(t => t.source === 'FOR_BENEFIT_OF_ACCOUNT')
.reduce((sum, t) => sum + t.amount, 0);

// 3. Get current account balances
const operatingBalance = await getAccountBalance('OPERATING_ACCOUNT');
const fboBalance = await getAccountBalance('FOR_BENEFIT_OF_ACCOUNT');

// 4. Compare with expected balances
console.log('Reconciliation Results:', {
total_to_fbo: toFBO,
total_from_fbo: fromFBO,
net_fbo_change: toFBO - fromFBO,
current_operating_balance: operatingBalance,
current_fbo_balance: fboBalance
});

// 5. Flag discrepancies
const netChange = toFBO - fromFBO;
const tolerance = 100; // $1.00 tolerance

if (Math.abs(netChange - (fboBalance - previousFBOBalance)) > tolerance) {
console.warn('RECONCILIATION DISCREPANCY DETECTED');
await alertFinanceTeam('Balance transfer reconciliation mismatch');
}
}

Audit Trail Report

async function generateAuditTrail(startDate, endDate) {
const allTransfers = await getAllBalanceTransfers();

// Filter by date range
const transfers = allTransfers.filter(transfer => {
const date = new Date(transfer.created_at);
return date >= startDate && date <= endDate;
});

// Group by day
const byDay = {};
transfers.forEach(transfer => {
const day = transfer.created_at.split('T')[0];
if (!byDay[day]) {
byDay[day] = [];
}
byDay[day].push(transfer);
});

// Generate detailed report
const auditReport = {
period: {
start: startDate.toISOString(),
end: endDate.toISOString()
},
summary: {
total_transfers: transfers.length,
total_amount: transfers.reduce((sum, t) => sum + t.amount, 0),
success_count: transfers.filter(t => t.state === 'SUCCEEDED').length,
failure_count: transfers.filter(t => t.state === 'FAILED').length
},
daily_details: byDay
};

return auditReport;
}

Interactive API Reference