Skip to main content

List Balance Entries

Retrieve a list of Balance Entry resources for a specific balance. A Balance Entry reflects a change to an application's Balance due to a Transfer or a Balance Adjustment (i.e., "top-up"). This endpoint provides a complete ledger of all transactions affecting a balance.

Use Cases

1. Get All Entries for a Balance

Retrieve the complete transaction history:

curl "https://api.ahrvo.network/payments/na/balances/balance_dyz5APsJebDef9oTW9J2Hc/balance_entries?limit=100" \
-u username:password \
-H "Content-Type: application/json"

2. Generate Balance Ledger

Create a complete ledger of all balance changes:

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

const data = await response.json();
const entries = data._embedded?.balance_entries || [];

const ledger = entries.map(entry => ({
date: entry.posted_at || entry.created_at,
type: entry.type,
description: entry.description || entry.entity_type,
debit: entry.amount < 0 ? Math.abs(entry.amount) : null,
credit: entry.amount > 0 ? entry.amount : null,
amount_formatted: `${entry.amount >= 0 ? '+' : ''}$${(entry.amount / 100).toFixed(2)}`,
reference: entry.entity_id,
state: entry.state
}));

console.log('Balance Ledger:', ledger);

return ledger;
}

3. Calculate Total Movements

Sum all debits and credits:

async function calculateTotalMovements(balanceId) {
const response = await fetch(
`https://api.ahrvo.network/payments/na/balances/${balanceId}/balance_entries?limit=100`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);

const data = await response.json();
const entries = data._embedded?.balance_entries || [];

const movements = {
total_entries: entries.length,
total_credits: entries.filter(e => e.amount > 0).reduce((sum, e) => sum + e.amount, 0),
total_debits: Math.abs(entries.filter(e => e.amount < 0).reduce((sum, e) => sum + e.amount, 0)),
net_change: entries.reduce((sum, e) => sum + e.amount, 0),
by_type: {},
formatted: {}
};

// Group by type
entries.forEach(entry => {
if (!movements.by_type[entry.type]) {
movements.by_type[entry.type] = {
count: 0,
total_amount: 0
};
}
movements.by_type[entry.type].count++;
movements.by_type[entry.type].total_amount += entry.amount;
});

movements.formatted = {
credits: `$${(movements.total_credits / 100).toLocaleString()}`,
debits: `$${(movements.total_debits / 100).toLocaleString()}`,
net: `${movements.net_change >= 0 ? '+' : ''}$${(movements.net_change / 100).toLocaleString()}`
};

return movements;
}

4. Filter by Entry Type

Find all entries of a specific type:

async function getEntriesByType(balanceId, entryType) {
const response = await fetch(
`https://api.ahrvo.network/payments/na/balances/${balanceId}/balance_entries?limit=100`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);

const data = await response.json();
const entries = data._embedded?.balance_entries || [];

const filtered = entries.filter(e => e.type === entryType);

console.log(`Found ${filtered.length} entries of type ${entryType}`);

return filtered;
}

// Example: Get all ACH top-ups
const achTopUps = await getEntriesByType('balance_dyz5APsJebDef9oTW9J2Hc', 'BALANCE_TOP_UP_ACH');

5. Find Recent Transactions

Get the last N transactions:

async function getRecentTransactions(balanceId, limit = 10) {
const response = await fetch(
`https://api.ahrvo.network/payments/na/balances/${balanceId}/balance_entries?limit=${limit}`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);

const data = await response.json();
const entries = data._embedded?.balance_entries || [];

const recent = entries
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at))
.slice(0, limit)
.map(entry => ({
id: entry.id,
date: entry.posted_at || entry.created_at,
type: entry.type,
amount: `${entry.amount >= 0 ? '+' : ''}$${(entry.amount / 100).toFixed(2)}`,
state: entry.state,
reference: entry.entity_id
}));

return recent;
}

6. Export to CSV

Export balance entries to CSV format:

async function exportEntriesToCSV(balanceId) {
const response = await fetch(
`https://api.ahrvo.network/payments/na/balances/${balanceId}/balance_entries?limit=100`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);

const data = await response.json();
const entries = data._embedded?.balance_entries || [];

const headers = [
'Entry ID',
'Created At',
'Posted At',
'Type',
'Amount',
'Currency',
'State',
'Entity Type',
'Entity ID',
'Description'
];

const rows = entries.map(entry => [
entry.id,
entry.created_at,
entry.posted_at,
entry.type,
(entry.amount / 100).toFixed(2),
entry.currency,
entry.state,
entry.entity_type,
entry.entity_id,
`"${entry.description || ''}"`
]);

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

return csv;
}

