Reporting API
All endpoints require authentication and reporting tool permissions. A global reporting:view permission check is applied to all routes.
Data is cached in the report_snapshots table with a 4-hour TTL for the current month and 24-hour TTL for past months.
Monthly Summary
Section titled “Monthly Summary”GET /api/reporting/summaryQuery parameters:
| Param | Type | Description |
|---|---|---|
period | string | this_month (default) or last_month |
Response:
{ "period": { "label": "April 2026", "from": "2026-04-01", "to": "2026-04-30" }, "fetched_at": "2026-04-17T00:00:00Z", "financial": { "revenue": 0, "expenses": 0, "profit": 0, "profit_ratio": null, "company_tax": null, "actual_profit": null, "dividends": null, "individual_dividend": null }, "utilisation": { "billable_hours": 0, "billable_percent": 0, "worked_hours": 0, "worked_percent": 0 }, "derived": { "effective_rate": 0, "revenue_per_head": 0, "gross_margin_percent": 0, "budget_burn_percent": null, "headcount": 0 }}- Financial data is sourced from Xero Profit & Loss reports.
- Utilisation data is sourced from Productive time reports.
- Derived metrics include effective hourly rate, revenue per head, gross margin, and budget burn percentage (from Productive budget reports).
Breakdown by Squad or Team
Section titled “Breakdown by Squad or Team”GET /api/reporting/breakdownQuery parameters:
| Param | Type | Description |
|---|---|---|
period | string | this_month (default) or last_month |
view | string | squad (default) or team |
Squad view
Section titled “Squad view”Returns time and revenue aggregated by squad (from the people table).
Response:
{ "rows": [ { "squad": "string", "total_hours": 0, "billable_hours": 0, "billable_percent": 0, "revenue": 0, "headcount": 0 } ], "totals": { "total_hours": 0, "billable_hours": 0, "revenue": 0, "headcount": 0, "billable_percent": 0 }}Team view
Section titled “Team view”Returns time and revenue per individual person.
Response:
{ "rows": [ { "person_id": "string", "name": "string", "squad": "string", "total_hours": 0, "billable_hours": 0, "revenue": 0 } ], "totals": { "total_hours": 0, "billable_hours": 0, "revenue": 0 }}12-Month Trends
Section titled “12-Month Trends”GET /api/reporting/trendsReturns financial and utilisation data for the last 12 months. Fetches from cache where available and backfills from Xero/Productive for uncached months.
Response:
{ "months": [ { "month": "2026-04", "label": "Apr 26", "revenue": 0, "profit": 0, "worked_hours": 0, "billable_hours": 0 } ]}Force Cache Refresh
Section titled “Force Cache Refresh”POST /api/reporting/refreshPermission: reporting:manage
Query parameters:
| Param | Type | Description |
|---|---|---|
period | string | this_month (default) or last_month |
Deletes all cached report snapshots for the given period, forcing a fresh fetch on the next request.
Response: { ok: true, cleared: "2026-04" }