Ledger
The ledger is Plugipay's double-entry accounting record of every money movement in your workspace: charges, refunds, platform fees, taxes, payouts. Every event posts paired debit / credit entries to typed account codes (cash, revenue, revenue_refund, platform_fee, etc.); the ledger is the source of truth your payouts.balance() figure is derived from. This page covers the plugipay.ledger namespace; for the underlying HTTP surface see API: Ledger, and for the account-code taxonomy see Concepts → Ledger.
Namespace
plugipay.ledger — every method on this namespace:
plugipay.ledger.list(params?)
plugipay.ledger.balances()
The ledger is read-only via the SDK. You can't post arbitrary entries — Plugipay derives them from charges, refunds, and payouts. To export the full ledger as CSV for accounting hand-off, use the low-level client.request against the /api/v1/reports/ledger.csv endpoint (see Reports for why it's not auto-exposed).
Methods
ledger.list
Signature. plugipay.ledger.list(params?): Promise<{ data: LedgerEntry[]; cursor: string | null; hasMore: boolean }>
Cursor-paginated list of ledger entries. Filters:
code— account code, e.g.'cash','revenue','platform_fee','revenue_refund'txId— group of entries that were posted in the same atomic transactionsourceType—'checkout_session','invoice','refund','payout'sourceId— specific source objectorder—'asc'(oldest first) or'desc'(newest first, default)limit,cursor— see Pagination
import { PlugipayClient } from '@forjio/plugipay-node';
const plugipay = new PlugipayClient({
keyId: process.env.PLUGIPAY_KEY_ID!,
secret: process.env.PLUGIPAY_SECRET!,
});
// All entries from one checkout session's settlement transaction:
const { data } = await plugipay.ledger.list({
sourceType: 'checkout_session',
sourceId: 'cs_01HX...',
});
for (const entry of data) {
console.log(`${entry.postedAt} ${entry.code.padEnd(20)} ${entry.direction} ${entry.amount}`);
}
ledger.balances
Signature. plugipay.ledger.balances(): Promise<LedgerBalance[]>
Returns the running balance for every account code: total debits, total credits, and the net balance. This is the "trial balance" view.
const balances = await plugipay.ledger.balances();
for (const b of balances) {
console.log(`${b.code}: ${b.balance} (D ${b.debits} / C ${b.credits})`);
}
In a well-formed double-entry system, the sum of all balances should be zero — every debit has a matching credit.
Types
interface LedgerEntry {
id: string; // 'le_...'
accountId: string;
txId: string; // 'tx_...' — groups paired entries
code: string; // account code, e.g. 'cash'
direction: 'debit' | 'credit';
amount: number; // always positive; direction tells you the sign
currency: string;
sourceType: string; // 'checkout_session' / 'invoice' / 'refund' / 'payout'
sourceId: string;
memo: string | null;
postedAt: string; // ISO-8601
}
interface LedgerBalance {
code: string;
debits: number;
credits: number;
balance: number; // debits - credits (or vice versa depending on account nature)
}
For the full code taxonomy (which codes exist, which are debit-natured vs credit-natured), see API: Ledger.
Common patterns
Inspect a single transaction's entries
When debugging "where did this money go?", group by txId:
async function entriesByTx(txId: string) {
const { data } = await plugipay.ledger.list({ txId, order: 'asc', limit: 100 });
return data;
}
// Pretty-print a transaction:
const entries = await entriesByTx('tx_01HX...');
console.table(entries.map((e) => ({
code: e.code,
direction: e.direction,
amount: e.amount,
source: `${e.sourceType}/${e.sourceId}`,
})));
Cross-check payouts.balance() against the ledger
Your available balance should equal cash balance minus payout_in_transit (locked):
const balances = await plugipay.ledger.balances();
const cash = balances.find((b) => b.code === 'cash')?.balance ?? 0;
const locked = balances.find((b) => b.code === 'payout_in_transit')?.balance ?? 0;
const apiBalance = await plugipay.payouts.balance();
console.log('Ledger says:', cash - locked);
console.log('API says: ', apiBalance.available);
If these diverge significantly, something has gone wrong — reach out to support with the figures.
Reconcile revenue to your accounting system
Pull every revenue credit since your last reconciliation cursor:
async function* newRevenueEntries(sinceIso: string) {
let cursor: string | undefined;
while (true) {
const page = await plugipay.ledger.list({
code: 'revenue',
order: 'asc',
limit: 100,
cursor,
});
for (const e of page.data) {
if (e.postedAt > sinceIso) yield e;
}
if (!page.hasMore) break;
cursor = page.cursor!;
}
}
for await (const e of newRevenueEntries('2026-11-01T00:00:00Z')) {
await pushToAccounting(e);
}
Trace refund impact
A refund posts a revenue_refund debit and a cash credit. To see the full impact of one refund:
async function refundImpact(refundId: string) {
const { data } = await plugipay.ledger.list({
sourceType: 'refund',
sourceId: refundId,
});
return data.reduce(
(acc, e) => {
acc[e.code] = (acc[e.code] ?? 0) + (e.direction === 'credit' ? e.amount : -e.amount);
return acc;
},
{} as Record<string, number>,
);
}
Period P&L from raw entries
For a custom P&L outside what reports.pnl gives:
async function pnl(from: string, to: string) {
const totals = { revenue: 0, refunds: 0, platformFees: 0, tax: 0 };
let cursor: string | undefined;
while (true) {
const page = await plugipay.ledger.list({ order: 'asc', limit: 100, cursor });
for (const e of page.data) {
if (e.postedAt < from || e.postedAt >= to) continue;
const signed = e.direction === 'credit' ? e.amount : -e.amount;
if (e.code === 'revenue') totals.revenue += signed;
if (e.code === 'revenue_refund') totals.refunds -= signed;
if (e.code === 'platform_fee') totals.platformFees -= signed;
if (e.code.startsWith('tax_')) totals.tax -= signed;
}
if (!page.hasMore) break;
cursor = page.cursor!;
}
return totals;
}
For most workspaces the canned client.reports.pnl({ from, to }) call is faster — it runs the same aggregation server-side.
Errors
| Code | Status | Cause |
|---|---|---|
validation_error |
400 | Unknown code, bad sourceType, malformed txId. |
forbidden |
403 | Key lacks plugipay:ledger:read scope. |
The ledger is read-only; there's no class of state_conflict you can hit here.
Next
- Payouts — the balance figure derived from
cash. - Refunds — the source of
revenue_refundentries. - Events — high-level event log (vs the ledger's accounting log).
- API: Ledger — HTTP reference and full account-code taxonomy.