Ledger Statement

Bank-style account statement

Ledger Statement

The Ledger Statement provides a bank-style account statement showing all movements that affected your virtual account balance over a specified period.

What is a Ledger Statement?

Think of it like a bank statement for your virtual account:

  • Opening Balance: Your balance at the start of the period
  • Movements: Every debit and credit that occurred
  • Running Balance: Balance after each movement
  • Closing Balance: Your balance at the end of the period

Key Concepts

Movement Types

TypeDescriptionEffect on Balance
creditFunds added to accountIncreases balance
debitFunds removed from accountDecreases balance

Movement Categories

CategoryDescription
PAYINCustomer payment received
PAYOUTProvider payment sent
BALANCE_TRANSFERCurrency exchange between accounts
SETTLEMENTAutomatic withdrawal to bank
ADJUSTMENTManual balance correction
FEEService fee charged
TAXTax withholding
ONRAMPFiat used to buy crypto
OFFRAMPCrypto sold for fiat
REVERSETransaction reversal
OTHEROther movement types

API Endpoint

GET /api/v1/organizations/{organizationId}/merchants/{merchantId}/accounts/{accountId}/reports/ledger-statement

Path Parameters

ParameterRequiredDescription
organizationIdYesOrganization ID
merchantIdYesMerchant ID
accountIdYesVirtual account ID

Query Parameters

ParameterRequiredDefaultDescription
fromYes-Start date (YYYY-MM-DD)
toYes-End date (YYYY-MM-DD)
granularityNodailyDate cutoff: daily or monthly
cursorNo-Pagination cursor from previous response
limitNo50Items per page (1-100)

Quick Example

1const response = await axios.get(
2 `https://api.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/accounts/${accountId}/reports/ledger-statement`,
3 {
4 params: {
5 from: '2025-01-01',
6 to: '2025-01-31'
7 },
8 headers: { 'Authorization': `Bearer ${token}` }
9 }
10);
11
12console.log('Opening Balance:', response.data.openingBalance);
13console.log('Closing Balance:', response.data.closingBalance);
14console.log('Movements:', response.data.movements.length);

Understanding the Response

1{
2 "accountId": "acc_0031c537-2301-40ab-9153-0f7c48505350",
3 "currency": "CLP",
4 "merchantId": "mrc_2e8f96ab-dbd5-45f9-b4b6-645945daf340",
5 "periodStart": "2025-01-01T00:00:00.000Z",
6 "periodEnd": "2025-01-31T23:59:59.999Z",
7 "openingBalance": "4000000.00",
8 "closingBalance": "5500000.00",
9 "movements": [
10 {
11 "ledgerEntryId": "137",
12 "postedAt": "2025-01-15T10:30:00.000Z",
13 "type": "credit",
14 "amount": "1000000.00",
15 "currency": "CLP",
16 "runningBalance": "5000000.00",
17 "description": "PAYIN from Juan Pérez - Bank transfer received",
18 "orderId": "ord_abc123",
19 "category": "PAYIN"
20 },
21 {
22 "ledgerEntryId": "142",
23 "postedAt": "2025-01-20T14:15:00.000Z",
24 "type": "debit",
25 "amount": "500000.00",
26 "currency": "CLP",
27 "runningBalance": "4500000.00",
28 "description": "PAYOUT to Supplier Co - Invoice payment",
29 "orderId": "ord_def456",
30 "category": "PAYOUT"
31 }
32 ],
33 "pagination": {
34 "cursor": "eyJpZCI6IjE0MiIsImRhdGUiOiIyMDI1LTAxLTIwVDE0OjE1OjAwLjAwMFoifQ==",
35 "hasMore": true,
36 "limit": 50
37 },
38 "generatedAt": "2025-01-31T12:00:00.000Z"
39}

Response Fields

FieldDescription
openingBalanceBalance at period start
closingBalanceBalance at period end
movements[]Array of individual movements
movements[].ledgerEntryIdUnique ID (use for detailed receipt)
movements[].runningBalanceBalance after this movement
movements[].typecredit or debit
movements[].categoryMovement category (PAYIN, PAYOUT, etc.)
pagination.cursorUse for next page request
pagination.hasMoreWhether more pages exist

