
The reporting stack I described in the last article was built for one account at a time. Daily recap, weekly client report, period comparison — each a Claude Code skill that reads a few APIs and formats an answer.
The follow-up question every operator asks is the obvious one. What happens when it's not one account. What happens when it's a book of accounts, each on different channels, with different brands, different report templates, different recipients. That is where most home-built reporting setups fall apart. It is also where the architecture matters more than the skills themselves.
The Operator Pain
Monday morning across a book of accounts used to mean N tabs and N spreadsheets. Open Meta Ads Manager, switch the account dropdown, wait for it to load, copy the spend. Open Shopify, switch the store, copy the revenue. Open Amazon Seller Central, switch the marketplace, copy the orders. Paste into a spreadsheet. Reconcile the Amazon FBM rows against Shopify so revenue is not double-counted. Format the email. Send. Move to the next account. Repeat.
The other option was paying a reporting platform a per-account fee to do the same work behind a dashboard nobody opens. Either way, the cost scaled linearly with the number of accounts.
The point of moving reporting into Claude Code was to break that linearity. One skill should serve every account. Adding an account should be editing a config file, not building a new pipeline.
The Architecture
The whole thing hinges on a single file at the repo root — clients.yaml. Every account is one entry. Slug, display name, channel credentials, brand colors, recipient list. That is the only place client-specific configuration lives.
clients:
shelskys:
name: "Shelsky's of Brooklyn"
channels:
meta:
account_id: "act_1447863689312920"
google:
customer_id: "234-567-8901"
shopify:
store: "shelskys.myshopify.com"
brand:
primary_color: "#1a3a52"
accent_color: "#d4732a"
logo_url: "..."
recipients: ["[email protected]", "[email protected]"]
cobi:
name: "Cobi"
channels:
meta:
account_id: "act_458428125665693"
shopify:
store: "cobiclothing.myshopify.com"
amazon:
marketplace_id: "ATVPDKIKX0DER"
seller_id: "..."
brand:
primary_color: "#0f2540"
accent_color: "#f0883a"
recipients: ["..."]
The shape is the point. Each account names only the channels it has. Cobi has Meta, Shopify, and Amazon. Shelsky's has Meta, Google, and Shopify but no Amazon. The reporting skill reads this file and only fires the pulls for channels that are actually wired.
This is the difference between a multi-tenant tool and a tool that hardcodes assumptions. A SaaS dashboard either supports a channel or it does not, and if it supports a channel for one account, it tries to render it for every account. The config-driven version skips what is not there.
The Skill
The skill itself — monday-report — is short. It reads clients.yaml, takes a slug as an argument, and iterates the channels for that slug. For each channel, it calls the matching pull function. Meta pull. Google pull. Shopify pull. Amazon pull if wired. The pulls run in parallel because they are independent API calls.
The orchestration is maybe two hundred lines. Most of the lift is in the channel-specific pull functions, which are reusable across the daily, weekly, and comparison reports. Same Meta pull powers all three. Same Shopify pull. The skill is the routing layer on top of the pulls.
Running it looks like this.
$ claude /monday-report cobi
Reading clients.yaml...
Wired channels: meta, shopify, amazon
Pulling Meta (last 7 days)... done (1.2s)
Pulling Shopify (last 7 days)... done (0.9s)
Pulling Amazon (last 7 days)... done (2.1s)
Deduping Amazon FBM rows that synced to Shopify... 4 rows removed
Rendering HTML email...
Sending via Resend to [email protected], [email protected]
Sent.
End-to-end, one account, about ten seconds. The skill knows what channels to pull because the config told it. It knows what colors to use because the config told it. It knows where to send the email because the config told it.
Dedupe and Blending
This is the part that is easy to underestimate until you ship it.
Amazon FBM orders — fulfilled by merchant, shipped through Shopify — appear in both Amazon Seller Central reporting and Shopify's order feed. If you naively add Amazon revenue plus Shopify revenue, you double-count those orders. The number goes into the client email. The client emails back asking why your revenue does not match their internal dashboard. Trust gone.
The skill handles this with a small reconciliation step. Pull Amazon orders. Pull Shopify orders. For each Amazon order, look for a matching Shopify order by order date plus customer plus total. Drop the matches from one of the two sources. Blend the rest.
Same logic applies to blended ad spend across Meta and Google, blended ROAS across the full channel mix, and the all-channels revenue total. The dedupe and blending logic lives in one place — a shared utility module that every reporting skill imports. Daily recap uses it. Weekly report uses it. Period comparison uses it.
The reason this matters in a multi-client article is that the rules are not the same for every account. Cobi has Amazon FBA and Amazon FBM. Shelsky's has Shopify only. Auburn has Google Ads but no e-commerce store at all. The reconciliation logic has to be conditional on what is actually wired, which the config file already encodes.
Output Routing
Same skill, three output channels. This is the other quiet win of the config-driven architecture.
The daily recap version prints to the terminal — a Slack-message-shaped block of text. Good for the operator running through accounts in the morning. No formatting overhead, no email send, instant feedback.
The weekly report version renders a branded HTML email and ships via Resend to the recipient list named in the config. Brand colors come from the same config block. The client sees their colors, their logo, their tone — the email looks like it was built for them because it was, with the design system pulling from one source.
The period comparison version writes a markdown file to ~/Projects/clients/<slug>/reports/<name>.md. Filed under the client folder. Version-controllable. Shareable as a link or pasted into Slack. Same numbers, same dedupe logic, different output medium for a different reading context.
One skill, three output channels, all configured by argument flags. The intelligence sits in the pulls and the reconciliation. The output rendering is the easy part once the data is right.
What This Replaces
Multi-client dashboards billed per account. A platform that charges $200-300 per account per month for a weekly report most clients never log into is the obvious target. Across a book of accounts, that is the line item that grows fastest as you sign new clients.
Freelance reporting help. The other version of this is paying someone to manually pull numbers each Monday morning. The cost scales worse than software because there is no economy of scale on the labor side.
The Monday-morning manual ritual itself. Two to three hours of tab-switching and spreadsheet reconciliation, every week, forever. The cost of that hour shows up as the work you did not do — the campaign optimization that got skipped, the creative brief that did not get reviewed, the client call you ran on autopilot because you were still mentally pasting numbers.
Where the Starter Falls Short
A config-driven multi-client reporting skill is the foundation. It is not the whole production system.
- No anomaly detection. The starter prints whatever it pulls. It does not flag a 40% week-over-week spend drop, a sudden ROAS cliff, or a pixel that stopped firing. Those need a separate layer that compares each pull to a rolling baseline and surfaces the deltas worth investigating.
- No retry logic for API failures. When the Meta API rate-limits or Shopify times out, the starter just errors. Production needs exponential backoff, partial-data handling, and a fallback that ships the report with a flagged gap rather than failing the whole send.
- No scheduling. The starter runs when you run it. Production schedules the Monday-morning send across the full book of accounts via cron, with timezone awareness for each client's local Monday.
- No template variants per client. The starter renders the same HTML structure with different colors. Some clients want a different section order, a different table format, an executive-summary-only version for the CEO and a detail version for the marketing lead. Production handles per-client template overrides.
The Production Version
The full multi-client reporting setup lives in the Reporting Command Center pack on operatorstack.app/packs/reporting-command — $99 for the pack. It adds the anomaly layer, the retry logic, the scheduler, the per-client template overrides, and the cross-channel dedupe utilities that this article only sketches.
If you want the wider Claude Code workflow — channel-specific report skills, the API crash course, and the rest of the operator stack — the full bundle is $497.
Or hire Clare Digital and we will run the whole reporting layer across your accounts.
Q: Does this work if I only have one account?
Yes. The architecture is the same. A clients.yaml with one entry runs identically to one with many entries. The advantage of building it this way from the start is that adding the second account later is editing one file, not refactoring a pipeline.
Q: What stops the API credentials in clients.yaml from leaking?
The version of clients.yaml that gets committed has placeholders. Real credentials live in environment variables — META_TOKEN, SHOPIFY_TOKEN, and so on — and the config file references them by name. The credentials never end up in a repo or in a draft email.
Q: How does adding a new client work end to end?
About thirty minutes. Add an entry to clients.yaml with the slug, name, recipient list, and brand colors. Add the API credentials to the environment. Run the daily recap once to verify the pulls return real numbers. Done. The Monday report and period comparison skills work immediately off the same config.