Skip to main content

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

  • currency is MIXED when more than one base currency exists in the selected range.
  • currencyBreakdown is 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

  • groupBy can be day, week, or month.
  • 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 is fromParty
    • for EXPENSE, the counterparty is toParty
  • 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.
  • entryCount is 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

  • totalCents is 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:

  1. summary.currencyBreakdown
  2. by-project
  3. by-party
  4. by-tag
  5. visual chart compositions on the frontend

The frontend still marks some chart-based views as exploratory until the full reporting remediation is complete.