Reports
A report is a view over posted transactions — the roll-up, the sign convention and the posted-only rule applied for you.
Read-only. A report is derived from accounts and transactions, so there is nothing here that could be written.
Why this exists
Without it, answering "how did last quarter go" means fetching raw entries and re-implementing the statement: rolling child accounts into their parents, flipping the sign per account type, excluding drafts and voids. Every caller would reimplement it, and any of them could get it subtly wrong — producing a figure that looks plausible and disagrees with what the app shows.
The period model is shared with the UI, so compare=fy_months means the same
twelve columns here as it does on screen.
GET /api/v1/reports/profit-loss
Income and expenses over one period, or across several at once.
| Param | Notes |
|---|---|
compare | single (default), fy_months, fy_quarters, trailing_months, trailing_quarters, years |
date_from, date_to | Required when compare=single. Inclusive |
anchor | YYYY-MM-DD. Columns are built backwards from here. Defaults to the latest posted date |
count | Column count for the rolling modes. 1–60 |
dimension.<key> | e.g. dimension.cost_centre=Mumbai |
Only posted transactions are counted. Drafts are allowed to be unbalanced and
voids are cancelled, so including either would produce a statement that does not
agree with itself.
Periods
The financial year is April–March. Quarters follow it, so Q1 is Apr–Jun.
anchor defaults to the latest posted transaction date, not today. On a
ledger that mirrors an upstream system which has stopped being fed, anchoring on
today returns a screen of empty columns; anchoring on the data returns the most
recent periods that have anything in them.
single has no such default: it requires explicit dates. A report that guessed
its period would return a figure the caller did not ask for and has no way to
notice.
Response
compare=single returns one amount per account. Every other mode returns an
amounts map keyed by period, plus the periods array describing the columns.
{
"data": {
"periods": [
{ "key": "2024", "label": "FY 24-25", "date_from": "2024-04-01", "date_to": "2025-03-31" }
],
"income": [
{
"id": "T0ZPgsZrPqRD",
"name": "Income",
"type": "income",
"is_group": true,
"own_amounts": { "2024": 0 },
"amounts": { "2024": 92311795 },
"children": []
}
],
"expenses": [],
"total_income": { "2024": 92311795 },
"total_expenses": { "2024": 0 },
"net_profit": { "2024": 92311795 }
}
}
Amounts are integers in minor units — 92311795 is ₹9,23,117.95.
A group's amounts already include its descendants, while own_amounts is
what was posted to the group account itself. Summing a group and its children
counts the same money twice.
Accounts with no activity in any period are omitted. Every org carries the full seeded chart whether or not it uses all of it, so including them would bury the real figures.
Errors
| Code | When |
|---|---|
VALIDATION_ERROR | Unknown compare mode, missing dates on single, count outside 1–60, malformed anchor |
An unknown mode lists the allowed ones in details.allowed.