Client Profitability Reporter
A worked example of an advanced workflow: a Claude-managed scheduled agent that, once a month, reads QuickBooks Online revenue data, ranks clients by revenue for the period, pulls the company P&L for net-margin context, and publishes a digest to Slack as a canvas with a one-line summary. It is strictly read-only and never writes to QuickBooks. There are no servers and no code to deploy: the agent is a saved prompt plus a schedule plus two MCP connectors, running in Claude’s cloud.
One thing to understand before building: QuickBooks Online tracks revenue by customer cleanly, but it has no per-customer cost dimension — it does not know how many technician hours, licenses, or tools you spent serving each client. This routine therefore produces a revenue-ranked view of your client book, not true cost-allocated margin. Company-level net margin comes from the P&L. For full per-client margin (revenue minus labor plus license plus tooling cost), you need an analysis that composes PSA labor data, accounting revenue, and marketplace costs — that is the deep version, sketched in Extending it; this routine is the fast monthly revenue pulse.
What it builds
Section titled “What it builds”The finished workflow runs unattended on a monthly cron, aligned to the accounting close. Each run it pulls the customer-sales report for the prior month, ranks clients by revenue, pulls the company P&L for net-margin context, and notifies your team — turning a buried QBO report into a monthly revenue digest with a Slack trail. The report is explicit about what it is (revenue-ranked) and what it is not (cost-allocated margin), so readers do not mistake a revenue ranking for a profitability ranking.
Prerequisites
Section titled “Prerequisites”Before building, your Claude account needs all of the following:
| Requirement | Notes |
|---|---|
| Conduit connector | Connected in claude.ai (https://conduit.wyre.ai/v1/mcp). Provides the qbo__* tools. See Connecting an AI Client. |
| QuickBooks Online connected in Conduit | The QuickBooks Online vendor connection must be able to read reports and customers. The routine uses only read tools — no write scope is required. |
| Slack connector | The first-party Slack connector connected in claude.ai (https://mcp.slack.com/mcp). Provides slack_create_canvas and slack_send_message. |
| Scheduled routines access | Created via the /schedule capability; managed at claude.ai/code/routines. |
| A destination Slack channel | e.g. #finance or #leadership, plus its channel ID. The Slack connector must have access to it. |
Known gotchas
Section titled “Known gotchas”These are the things that cost real time the first time through. Account for them up front and the build genuinely takes minutes.
- This is a revenue ranking, not a profitability ranking — be explicit. The single biggest misread of a “client profitability” report built purely from QBO is treating revenue rank as margin rank. Your highest-revenue client could be your least profitable if the engagement is labor-intensive. The routine explicitly flags this in its output. Do not remove that language.
- Pin the reporting period in the prompt. The QBO report tools return
period-scoped data, and the default period shifts depending on when you
call them. If the routine does not pin explicit
start_dateandend_datevalues, consecutive monthly runs may cover different windows and the numbers will not be comparable. The routine prompt pins the period to the prior full calendar month so each run covers the same clean, closed window. - One QBO company file per connection — one routine per entity. A QuickBooks Online vendor connection points at a single company file. If your MSP manages multiple legal entities in separate QBO files, run one routine per QBO connection rather than trying to sweep all entities in a single run. The company context is set at the connection level, not per tool call.
permitted_toolsmust be populated per connector. A routine with a connector attached but an emptypermitted_toolslist runs with no tools and silently does nothing — no error, no output. List the exact tool names the routine needs.- A routine reaches only its attached connectors. The routine sandbox blocks arbitrary network egress, so notifications must go through the Slack connector’s tools, not an outbound webhook. Attach every connector the workflow touches.
- Faster-than-hourly cadences are rejected. The scheduler will not
accept a cron more frequent than once an hour. The monthly cron
(
0 12 1 * *) is well within that limit. Note that cron expressions are interpreted as UTC — the 1st of the month at 08:00 America/New_York is0 12 1 * *in UTC (12:00 UTC during Eastern Daylight Time; adjust to0 13 1 * *when Eastern Standard Time is in effect). - If Slack is not in the
/scheduleconnector list, read itsconnector_uuidandurlfrom an existing routine that already uses Slack: list routines via RemoteTrigger, get one that uses Slack, and copy themcp_connectionsentry. The build prompt handles this automatically. - Prefer report tools over per-record gets.
qbo__qbo_reports_customer_salesandqbo__qbo_reports_profit_and_lossreturn aggregated summaries in a single call. Useqbo__qbo_customers_listonly to resolve a customer name not already present in the report payload — do not call it in a per-customer loop, which risks the 60-second tool timeout across a large customer book.
The one-shot build prompt
Section titled “The one-shot build prompt”With the connectors above in place, paste this to Claude. It confirms the Conduit connector, creates the routine, and confirms its configuration from the API response.
Build me a scheduled Client Profitability Reporter agent. Do all of this end to end:
1. Confirm the Conduit connector works and QuickBooks Online is reachable: call qbo__qbo_reports_customer_sales and check it returns data for the current period. QuickBooks here is a single company file — there is no per-tenant loop.2. Confirm a Slack connector is connected. Note the destination channel name and ID (e.g. #finance). If Slack does not show in the /schedule connector list, read its connector_uuid and url from an existing routine that already uses Slack (RemoteTrigger list -> get -> mcp_connections).3. Create a Claude-managed scheduled routine named "Client Profitability Reporter": - Schedule: monthly, cron "0 12 1 * *" (1st of month 08:00 America/New_York = 12:00 UTC). Faster-than-hourly cadences are rejected. Monthly aligns with the accounting close cycle so each run covers a clean, closed period. - Attach TWO connectors, each with permitted_tools populated: * Conduit: qbo__qbo_reports_customer_sales, qbo__qbo_reports_profit_and_loss, qbo__qbo_customers_list — READ tools only. Do NOT list any qbo__qbo_*_create or qbo__qbo_*_update tool. * Slack: slack_create_canvas, slack_send_message An empty permitted_tools list = the routine runs with no tools. - Routine prompt: every run, pull the customer-sales report for the prior month, rank clients by revenue (top and bottom), pull the company P&L for net margin context, build a digest noting that this is revenue-ranked (not cost-allocated margin), publish a canvas "Client Profitability — <month>" and post a one-line summary to Slack. Use the exact routine prompt below.4. Confirm the API response echoes both connectors with the correct permitted_tools (verify NO write tools are present) and the routine prompt verbatim. Record the trig_... ID.The resulting routine prompt
Section titled “The resulting routine prompt”This is the lean prompt the build process installs into the scheduled routine itself. Substitute your own destination channel if it is not the default. Do not remove the revenue-vs-margin disclaimer language — it is load-bearing for reader trust.
You are the Client Profitability Reporter. You run monthly, READ-ONLY against QuickBooks Online (a single company file). You must NEVER create or modify any QuickBooks record — only read.
Use ONLY: qbo__qbo_reports_customer_sales, qbo__qbo_reports_profit_and_loss, qbo__qbo_customers_list, slack_create_canvas, slack_send_message.
IMPORTANT — what this report measures: QuickBooks Online tracks revenue by customercleanly, but it does NOT track cost-to-serve (technician labor, tooling, licenses)per customer. This report produces a REVENUE-RANKED view of clients, notcost-allocated margin. The company P&L provides net-margin context at the companylevel only. Acknowledge this limitation explicitly in the output.
Steps:1. Determine the reporting period: the full prior calendar month (e.g. if today is 2026-06-01, the period is 2026-05-01 to 2026-05-31). State the exact period in the report — this makes month-over-month comparison unambiguous.2. Call qbo__qbo_reports_customer_sales with start_date and end_date set to the prior month boundaries to get revenue by customer for the period.3. Rank clients by total revenue for the period (descending). Identify: - TOP 5 clients by revenue (name, revenue, % of total revenue) - BOTTOM 5 clients by revenue, excluding $0 rows if there are more than 10 active clients (to avoid polluting the bottom-5 with inactive accounts)4. Call qbo__qbo_reports_profit_and_loss with the same period to get company-level figures: total revenue, gross profit, and net income. Calculate net margin (net income ÷ total revenue × 100) for the period.5. Call qbo__qbo_customers_list only if you need to resolve a customer name not already present in the customer-sales report.
Build a report with four sections:- REPORTING PERIOD: state the month and the note that this is revenue-ranked, not cost-allocated margin. Explain that QuickBooks has no per-customer cost dimension unless classes or projects are used, and that full cost-allocated analysis requires composing PSA labor data with this revenue view.- TOP 5 CLIENTS BY REVENUE: name, revenue, % of total, sorted descending.- BOTTOM 5 CLIENTS BY REVENUE: name, revenue, % of total, sorted ascending (lowest first).- COMPANY P&L CONTEXT: total revenue, gross profit, net income, and net margin % for the period. Note that margin is company-wide, not per-client.
Include a one-line executive summary: "Top client <name> $X; bottom active client<name> $Y; company net margin Z% — <month>. Revenue-ranked view; cost-allocatedmargin requires PSA labor data."
Could not read section: if any report cannot be fetched, include a "could not read"section naming the report and the error. Do not skip silently or fabricate figures.
Deliver:- Call slack_create_canvas titled "Client Profitability — <month YYYY-MM>" with the full report in Markdown.- Call slack_send_message to the destination channel with the one-line executive summary and a link to the canvas.
This routine is strictly read-only: never call any qbo write or update tool.How it works
Section titled “How it works”Monthly cadence, aligned to the accounting close
Section titled “Monthly cadence, aligned to the accounting close”Revenue-by-client is a monthly signal, not a daily one — it follows the billing and close cycle. The routine runs on the 1st of each month so it covers the prior full calendar month: a clean, closed period where all invoices have been raised and the books are stable. Running mid-month would produce a partial-period ranking that changes every day and is misleading for any comparison. Monthly is also kind to the API: one read sweep per month.
Revenue-ranked, not cost-allocated margin
Section titled “Revenue-ranked, not cost-allocated margin”This is the most important thing to understand about a QBO-only profitability report. QuickBooks Online records what you invoiced to each customer, not what it cost to serve them. Technician labor hours, tooling subscriptions, license costs, and travel time do not live in QBO at the per-customer level — they live in your PSA (Autotask, HaloPSA, etc.) and in your vendor bills, which are not split by customer unless you use QBO Classes or Projects.
The result is that this routine produces a revenue ranking: who paid you the most last month. The company P&L adds the company-wide net margin as context — “we earned $X in revenue and kept Y% of it” — but that margin number is not allocated down to individual clients. A client who appears in your top five by revenue may be your least profitable engagement once labor is factored in. The routine’s output states this limitation explicitly.
Company P&L as the margin-context number
Section titled “Company P&L as the margin-context number”Alongside the client revenue ranking, the routine pulls
qbo__qbo_reports_profit_and_loss for the same period to surface the
company-level picture: total revenue, gross profit, net income, and net
margin percentage. This gives the reader a single sentence of financial
context — “we made X% net margin this month” — without implying that
margin is distributed evenly across clients. It is a sanity check on the
overall business health for the period, not per-client analysis.
Canvas and summary delivery
Section titled “Canvas and summary delivery”The full ranked client list and P&L context can be long, so the routine publishes it as a Slack canvas — a durable document teammates can scroll, share, and compare month to month — and posts only a one-line executive summary to the channel linking it. The channel stays readable; the detail lives in the canvas. See delivery adapters for other ways to surface a routine’s output.
Extending it
Section titled “Extending it”For true cost-allocated per-client margin, build the deep version as an
interactive agent on the same Conduit
connection: compose PSA labor data from Autotask
(autotask__autotask_search_time_entries) or HaloPSA, QBO revenue, and
marketplace cost data (e.g. pax8__* subscriptions) to produce a margin
figure per client. That analysis is too heavy for an unattended monthly
routine; use this routine to identify which clients are driving revenue,
then run the deep analysis to understand which of those clients are
actually profitable.
If your PSA carries cost data at the client level, you can approximate
per-client margin without the full analysis by permitting the HaloPSA
tools (halopsa__*) in a separate routine. HaloPSA’s built-in
profitability reports factor in labour cost if your technician charge
rates and cost rates are configured — permit the HaloPSA tools alongside
the QBO tools and cross-reference the two views in a single canvas.
The workflow is also accounting-platform-agnostic. Swapping to Xero is a
tool-list change: replace qbo__qbo_reports_customer_sales,
qbo__qbo_reports_profit_and_loss, and qbo__qbo_customers_list in
permitted_tools with the Xero connection’s equivalents (xero__*). The
report-building logic and Slack delivery body are identical — the tool
names are the only things that change.
Questions or a workflow you’d like documented? See Getting support.