Pagination

The ledger statement uses cursor-based pagination. To retrieve all movements:

1async function getAllMovements(orgId, merchantId, accountId, from, to, token) {
2 const allMovements = [];
3 let cursor = null;
4
5 do {
6 const response = await axios.get(
7 `https://api.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/accounts/${accountId}/reports/ledger-statement`,
8 {
9 params: {
10 from,
11 to,
12 limit: 100,
13 ...(cursor && { cursor })
14 },
15 headers: { 'Authorization': `Bearer ${token}` }
16 }
17 );
18
19 const data = response.data;
20 allMovements.push(...data.movements);
21
22 cursor = data.pagination.hasMore ? data.pagination.cursor : null;
23
24 console.log(`Fetched ${data.movements.length} movements, total: ${allMovements.length}`);
25
26 } while (cursor);
27
28 return allMovements;
29}
30
31// Usage
32const movements = await getAllMovements(orgId, merchantId, accountId, '2025-01-01', '2025-01-31', token);
33console.log('Total movements:', movements.length);

Granularity Options

The granularity parameter controls how date boundaries are applied:

GranularityPeriod StartPeriod End
dailyStart of day (00:00:00)End of day (23:59:59)
monthlyFirst day of monthLast day of month

Use monthly granularity for month-end reconciliation reports to ensure consistent period boundaries.


Complete Integration Example

Node.js
1async function generateMonthlyStatement(orgId, merchantId, accountId, year, month, token) {
2 // Calculate date range for the month
3 const from = `${year}-${String(month).padStart(2, '0')}-01`;
4 const lastDay = new Date(year, month, 0).getDate();
5 const to = `${year}-${String(month).padStart(2, '0')}-${lastDay}`;
6
7 console.log(`Generating statement for ${from} to ${to}`);
8
9 // Fetch first page
10 const response = await axios.get(
11 `https://api.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/accounts/${accountId}/reports/ledger-statement`,
12 {
13 params: {
14 from,
15 to,
16 granularity: 'monthly',
17 limit: 100
18 },
19 headers: { 'Authorization': `Bearer ${token}` }
20 }
21 );
22
23 const statement = response.data;
24
25 // Summary
26 console.log('='.repeat(50));
27 console.log('MONTHLY STATEMENT');
28 console.log('='.repeat(50));
29 console.log(`Account: ${statement.accountId}`);
30 console.log(`Currency: ${statement.currency}`);
31 console.log(`Period: ${statement.periodStart} to ${statement.periodEnd}`);
32 console.log('-'.repeat(50));
33 console.log(`Opening Balance: ${statement.openingBalance}`);
34 console.log(`Closing Balance: ${statement.closingBalance}`);
35 console.log('-'.repeat(50));
36
37 // Calculate totals
38 let totalCredits = 0;
39 let totalDebits = 0;
40
41 statement.movements.forEach(m => {
42 const amount = parseFloat(m.amount);
43 if (m.type === 'credit') {
44 totalCredits += amount;
45 } else {
46 totalDebits += amount;
47 }
48
49 console.log(`${m.postedAt} | ${m.type.toUpperCase().padEnd(6)} | ${m.amount.padStart(15)} | ${m.category} | ${m.description.substring(0, 30)}`);
50 });
51
52 console.log('-'.repeat(50));
53 console.log(`Total Credits: ${totalCredits.toFixed(2)}`);
54 console.log(`Total Debits: ${totalDebits.toFixed(2)}`);
55 console.log(`Net Change: ${(totalCredits - totalDebits).toFixed(2)}`);
56 console.log('='.repeat(50));
57
58 return statement;
59}
60
61// Usage: Generate January 2025 statement
62const statement = await generateMonthlyStatement(
63 'org3_xxx',
64 'mrc_xxx',
65 'acc_xxx',
66 2025,
67 1,
68 token
69);

Next Steps