Skip to content
On this page

Developers › After the payment

Reports and exports

Summarise a period, check it against the ledger, and download the rows behind it.

Verified 6 November 2026Explain this to your team →

View as Markdown

The balance summary

One call gives you a period: what you opened with, everything that moved, what you closed with — and the same closing figures recomputed independently from the ledger, so you can see that the two agree.

ts
const month = await sp.reports.balanceSummary({ start: "2026-09-01", end: "2026-10-01" });

if (!month.reconciled) {
  // Your statement and your balance disagree. Do not reconcile around it — get in touch.
  throw new Error("balance summary did not reconcile");
}

month.starting_balance;   // { available: 0,      pending: 0 }
month.activity.charges;   // { count: 2, gross: 20000, fees: 1000, net: 19000 }
month.activity.refunds;   // { count: 1, amount: -2000, fees_returned: -100 }
month.activity.payouts;   // { count: 1, amount: -17100 }
month.ending_balance;     // { available: 0,      pending: 0 }
month.ledger;             // { available: 0,      pending: 0 }  — recomputed from the entries
reconciled: false is an incident, not a display quirk. ending_balance is derived from the period’s movements on top of the opening balance. ledger is the same two figures recomputed from the ledger’s own entries at the moment of your request, by the same function a real payout is cut against. Two paths, no shared query. If they disagree, your statement and your balance are telling you different things, and that is a support call rather than a number to work around.

Every figure is an integer in minor units of your settlement currency, and it is signed the way the ledger signs it: a charge is positive, a refund and a payout are negative, and fees_returned is negative because it is a fee coming back to you. That is what makes the lines add up — they total exactly ending_balance − starting_balance, with nothing to reinterpret.

FieldWhat it is
starting_balanceavailable and pending, at the instant the period opened.
activity.chargescount, gross, fees, net. `net` is what reached your balance.
activity.refundscount, amount (negative), fees_returned (negative).
activity.payoutscount and amount (negative): money that left for your bank.
activity.payout_reversalsA payout cancelled or returned. Positive.
activity.application_feescount and amount: fees a platform took on payments through your account, negative, or that a platform received, positive. Zero unless you are on Connect.
activity.adjustmentsA correction, with a reason recorded against it.
activity.disputescount, amount, fees. amount is the chargebacks as one signed figure: the amounts held, plus the ones given back when you won. Negative means money is held or gone; zero means it all came back. fees is our handling fee on a lost chargeback, positive, and is not in amount — amount − fees is what disputes moved.
ending_balancestarting_balance plus the period's movements.
ledgerThe same two figures recomputed from ledger.account_balance() right now.
reconciledWhether the two agreed. See above.

Reconciling against your own books

The check that matters is one line of arithmetic, and it holds for any period:

ts
const opened = month.starting_balance.available + month.starting_balance.pending;
const closed = month.ending_balance.available + month.ending_balance.pending;

const moved =
  month.activity.charges.net +
  (month.activity.refunds.amount - month.activity.refunds.fees_returned) +
  month.activity.payouts.amount +
  month.activity.payout_reversals.amount +
  month.activity.application_fees.amount +
  month.activity.adjustments.amount +
  (month.activity.disputes.amount - month.activity.disputes.fees);

closed - opened === moved;   // always true, and `reconciled` is us asserting it too

Twelve monthly summaries tile a year exactly, because start is inclusive and end is exclusive. Ask for the 1st to the 1st and you get a whole month; the payment taken at the stroke of midnight belongs to exactly one of two adjacent reports, never both and never neither.

When your period ends in the past, ending_balance and ledger will differ by everything that has happened since. That is expected and is not a disagreement — reconciled accounts for it. When the period runs to now, the two are identical.

Dates

start and end take a unix timestamp in seconds, or an ISO date such as 2026-09-01, which is read as midnight in Belize — 06:00 UTC, because Belize is UTC−6 all year, so an evening payment belongs to that day, not the next. A unix timestamp or a full ISO instant is taken exactly as given. Left out, they default to the current Belize calendar month up to now.

