User guide
Executive Dashboards and Reporting
Looking for what it does rather than how to use it? Read the Dashboards and reporting overview .
Last verified against the codebase: 2026-07-27. See How this document is verified.
What it is
Solidlio has two reporting surfaces. Executive dashboards are live aggregates you read on screen — tickets, SLA attainment, assets, escalations, receivables and revenue, refreshed on every page load. Reports are artifacts: you queue one, a background worker aggregates and renders it, and you download a PDF, CSV or Excel file that does not change afterwards.
The split is deliberate. A dashboard answers “how are we doing right now”. A report is a document you attach to a QBR pack, hand to a client, or file for an auditor — and it must still say the same thing in six months.
Concepts
| Concept | What it is |
|---|---|
| Executive stats | A live aggregate served by one request. Three variants exist: MSP (cross-client), organization (one tenant), platform (whole install). |
| Report | A row in the report library (rpt_ prefix) plus, once generated, a stored file. Carries its type, format, date range, client selection and the person who ran it. |
| Report type | Which aggregation runs. Ten values, mapping to six distinct aggregators plus a composable CUSTOM. |
| Report format | PDF, CSV or XLSX. Chosen at generate time; the artifact is rendered once in that format. |
| Report status | PENDING then GENERATING then COMPLETED or FAILED. The library polls while anything is in flight. |
| Reach | Every organization your staff work across — your own, plus every client account you primary-manage, plus every account you hold an active co-management link to. |
| Target | Your clients only, i.e. reach minus your own account. Client counts and client revenue use this. |
| Reporting period | today, 7d, 30d or 90d. Applies to the dashboards, not to report artifacts (those carry an explicit date range). |
Reach is not target
This distinction runs through the whole MSP dashboard and is the single easiest thing to get wrong.
- Open tickets, SLA attainment, assets, escalations are counted over reach. Your own service desk is part of your workload, and a client you co-manage under a collaborator link is too.
- Active clients, at-risk clients, client MRR, top clients by MRR are counted over target. You are not a client of yourself, and counting your own organization would inflate the client count and put you at the top of your own revenue table.
So the client count and the ticket count are populations of different sizes on purpose. They are not meant to reconcile.
Roles and permissions
The hierarchy is platform administrator > MSP administrator > MSP technician > organization administrator > power user > CUSTOMER`.
MSP technician outranks organization administrator in that order. Reporting therefore gates on explicit role sets, never on a rank floor — a floor of MSP technician rejects every organization administrator, which is exactly the defect that used to lock org tenants out of their own exports.
Report library
| platform administrator | MSP administrator | MSP technician | organization administrator | power user | CUSTOMER | |
|---|---|---|---|---|---|---|
| List, read, delete reports | ● | ● | ● | ● | — | — |
| Generate operational reports | ● | ● | ● | ● | — | — |
| Generate/download Revenue, Revenue Summary, Profitability | ● | ● | — | ● | — | — |
The financial set is narrower and different, not merely stricter: a technician is admitted to the library and refused margin; an org administrator is admitted to both, because they own their own organization’s invoices.
The financial check runs on generate and on download. Reports are account-wide, so a report an administrator ran is listed to every technician on the account — gating only the queue step would let a technician download the artifact somebody else asked for.
Executive dashboards
| platform administrator | MSP administrator | MSP technician | organization administrator | power user | CUSTOMER | |
|---|---|---|---|---|---|---|
| MSP cross-client dashboard (operational) | ● | ● | ● | — | — | — |
| MSP revenue block (MRR, growth, top clients) | ● | ● | — | — | — | — |
| Organization dashboard (operational) | ● | ● | ● | ● | ● | — |
| Organization receivables block | ● | ● | ● | ● | — | — |
| Platform dashboard | ● | — | — | — | — | — |
A withheld block comes back null with a restricted flag, never zeroed. The UI renders a dash and a reason. This matters: “$0 outstanding” is a statement about the business, ”—” is a statement about the reader, and the two must never be confused.
Navigation
The Executive and Reports sidebar entries are both gated on the canViewReports capability — true for every MSP role, and organization administrator or power user in the organization portal. Inside /org/reports, the Billing & Finance and Exports tabs are additionally hidden below organization administrator, matching the backend gates.
Walkthrough — the MSP executive dashboard
Path: MSP portal → Executive (/msp/executive)
- Choose a window. The selector in the header offers Today, Last 7 Days, Last 30 Days and Last 90 Days. It shifts the SLA, resolved-escalation and recent-escalation windows. Open counts are point-in-time and do not move.
- Read the four tiles. Client MRR (with month-over-month growth), Active Clients, Open Tickets, Assets Under Management.
- SLA Compliance shows response and resolution attainment as rings, plus the count of currently breached open tickets and the sample size the rates were computed over. With no SLA-tracked tickets resolved in the window, it says “No data” rather than showing a green 100%.
- Revenue Overview shows client MRR, the month-over-month change, and your top five clients by MRR.
- Escalation Trends lists active, pending and resolved escalations, a priority breakdown, and the five most recent escalation events with their ticket and organization.
- Client Health splits your clients into active, at-risk (suspended or inactive) and inactive, with an active-rate bar.
- Workload Metrics and Asset Health close the page: open, urgent, SLA-breached, unassigned, at-risk clients, assets with alerts, and warranty expiries inside 30 days.
If the plan does not include roll-up reporting, the page renders the upgrade offer the API returns — the required tier, its monthly price, and either a purchase link or “contact your account administrator”, depending on whether you can self-serve. It does not render an error.
If your role does not include financials, the revenue tile and card say “Requires an MSP administrator” and show a dash. You still get the whole operational dashboard.
Walkthrough — the organization executive dashboard
Path: Organization portal → Executive (/org/executive)
- Choose a window — same four options.
- Four tiles: Open Tickets, SLA Rate, Assets, Outstanding.
- SLA Compliance — response and resolution attainment over tickets resolved in the window that carry an SLA policy.
- Service Overview — open, in progress, urgent and SLA-breached counts, plus average resolution time. The average names the number of resolved tickets it was computed over; with nothing resolved it shows a dash, not “0 minutes”.
- Asset Health — total, active, with alerts, warranty expiring in 30 days.
- Financial Overview — unpaid and overdue invoice counts and amounts, an overdue rate, and total outstanding. Below organization administrator this card says the figures are administrator-only rather than showing zeros.
Walkthrough — generating and downloading a report
MSP path: MSP portal → Reports (/msp/reports) Organization path: Organization portal → Reports → Exports (/org/reports, Exports tab, organization administrator and above)
- Pick a report type from the catalogue cards and press Generate. Financial types are not shown at all to roles that cannot run them.
- Set the date range. It defaults to the last 30 days. Start must be on or before end.
- Narrow to specific clients (MSP portal only). Leave the list empty for every organization the account can see. The selection can only ever narrow that set — a client outside it is dropped at generation time rather than honoured.
- Choose a format — PDF, CSV or Excel.
- Choose whether to include charts (PDF only). Breakdown sections get a bar chart above the table. The option is not offered for CSV or Excel, and is recorded as off if requested for them.
- Press Generate. You get an immediate acknowledgement and a
PENDINGrow in the history; the page polls every five seconds and the row advances toGENERATING, thenCOMPLETED, on its own. - Download. The button fetches the bytes through the authenticated API and saves the file. It is not a public link — the account is re-checked on every download, and financial types re-check the role.
- Delete removes the row from the library, after a confirmation.
The report catalogue
Ten report types map to six aggregators plus one composable type. The pairs (REVENUE/REVENUE_SUMMARY, TICKETS/TICKET_ANALYSIS, ASSETS/ASSET_INVENTORY) run the same aggregation; both names exist for API compatibility, and the UI offers one of each.
| Type | Summary figures | Tables |
|---|---|---|
REVENUE / REVENUE_SUMMARY | Invoices, total billed, total collected, outstanding, collection rate | Revenue by client; revenue by status; every invoice |
TICKETS / TICKET_ANALYSIS | Tickets, resolved, resolution rate, mean time to resolve | By status; by priority; by client; every ticket |
ASSETS / ASSET_INVENTORY | Assets, total purchase value, warranty expired, warranty expiring within 90 days | By client; by type; by status; every asset |
CLIENT_HEALTH | Clients, tickets, SLA breaches, breach rate, billed | One row per client: tickets, open, breaches, breach rate, assets, billed |
SLA_COMPLIANCE | Tickets under SLA, response met, resolution met, response breaches, resolution breaches | Attainment by client; every breached ticket with its deadlines |
PROFITABILITY | Revenue, labour cost, gross margin, margin %, unpriced hours | Per client: revenue, hours, billable hours, cost, margin, margin % |
CUSTOM | The summaries of every dataset you name, prefixed by dataset | The tables of every dataset you name |
Date ranges mean different things per type, because the underlying data means different things:
- Revenue and profitability filter on invoice issue date.
- Tickets and SLA filter on ticket creation date.
- Assets filter on purchase date — an inventory is a stock, not a flow, so a range narrows to kit acquired in that window rather than excluding older kit that is still deployed.
- Client health filters tickets and invoices by the range; the open-ticket count and asset count are point-in-time.
Profitability costs labour at the rate recorded on each time entry — the rate that applied when the work was done. Entries with no rate are reported as “unpriced hours” and excluded from cost rather than costed at zero, which would overstate margin.
CUSTOM requires parameters.datasets, naming one or more of REVENUE, TICKETS, ASSETS, CLIENT_HEALTH, SLA_COMPLIANCE, PROFITABILITY. An unrecognised name fails the report with a readable reason instead of being silently dropped.
Report options
| Option | Values | Default | Effect |
|---|---|---|---|
type | The ten report types | required | Which aggregation runs |
title | up to 200 chars | per-type default | The document heading and the download filename |
description | up to 2000 chars | none | Stored on the row; searchable |
dateRange | ISO date or date-time | all time | The reporting window (see per-type meaning above) |
clientIds | up to 500 org ids | every visible org | Narrows the scope. Cannot widen it |
options.exportFormat | PDF, CSV, XLSX | PDF | Which renderer runs |
options.includeCharts | boolean | off | PDF only. Adds a bar chart above each chartable breakdown table. Forced off for CSV and XLSX |
parameters | free-form object | none | Type-specific. CUSTOM requires datasets |
What “chartable” means
Percentage columns are never plotted — a rate does not share a scale with the counts beside it. Bars are scaled against the largest magnitude in the set and every bar prints its own number, so the chart cannot imply a magnitude the data does not support.
In practice this charts the breakdowns (“By status”, “By priority”, “By client”, “By type”) and leaves the long raw listings as tables.
Report lifecycle
- A report is never left silently pending. A generator that throws marks its row
FAILEDwith the error message and counts into the job’s error total. - Two sweeps cannot render the same report. Each row is claimed with a conditional
PENDINGtoGENERATINGupdate; a row whose claim affects nothing was taken by the other sweep and is skipped. rowCount: 0on aCOMPLETEDreport means “no data matched”, not “the generator broke”. Both portals say so on the row.- Scope is resolved at generation time, from the report’s own account — not from the request, and not at queue time. A report queued before a client was detached cannot widen back into it later.
- The batch is bounded at 10 per sweep. A tenant that queues two hundred reports does not monopolise the worker; the next tick picks up the rest.
Analytics tabs (organization portal)
/org/reports also carries five live analytics tabs alongside the Exports tab.
| Tab | Source | Visible to |
|---|---|---|
| Overview | Organization executive stats | power user and above |
| Tickets & SLA | Ticket statistics | power user and above |
| Billing & Finance | Invoice and contract statistics | organization administrator and above |
| Assets | Organization executive stats | power user and above |
| Time Tracking | Time-entry statistics | power user and above |
| Exports | The report library | organization administrator and above |
The period selector in the header drives the Tickets & SLA and Time Tracking windows (as a day count) and the Overview and Assets windows (as a named period). Billing & Finance is month-to-date / year-to-date and does not move with it.
The platform dashboards
Paths: /platform/executive and /platform/reports, both platform-admin only.
/platform/executive shows account totals and status mix, platform MRR with its active/trial split, install-wide SLA attainment, ticket and change workload, alert severity, unbilled time, and the organizations with the most escalations and alerts.
/platform/reports is a four-tab read of the same install-wide aggregate: Overview, SLA & Operations, Revenue, Accounts.
Platform MRR is realized, not estimated. It sums PlatformSubscription.monthlyAmountCents across active and trialling subscriptions — the amount the billing engine actually bills, including seats, billing cycle and per-account price overrides.
Reporting windows
What the window does and does not move:
| Moves with the period | Point-in-time regardless |
|---|---|
| SLA attainment (tickets resolved in the window) | Open, urgent, unassigned and breached counts |
| Resolved escalations, recent escalation list | Active and pending escalations |
| Average resolution time and its sample size | Asset totals, alerts, warranty expiries |
| Invoice unpaid/overdue totals | |
| Client counts and MRR |
How every number is derived
The ones worth stating explicitly:
| Figure | Derived from |
|---|---|
| SLA rate with no sample | null, rendered as “No data”. Never 100%, never 0% |
| Client MRR | Organization.mrr summed across client organizations |
| MRR growth | Today’s client MRR against the most recent AccountMrrSnapshot on or before roughly one month ago, written daily by the account-mrr-snapshot job. null when there is no prior snapshot — “not enough history”, not 0% |
| Platform MRR | PlatformSubscription.monthlyAmountCents over ACTIVE and TRIAL |
| Average resolution time | A database-side average of resolvedAt - createdAt across every ticket resolved in the window, with its sample size. null when nothing was resolved |
| SLA compliance rate (reports page) | Counted over tickets whose SLA outcome is KNOWN — breached, or responded and resolved, or finished. Tickets still inside their target are excluded from both the numerator and the denominator, and appear in the at-risk counter instead |
| Realized revenue (MTD, YTD, monthly trend, collection rate) | SUM(payment.amount - payment.refundedAmount) over succeeded, partially-refunded and refunded payments. Refunds are subtracted by amount, so a partial refund reduces the figure rather than erasing the payment; uncaptured payments do not count at all |
| Unpaid / overdue | Invoice.status grouped; SENT, VIEWED, PARTIAL and OVERDUE count as unpaid, OVERDUE also counts as overdue |
| Assets with alerts | Active assets with at least one ACTIVE or ACKNOWLEDGED monitoring alert. Alerts arrive from a connected monitoring integration; with none connected this is legitimately 0 |
| Warranty expiring | Active assets with warrantyExpiry between now and 30 days out (90 days in the asset report). Warranty dates come from manual entry or CSV import — RMM discovery does not populate them, so a fleet built purely from discovery reports 0 here |
| Escalations | Ticket.isEscalated for counts; TicketEscalation rows for the recent list |
| Unbilled time | TimeEntry with no invoice and isBillable |
| Labour cost (profitability) | Each entry’s own hourlyRate multiplied by its duration. Entries with no rate are reported as unpriced hours and excluded |
Plan tiers
The report library carries no plan gate. Every report type, format and option is available on every tier including Free, for both MSPs and end customers. So is the organization executive dashboard.
The MSP cross-client roll-up is gated on the rollUpReporting entitlement:
| Level | What it unlocks |
|---|---|
| off | /msp/executive returns an upgrade offer |
BASIC | The operational roll-up: tickets, SLA, assets, escalations, clients |
FULL | Adds the financial roll-up: MRR, growth and top clients by revenue |
| Tier | rollUpReporting |
|---|---|
| MSP Free / Starter / Growth | off |
| MSP Scale | BASIC |
| MSP Enterprise | FULL |
| Customer Free / Essentials / Professional | off |
| Customer Business | BASIC |
| Customer Enterprise | FULL |
Platform administrators bypass the gate. An account with no resolvable tier is not blocked — the same fail-open-on-unresolved rule every other gate in the product uses.
Troubleshooting
| What you see | Cause |
|---|---|
Insufficient role | Your role is outside the report or dashboard role set. power user and CUSTOMER cannot use the report library at all. |
<TYPE> reports expose account-wide financials and require an administrator role | A technician tried to generate or download Revenue, Revenue Summary or Profitability. |
Cross-organization roll-up reporting isn't included in your current plan. | The account’s tier has rollUpReporting off. The response carries the upgrade offer and the page renders it. |
Date range start must be before end | Start is after end. |
Invalid date range | A date failed to parse. |
Report is not ready (status: pending) / (status: generating) | The sweep has not produced the artifact yet. The library polls; wait for the row to flip to Completed. |
Report generation failed: <reason> | The row is FAILED; the reason is shown on the row and repeated here. Retry re-opens the generate dialog for the same type. |
A CUSTOM report needs parameters.datasets — e.g. {"datasets":["REVENUE","TICKETS"]} | A CUSTOM report was queued without naming its datasets. |
Unknown dataset(s): <x>. Available: ... | A CUSTOM report named a dataset that does not exist. It fails loudly rather than quietly omitting a section you asked for. |
Report not found (404) | The report belongs to another account. Reports are scoped by account in the same query as the id, so another tenant’s report is indistinguishable from one that does not exist. |
| A report completes with “No data matched this range or client selection.” | rowCount is 0. The generator worked; nothing fell inside the range or the client selection. |
| SLA rings show “No data” | Nothing SLA-tracked was resolved in the window. This is the honest answer; the product will not show a green 100% for an unmeasured population. |
| MRR growth shows ”— MoM” | There is no AccountMrrSnapshot from roughly a month ago yet. Growth becomes available once the daily snapshot job has a month of history. |
| Revenue shows ”—” with “Requires an MSP administrator” | Your role is below MSP administrator. This is not a plan problem and no upsell is shown. |
| Outstanding shows ”—” with “Administrators only” | Your role is below organization administrator. Receivables are administrator data. |
Limits and known behaviour
- A report section is capped at 5,000 rows. A truncated table says so, in every format. Narrow the date range or the client selection for full detail.
- Charts are PDF only. Requesting them for CSV or Excel records the option as off; the API will not report back a setting it did not honour.
- Deleting a report removes the row, not the bytes. The stored file is left to the exports container’s lifecycle expiry rather than deleted inline, so a failed blob delete cannot leave you looking at a report you just deleted. The file is unreachable either way — downloads go through the row.
- There is no report scheduling. Reports are generated on request. There is no recurring or emailed report.
- Dashboards are not exportable as a document. To hand a number to someone outside the product, run the matching report.
- The MSP client picker offers managed clients only. The default (nothing selected) covers every organization in reach, including the MSP’s own, but you cannot select your own organization as an explicit filter.
- The reporting period does not apply to report artifacts. Reports carry an explicit date range chosen in the generate dialog.
- Reporting is not available in the customer portal or on mobile.
- Warranty exposure depends on warranty dates being present. Assets created by RMM discovery carry no warranty date until someone enters one or imports it, so “warranty expiring” reports 0 on a discovery-only fleet. This is an absent input, not a broken metric.
- Alert counts depend on a connected monitoring integration. With none connected, alert figures are legitimately zero.
- SLA compliance excludes work still inside its target. A ticket that has not breached and has not been resolved is neither met nor missed; it appears in the at-risk counter instead. This is why compliance can be “No data” on a busy desk that has simply not closed anything in the window yet.
REVENUE/REVENUE_SUMMARY,TICKETS/TICKET_ANALYSISandASSETS/ASSET_INVENTORYare aliases. Each pair runs identical aggregation; the UI offers one name from each pair.