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.

ParamNotes
comparesingle (default), fy_months, fy_quarters, trailing_months, trailing_quarters, years
date_from, date_toRequired when compare=single. Inclusive
anchorYYYY-MM-DD. Columns are built backwards from here. Defaults to the latest posted date
countColumn 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

CodeWhen
VALIDATION_ERRORUnknown compare mode, missing dates on single, count outside 1–60, malformed anchor

An unknown mode lists the allowed ones in details.allowed.