Skip to content

Reports

Should you build statements on the reports endpoints? No — build them on ledger data, then use the reports endpoints to prove you got the same answer. Report output is presentation, with no stable row identity to drill from.

Concepts
7
Divergences
2
Structural
1

Endpoints

Xero

GET /Reports/{ProfitAndLoss,BalanceSheet,TrialBalance,AgedReceivablesByContact,…}

Row-typed output

QBO

GET /reports/{ProfitAndLoss,BalanceSheet,GeneralLedger,TrialBalance,…}

Nested Rows and Columns

NetSuite

Saved searches, SuiteQL, SuiteAnalytics

You define the query

The same concept in three vocabularies

Read across a row. Where a cell is absent, the concept has nowhere to live in that system — which is a design constraint, not a gap to paper over.

NativeNative
— A field or object exists for it
PartialPartial
— Exists with a caveat that changes what you can build
DerivedDerived
— Not stored — you compute it
AbsentAbsent
— Nothing in the API
Reports fields compared across Xero, QuickBooks Online and NetSuite
ConceptXeroQBONetSuite
Output shape
Partial

Rows[] with RowType

Header, Section, Row, SummaryRow

Partial

Nested Rows / Columns with ColData[]

Native

Whatever your query returns

Stable row identityWhether a report row can be drilled into.
Partial

Some cells carry AccountID

Partial

ColData.id, sometimes

Native

You control it

Comparative periods
Native

periods, timeframe parameters

Native

date_macro, summarize_column_by

Native

Query it

Cash vs accrual basis
Partial

paymentsOnly parameter on some reports

Native

accounting_method parameter

Native

Report or query setting

Dimensional breakdown
Partial

trackingCategoryID parameters

Two at most

Partial

Class and Department columns

Native

Any segment

Aging
Partial

AgedReceivablesByContact / AgedPayablesByContact

One contact at a time

Native

AgedReceivables, AgedPayables

Native

Saved search or SuiteQL

Drill-down to transactions
Absent

—

Absent

—

Native

Native

What actually differs, and what you do about it

A difference stated without a response is trivia. Each of these carries the handling.

Structural

Report rows are presentation, not data

Xero · QBO · NetSuite

Header, section and summary rows are interleaved with data rows, nesting depth carries meaning, and identity is inconsistent. Parsing is possible; drilling from a parsed row back to the transactions behind it generally is not.

Do — Build interactive statements from ledger data. Use the report endpoint on a schedule, assert equality, and surface a variance when it appears.

Sharp edge

Xero's aged reports are per contact

Xero

AgedReceivablesByContact takes one contact. Building a full aging report means one call per customer, against a 60-call-per-minute limit — a hundred customers is close to two full minutes of the organisation's budget, and a tenth of a Starter-tier app's 1,000-call day.

Do — Compute aging ourselves from invoices plus unapplied cash, and tie to the ledger's own report for a sample of contacts rather than all of them.

What this changes in the interface

The payoff. Every difference above that reaches the surface, and what the component does about it.

Our number and their number can differ
Statement components carry a tie-out line: the ledger's total, ours, and the variance. Zero variance is shown, not hidden — its presence is the assurance.
Report rows cannot be drilled
Drill-down is offered from ledger-derived statements only. Where a figure came from a report endpoint, the row is not clickable and says why.

Where the accounting is explained

What consumes this payload

The normalised object above is what the components read. These are the surfaces it feeds, the words for the concepts in it, and the document that explains the accounting.

Compared acrossXero docsQuickBooks Online docsNetSuite docs

Works in your QuickBooks Online or Xero file

Get your books closed every month

Lopifi is building AI bookkeeping that keeps your books closed on US GAAP as transactions happen, with a CPA review every quarter, for $200 a month. We’re not open yet.

$200/month when we open. No card now.