Path Parameters

ParameterTypeRequiredDescription
balance_idstringYesThe unique ID of the Balance

Query Parameters

ParameterTypeRequiredDefaultDescription
limitintegerNo10Number of items to return per page
after_cursorstringNo-Return items created after this cursor
before_cursorstringNo-Return items created before this cursor

Response Structure

FieldTypeDescription
_embedded.balance_entriesarrayArray of Balance Entry objects
_links.self.hrefstringURL to current page
_links.next.hrefstringURL to next page (if available)
page.limitintegerNumber of items per page
page.next_cursorstringCursor for next page (if available)

Balance Entry Fields

Each entry contains:

FieldTypeDescription
idstringUnique identifier
created_atstringISO 8601 timestamp of creation
updated_atstringISO 8601 timestamp of last update
amountintegerAmount in cents (positive = credit, negative = debit)
created_bystringEntity that created the entry
currencystringCurrency code
descriptionstringDescription of the entry
entity_idstringID of the source entity
entity_typestringType of source entity
estimated_posted_datestringEstimated posting date
linked_tostringID of linked Application
linked_typestringType of linked entity
posted_atstringActual posting date
statestringEntry state
typestringType of balance entry

Best Practices

1. Use Pagination for Large Ledgers

Always paginate when retrieving many entries:

async function getAllBalanceEntries(balanceId) {
const allEntries = [];
let nextCursor = null;

do {
const url = nextCursor
? `https://api.ahrvo.network/payments/na/balances/${balanceId}/balance_entries?limit=100&after_cursor=${nextCursor}`
: `https://api.ahrvo.network/payments/na/balances/${balanceId}/balance_entries?limit=100`;

const response = await fetch(url, {
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
});

const data = await response.json();
const entries = data._embedded?.balance_entries || [];

allEntries.push(...entries);
nextCursor = data.page?.next_cursor;

console.log(`Fetched ${entries.length} entries, total: ${allEntries.length}`);

} while (nextCursor);

return allEntries;
}

2. Reconcile Entry Totals with Balance

Verify entries match current balance:

async function reconcileEntriesWithBalance(balanceId) {
// Get current balance
const balanceResponse = await fetch(
`https://api.ahrvo.network/payments/na/balances/${balanceId}`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);
const balance = await balanceResponse.json();

// Get all entries
const entries = await getAllBalanceEntries(balanceId);

// Calculate total from entries
const calculatedTotal = entries
.filter(e => e.state === 'SUCCEEDED')
.reduce((sum, e) => sum + e.amount, 0);

const reconciliation = {
balance_id: balanceId,
current_posted_balance: balance.posted_amount,
calculated_from_entries: calculatedTotal,
matches: balance.posted_amount === calculatedTotal,
variance: balance.posted_amount - calculatedTotal,
entries_count: entries.length,
succeeded_entries: entries.filter(e => e.state === 'SUCCEEDED').length
};

if (!reconciliation.matches) {
console.error('⚠️ RECONCILIATION MISMATCH:', reconciliation);
} else {
console.log('✅ Balance reconciled successfully');
}

return reconciliation;
}

3. Group Entries by Date

Create daily summaries:

async function groupEntriesByDate(balanceId) {
const entries = await getAllBalanceEntries(balanceId);

const byDate = {};

entries.forEach(entry => {
const date = (entry.posted_at || entry.created_at).split('T')[0];

if (!byDate[date]) {
byDate[date] = {
date,
entries: [],
total_credits: 0,
total_debits: 0,
net_change: 0,
count: 0
};
}

byDate[date].entries.push(entry);
byDate[date].count++;
byDate[date].net_change += entry.amount;

if (entry.amount > 0) {
byDate[date].total_credits += entry.amount;
} else {
byDate[date].total_debits += Math.abs(entry.amount);
}
});

return byDate;
}

4. Track Entry Processing Times

Monitor how long entries take to post:

