Payments Engine

Reports and exports

Pre-built projections for the numbers accountants ask for, and a streaming CSV export for the ones they ask for in bulk. Both tell you how current they are, because a stale report is not wrong — it is old, and only one of those is safe to act on.

Reports

TypeRows
DEPOSITSCredited deposits by period and asset — count, total amount.
PAYOUTSSettled payouts by period and asset — count, total amount, total fee.
TREASURYPositions by tier over time.
FEESChain fees paid, by period and asset. The number to watch when sweep policy changes.
SUMMARYAll of the above, one row per period.
node
const report = await payments.reports.get('PAYOUTS', 'USDT');

report.builtThrough;   // "2026-08-29T20:41:00Z" — or null if never built
report.rows;           // [{ periodBucket, assetCode, count, totalAmountMinor, … }]

Staleness is part of the answer

Always read builtThrough. Projections are built by a worker loop from a cursor. If that loop stopped an hour ago, the numbers are an hour old and look exactly as authoritative as fresh ones. The field is returned on every report so a dashboard can show its own age — the operator console does, and a report older than its expected interval is worth an alert.

A null here means the projection has never been built. That is different from "there was nothing to report", and the distinction matters the first time somebody concludes from an empty report that no payouts happened.

Exports

node
// Creation is idempotent on the job ID: a retried request does not
// start a second export of the same range.
const job = await payments.exports.create({
  jobId: 'exp_2026_08',
  reportType: 'PAYOUTS',
  assetCode: 'USDT',
  fromTime: '2026-08-01T00:00:00Z',
  toTime: '2026-09-01T00:00:00Z'
});

const status = await payments.exports.get('exp_2026_08');
status.status;      // PENDING | RUNNING | COMPLETE | FAILED
status.rowCount;    // "184203" — a string

// Download is a signed GET that streams.
// GET /v1/exports/exp_2026_08/download

Why the download streams

The CSV is written to the socket row by row and never assembled in memory. A year of payouts for a busy operator is hundreds of megabytes; buffering that would mean an engine whose memory use is set by its largest customer's largest export.

A stream that fails mid-transfer destroys the connection rather than ending it cleanly. That is deliberate. A truncated CSV that arrives with a clean end looks complete, and a partial export loaded into an accounting system is worse than a failed one — the failure is visible, and the truncation is not.