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
| Parameter | Type | Required | Description |
|---|---|---|---|
balance_id | string | Yes | The unique ID of the Balance |
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
limit | integer | No | 10 | Number of items to return per page |
after_cursor | string | No | - | Return items created after this cursor |
before_cursor | string | No | - | Return items created before this cursor |
Response Structure
| Field | Type | Description |
|---|---|---|
_embedded.balance_entries | array | Array of Balance Entry objects |
_links.self.href | string | URL to current page |
_links.next.href | string | URL to next page (if available) |
page.limit | integer | Number of items per page |
page.next_cursor | string | Cursor for next page (if available) |
Balance Entry Fields
Each entry contains:
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier |
created_at | string | ISO 8601 timestamp of creation |
updated_at | string | ISO 8601 timestamp of last update |
amount | integer | Amount in cents (positive = credit, negative = debit) |
created_by | string | Entity that created the entry |
currency | string | Currency code |
description | string | Description of the entry |
entity_id | string | ID of the source entity |
entity_type | string | Type of source entity |
estimated_posted_date | string | Estimated posting date |
linked_to | string | ID of linked Application |
linked_type | string | Type of linked entity |
posted_at | string | Actual posting date |
state | string | Entry state |
type | string | Type 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'));