async function analyzeProcessingTimes(balanceId) {
const entries = await getAllBalanceEntries(balanceId);

const analysis = entries.map(entry => {
const created = new Date(entry.created_at);
const posted = new Date(entry.posted_at);
const processingTime = posted - created;

return {
entry_id: entry.id,
type: entry.type,
processing_time_ms: processingTime,
processing_time_hours: (processingTime / 3600000).toFixed(2),
created_at: entry.created_at,
posted_at: entry.posted_at
};
});

const avgProcessingTime = analysis.reduce((sum, a) => sum + a.processing_time_ms, 0) / analysis.length;

return {
entries_analyzed: analysis.length,
average_processing_time_hours: (avgProcessingTime / 3600000).toFixed(2),
slowest_entry: analysis.sort((a, b) => b.processing_time_ms - a.processing_time_ms)[0],
fastest_entry: analysis.sort((a, b) => a.processing_time_ms - b.processing_time_ms)[0],
details: analysis
};
}

5. Generate Monthly Report

Create comprehensive monthly summaries:

async function generateMonthlyReport(balanceId, year, month) {
const entries = await getAllBalanceEntries(balanceId);

// Filter for specific month
const monthlyEntries = entries.filter(entry => {
const date = new Date(entry.posted_at || entry.created_at);
return date.getFullYear() === year && date.getMonth() === month - 1;
});

const report = {
period: `${year}-${String(month).padStart(2, '0')}`,
total_entries: monthlyEntries.length,
total_credits: 0,
total_debits: 0,
net_change: 0,
by_type: {},
by_state: {
SUCCEEDED: 0,
FAILED: 0,
PENDING: 0
}
};

monthlyEntries.forEach(entry => {
report.net_change += entry.amount;

if (entry.amount > 0) {
report.total_credits += entry.amount;
} else {
report.total_debits += Math.abs(entry.amount);
}

// By type
if (!report.by_type[entry.type]) {
report.by_type[entry.type] = {
count: 0,
total_amount: 0
};
}
report.by_type[entry.type].count++;
report.by_type[entry.type].total_amount += entry.amount;

// By state
report.by_state[entry.state] = (report.by_state[entry.state] || 0) + 1;
});

report.formatted = {
total_credits: `$${(report.total_credits / 100).toLocaleString()}`,
total_debits: `$${(report.total_debits / 100).toLocaleString()}`,
net_change: `${report.net_change >= 0 ? '+' : ''}$${(report.net_change / 100).toLocaleString()}`
};

return report;
}

Common Workflows

Complete Audit Trail

async function generateCompleteAuditTrail(balanceId, outputFormat = 'json') {
const entries = await getAllBalanceEntries(balanceId);

const audit = {
generated_at: new Date().toISOString(),
balance_id: balanceId,
total_entries: entries.length,
entries: entries.map(entry => ({
timestamp: entry.posted_at || entry.created_at,
entry_id: entry.id,
type: entry.type,
amount: entry.amount,
amount_formatted: `${entry.amount >= 0 ? '+' : ''}$${(entry.amount / 100).toFixed(2)}`,
state: entry.state,
source: {
entity_type: entry.entity_type,
entity_id: entry.entity_id
},
description: entry.description,
created_by: entry.created_by
}))
};

if (outputFormat === 'csv') {
return exportEntriesToCSV(balanceId);
}

return audit;
}

Daily Balance Reconciliation

async function performDailyReconciliation(balanceId) {
const today = new Date().toISOString().split('T')[0];
const entries = await getAllBalanceEntries(balanceId);

// Get today's entries
const todayEntries = entries.filter(entry => {
return (entry.posted_at || entry.created_at).startsWith(today);
});

const reconciliation = {
date: today,
entries_count: todayEntries.length,
total_credits: todayEntries.filter(e => e.amount > 0).reduce((sum, e) => sum + e.amount, 0),
total_debits: Math.abs(todayEntries.filter(e => e.amount < 0).reduce((sum, e) => sum + e.amount, 0)),
net_change: todayEntries.reduce((sum, e) => sum + e.amount, 0),
by_state: {
succeeded: todayEntries.filter(e => e.state === 'SUCCEEDED').length,
failed: todayEntries.filter(e => e.state === 'FAILED').length,
pending: todayEntries.filter(e => e.state === 'PENDING').length
}
};

console.log('Daily Reconciliation:', reconciliation);

return reconciliation;
}

// Schedule daily at midnight
// scheduleCronJob('0 0 * * *', () => performDailyReconciliation('balance_dyz5APsJebDef9oTW9J2Hc'));

Interactive API Reference