---
id: docs.reports
title: "Reports and exports"
canonical: https://docs.secureprocessing.app/reports
last_verified: 2026-11-06
---

# Reports and exports

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

## 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
```

> **Warning:** **`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.

| Field | What it is |
|---|---|
| starting_balance | available and pending, at the instant the period opened. |
| activity.charges | count, gross, fees, net. \`net\` is what reached your balance. |
| activity.refunds | count, amount (negative), fees_returned (negative). |
| activity.payouts | count and amount (negative): money that left for your bank. |
| activity.payout_reversals | A payout cancelled or returned. Positive. |
| activity.application_fees | count 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.adjustments | A correction, with a reason recorded against it. |
| activity.disputes | count, 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_balance | starting_balance plus the period's movements. |
| ledger | The same two figures recomputed from ledger.account_balance() right now. |
| reconciled | Whether 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.

> **Note:** 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.

| Export | Filters beyond the dates |
|---|---|
| payments.csv | `status` |
| refunds.csv | `status` |
| payouts.csv | `status` |
| balance_transactions.csv | `type`, `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
```

| Convention | Detail |
|---|---|
| Money | Twice per figure: an integer in minor units, and a plain decimal with no symbol and no thousands separator. |
| Dates | ISO 8601 in UTC, so the file means the same thing on every machine that opens it. |
| Ids | Exactly as the API returns them. |
| metadata | One cell, holding your metadata as a JSON string. |
| Escaping | RFC 4180. A field containing a comma, a quote or a newline is quoted and its quotes doubled. |
| Order | Newest 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.

> **Note:** 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.