A period may cover at most 366 days, and so may an export. For a longer span, ask one window at a time — page by date the way you page a list by cursor. 366 rather than 365 because “the last year” across a leap year is 366 days long.

CSV exports

Four downloads, each covering the same date window and each a CSV streamed as it is read out of the database rather than assembled in memory first. They are the rows behind the summary.

ExportFilters beyond the dates
payments.csvstatus
refunds.csvstatus
payouts.csvstatus
balance_transactions.csvtype, payout

payments.csv also takes customer, payment_link and invoice. balance_transactions.csv with a payout gives exactly what that payout paid for.

bash
curl -H "Authorization: Bearer sk_test_..." \
     -H "Accept: text/csv" \
     "https://api.secureprocessing.app/v1/reports/exports/payments.csv?start=2026-09-01&end=2026-10-01" \
     -o payments-september.csv

curl -H "Authorization: Bearer sk_test_..." \
     -H "Accept: text/csv" \
     "https://api.secureprocessing.app/v1/reports/exports/balance_transactions.csv?start=2026-09-01&end=2026-10-01" \
     -o transactions-september.csv

From the SDK, you are handed the response so you can pipe it straight to a file:

ts
import { createWriteStream } from "node:fs";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";

const response = await sp.reports.export("payments", { start: "2026-09-01", end: "2026-10-01" });
await pipeline(Readable.fromWeb(response.body!), createWriteStream("payments-september.csv"));

What a row looks like

Every money column appears twice. amount_minor is the integer this API speaks in, and amount beside it is the same figure formatted for a person. A file with only the formatted column is a lossy copy of your ledger; a file with only minor units makes whoever opens it divide by a hundred four hundred times.

text
id,created,type,source,status,currency,amount_minor,amount,fee_minor,fee,net_minor,net,available_on
btxn_9f4c1e2a...,2026-09-14T15:02:11.884Z,charge,pi_3KqL8x...,available,bzd,12000,120.00,600,6.00,11400,114.00,2026-09-16T15:02:11.884Z
btxn_1a7b3c9d...,2026-09-19T09:41:03.210Z,refund,re_7mN4pQ...,available,bzd,-2000,-20.00,-100,-1.00,-1900,-19.00,2026-09-19T09:41:03.210Z
ConventionDetail
MoneyTwice per figure: an integer in minor units, and a plain decimal with no symbol and no thousands separator.
DatesISO 8601 in UTC, so the file means the same thing on every machine that opens it.
IdsExactly as the API returns them.
metadataOne cell, holding your metadata as a JSON string.
EscapingRFC 4180. A field containing a comma, a quote or a newline is quoted and its quotes doubled.
OrderNewest first, the same order and the same filters as the matching list endpoint.

A note on spreadsheets

Text fields in these files can contain anything your customers typed. A cell beginning =, +, - or @ is treated as a formula by Excel, Numbers and Google Sheets — so a description of =HYPERLINK("https://example.com","Click") would become a live link in your own spreadsheet. We prefix any such field with an apostrophe, which every spreadsheet reads as “this is text” and strips on display.

The money columns are not prefixed, deliberately. A negative amount like -19.00 also begins with a minus sign, and prefixing it would turn every refund and payout in the file into text — where SUM() silently returns zero. Those columns stay numbers so they add up. If you parse these files with a script, strip a single leading ' from text fields.

A payout’s reconciliation

Separately from the four exports, every payout has a report of its own: the movements it paid for, ending in a total, with the payout’s own amount printed in the header. The two are derived independently, so if they ever disagreed the document would say so rather than quietly agreeing with itself.

bash
curl -H "Authorization: Bearer sk_test_..." \
     -H "Accept: text/csv" \
     "https://api.secureprocessing.app/v1/reports/payout_reconciliation/po_7Kq2LxR9sT4vW8yZ1aB3cD5e" \
     -o payout.csv

It is also the “Download CSV” button on a payout’s page in the dashboard. The email you get when a payout is paid summarises the same figures and links to that page.