Skip to content

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.

GET /api/reporting/summary

Query parameters:

ParamTypeDescription
periodstringthis_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).
GET /api/reporting/breakdown

Query parameters:

ParamTypeDescription
periodstringthis_month (default) or last_month
viewstringsquad (default) or team

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
}
}

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
}
}
GET /api/reporting/trends

Returns 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
}
]
}
POST /api/reporting/refresh

Permission: reporting:manage

Query parameters:

ParamTypeDescription
periodstringthis_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" }