Reports API
Scope and trust model
All report endpoints are JWT-protected and workspace-scoped.
A user must have access to the requested workspace. Passing an arbitrary workspaceId is not sufficient without matching membership.
Moonlight reports are still evolving toward finance-grade semantics. The current contract is safer than before, but some report families should still be treated differently:
- Safer operational reports:
summary,by-party,by-tag,calendar - Exploratory visual feeds: some chart combinations on the frontend still remain exploratory
Summary
GET /reports/summary?workspaceId={id}&from=2026-01-01&to=2026-01-31
Response:
{
"totalIncomeCents": 150000,
"totalExpenseCents": 90000,
"netCents": 60000,
"entryCount": 18,
"avgTransactionCents": 13333,
"currency": "MIXED",
"currencies": ["EUR", "USD"],
"isCurrencyMixed": true,
"currencyBreakdown": [
{
"currency": "EUR",
"incomeCents": 50000,
"expenseCents": 20000,
"netCents": 30000,
"entryCount": 6
},
{
"currency": "USD",
"incomeCents": 100000,
"expenseCents": 70000,
"netCents": 30000,
"entryCount": 12
}
]
}
Notes
currencyisMIXEDwhen more than one base currency exists in the selected range.currencyBreakdownis the safer control-oriented field for mixed-currency workspaces.- Aggregate totals across mixed currencies are still operational signals, not normalized FX-based financial truth.
Timeline
GET /reports/timeline?workspaceId={id}&groupBy=month&from=2026-01-01&to=2026-12-31
Response:
[
{
"period": "2026-01",
"incomeCents": 100000,
"expenseCents": 40000,
"netCents": 60000,
"entryCount": 8
}
]
Notes
groupBycan beday,week, ormonth.- Timeline remains an aggregate feed. Use entry list drill-down filters for source inspection.
By Party
GET /reports/by-party?workspaceId={id}&from=2026-01-01&to=2026-12-31
Response:
[
{
"partyId": 5,
"partyName": "Acme Corp",
"partyType": "VENDOR",
"country": "US",
"totalIncomeCents": 0,
"totalExpenseCents": 320000,
"entryCount": 14
}
]
Notes
- Party aggregation now uses the directionally relevant counterparty:
- for
INCOME, the counterparty isfromParty - for
EXPENSE, the counterparty istoParty
- for
- This avoids counting the same entry against both sides of the transfer.
By Tag
GET /reports/by-tag?workspaceId={id}&from=2026-01-01&to=2026-12-31
Response:
[
{
"tagId": 8,
"tagName": "Operations",
"totalCents": 120000,
"incomeCents": 20000,
"expenseCents": 100000,
"entryCount": 11
}
]
Notes
- Multi-tagged entries are now allocated proportionally across their tags to reduce double counting.
entryCountis still the number of distinct entries associated with the tag.
By Project
GET /reports/by-project?workspaceId={id}&from=2026-01-01&to=2026-12-31
Response includes totalIncomeCents, totalExpenseCents, netCents, and entryCount per project.
Flows
GET /reports/flows?workspaceId={id}&from=2026-01-01&to=2026-12-31
Returns directional flow links between fromParty and toParty.
Calendar
GET /reports/calendar?workspaceId={id}&year=2026
Response:
[
{
"date": "2026-03-15",
"totalCents": -25000,
"entryCount": 3
}
]
Notes
totalCentsis now a signed net daily amount:- income contributes positive values
- expense contributes negative values
- This makes the calendar heatmap less misleading as a control surface.
Distribution
GET /reports/distribution?workspaceId={id}&from=2026-01-01&to=2026-12-31
Returns min/max/median/quartiles/mean and raw values grouped by entryType.
Operational guidance
Use reports in this order of trust:
summary.currencyBreakdownby-projectby-partyby-tag- visual chart compositions on the frontend
The frontend still marks some chart-based views as exploratory until the full reporting remediation is complete.