Per-client and per-service-line margin computation for an MSP: revenue from PSA billing or accounting invoices, cost of goods from Pax8/Sherweb wholesale pricing, and estimated labor from PSA time entries × a loaded technician rate. Covers the full-margin / gross-margin-only / cost-data-unavailable reporting tiers and why a missing cost input is flagged rather than interpolated from portfolio averages.
Installs into .claude/skills of the current project.
Are you the author of Margin Analysis?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/wyre-technology-margin-analysis)
---
name: "Margin Analysis"
description: >
Per-client and per-service-line margin computation for an MSP: revenue from
PSA billing or accounting invoices, cost of goods from Pax8/Sherweb
wholesale pricing, and estimated labor from PSA time entries × a loaded
technician rate. Covers the full-margin / gross-margin-only /
cost-data-unavailable reporting tiers and why a missing cost input is
flagged rather than interpolated from portfolio averages.
when_to_use: >-
When ranking clients or service lines by profitability, or investigating
whether a client is profitable at all. Use when: margin analysis, client
profitability, which clients make money, unprofitable clients, cost to
serve, margin by client, margin erosion, service line profitability,
contract margin, realized rate.
---
# Margin Analysis
## Overview
Revenue is visible in every PSA and accounting system; cost is not. MSPs
routinely know what they bill a client but not what it actually costs to serve
them, because cost has two very different components with very different data
quality: **cost of goods** (marketplace subscription costs from Pax8/Sherweb —
usually complete and precise) and **cost of labor** (technician time actually
spent — usually incomplete, because not every MSP enforces disciplined time
entry against every ticket).
This skill computes margin at the client or service-line level using whatever
cost data is actually available, and is explicit and conservative about what it
could not compute — a margin number built on a guessed labor cost is worse than
no margin number, because it will be trusted and acted on.
```
Margin = Revenue − Cost
Margin % = (Revenue − Cost) / Revenue × 100
Revenue = billed amount from PSA/accounting invoices for the period
Cost = marketplace wholesale cost (Pax8/Sherweb) + estimated labor cost
(PSA time entries × loaded technician rate, when available)
```
## Anti-triggers
- **Accounting-platform financial reports** — Profit and Loss, Balance
Sheet, and Aged Receivables are prebuilt reports with their own parameters
and response shape; use `xero-reports` or `qbo-reports`.
Neither produces per-client or per-service-line margin, which is what this
skill assembles.
- **Time entries or billing items as records** — use
`autotask-time-entries` or `autotask-billing`.
## Connected Systems
| System | Role | Required? |
|--------|------|-----------|
| PSA (Autotask/HaloPSA/ConnectWise/Syncro) or accounting (QBO/Xero) | Revenue — billed amount per client/service line | Yes (at least one) |
| Pax8 / Sherweb | Cost of goods — marketplace wholesale cost per subscription | Optional — recommended |
| PSA time entries (Autotask/HaloPSA/ConnectWise/Syncro) | Cost of labor — hours logged per client, for estimating labor cost | Optional |
Revenue is the only hard requirement. Everything else is a cost input that
improves accuracy but is not mandatory — this skill must still produce useful
output with only revenue and zero cost data, by explicitly reporting "cost data
unavailable" rather than fabricating a margin figure.
## Workflow
### Step 1: Discover what's connected
Call `conduit__search_tools` and search for `"invoice"`, `"subscription"`, and
`"time entr"` to determine which revenue and cost sources are live for this
org. Proceed with whatever combination exists.
### Step 2: Pull revenue per client
Pull billed amounts for the analysis period from whichever accounting/PSA
billing source is connected (`qbo__list_invoices`, `xero__list_invoices`, or
the PSA's invoice/billing-item search tool). Sum to a per-client revenue total
for the period. Where service-line-level margin is requested rather than
whole-client margin, keep revenue broken out by line description/service
rather than collapsing to a single total.
### Step 3: Pull cost of goods (if connected)
Pull active subscriptions from Pax8/Sherweb (`pax8__list_subscriptions` or the
Sherweb equivalent) for the same client, and sum wholesale unit cost × quantity
to a per-client cost-of-goods total for the period. This is the most reliable
cost input available — treat it as ground truth once fetched.
### Step 4: Estimate cost of labor (if time-entry data is connected — treat as an estimate, always)
Pull time entries for the client for the period from the PSA
(`autotask__search_time_entries` or the connected PSA's equivalent). Sum
logged hours. Multiply by a loaded technician rate to estimate labor cost.
**The loaded rate must come from context, not be invented:**
- If the user or prior context supplies a blended/loaded hourly rate, use it.
- If the PSA exposes a billing rate or cost rate per resource, use that as the
best available proxy and say so.
- If no rate is available from any source, do **not** invent one. Report hours
logged as a fact, mark estimated labor cost as "not computed — no loaded
rate available," and exclude it from the margin calculation rather than
silently defaulting to an arbitrary number.
Time-entry data is inherently an underestimate of true cost-to-serve when time
tracking discipline is inconsistent — note this caveat in the report whenever
labor cost is included, so the reader doesn't over-trust a number built on
incomplete logging.
### Step 5: Compute margin, flagging what's missing
For each client (or service line):
- **Full data (revenue + COGS + labor)** — compute margin and margin %
normally.
- **Revenue + COGS only** — compute a "gross margin (excludes labor)" figure
and label it as such. Do not present it as full margin.
- **Revenue only** — do not compute a margin figure. Report revenue and state
"cost data unavailable — margin cannot be computed" rather than guessing or
omitting the client from the report entirely.
Never interpolate a missing cost component from portfolio averages or silently
skip a client with incomplete data — both produce a false sense of coverage.
Every client in scope appears in the output, with its data-completeness state
explicit.
### Step 6: Rank and flag
Sort clients by margin % ascending (worst first) among those with a computed
margin. List clients with incomplete cost data in a separate section, not
interleaved with ranked results, so the ranking itself is never built on an
apples-to-oranges mix of full-margin and gross-margin-only figures. Flag any
client with negative margin (operating at a loss) prominently regardless of
where they sort.
## Report Format
```
═══════════════════════════════════════════════════════════════════
MARGIN ANALYSIS
Period: [Month/Quarter Year]
Cost inputs available: COGS [✓ / not connected] | Labor [✓ / not connected]
Generated: [Date]
═══════════════════════════════════════════════════════════════════
RANKED — FULL MARGIN (Revenue − COGS − Labor)
1. [Client] — Revenue $[X] − COGS $[X] − Labor $[X] = Margin $[X] ([X]%)
...
N. [Client] — Margin $[X] ([X]%) ⚠ NEGATIVE MARGIN — operating at a loss
RANKED — GROSS MARGIN ONLY (Revenue − COGS, labor not available)
[Client] — Revenue $[X] − COGS $[X] = Gross Margin $[X] ([X]%)
[Note: excludes labor cost — not directly comparable to full-margin clients above]
COST DATA UNAVAILABLE
[Client] — Revenue $[X] — no COGS or labor data connected; margin not computed
SUMMARY
Clients with full margin computed: [N]
Clients with gross margin only: [N]
Clients with no cost data: [N]
Clients operating at a loss: [N]
═══════════════════════════════════════════════════════════════════
```
## Graceful Degradation
| Missing / Unavailable | Handling |
|---|---|
| No accounting/PSA billing connected | Cannot run — no revenue source. State this explicitly. |
| No Pax8/Sherweb connected | Compute margin on labor only if available, or report revenue-only; never omit COGS silently from a "full margin" label. |
| No PSA time entries connected | Compute gross margin (revenue − COGS) and label it as such; do not present as full margin. |
| No loaded/blended labor rate available anywhere | Report hours logged as a fact; exclude labor cost from margin math rather than guessing a rate. |
| Client has revenue but zero rows in every cost source | Report revenue and mark "cost data unavailable" — do not drop the client from the report. |
## Related Skills
- [`agreement-reconciliation`](../agreement-reconciliation/SKILL.md) — confirm billed revenue is accurate before trusting it as a margin input
- [`license-true-up`](../license-true-up/SKILL.md) — confirm marketplace cost inputs reflect actual provisioned/deployed seats, not stale subscription data
- [`shared/skills/billing-reconciliation`](../../../shared/skills/billing-reconciliation/SKILL.md) — margin math reference for Pax8-cost-vs-sell-price comparisons at the subscription level