Acme at risk: traffic down 12%, 2 hours down, invoice 18 days overdue
1 of 6 clients is at risk. Acme: traffic down 12%, 2 hours of downtime, invoice 18 days overdue. Harbor Dental was not billed for September.
Monthly: one row per client with traffic, search clicks, ad results, downtime, site upkeep and overdue invoices, and the clients showing more than one warning.
Reads
You get
The card on the right is a sample. Yours shows your own numbers and updates in place every run.
What lands on your board
1 of 6 clients is at risk. Acme: traffic down 12%, 2 hours of downtime, invoice 18 days overdue. Harbor Dental was not billed for September.
Clients rarely leave over one bad number. They leave when traffic slips, the site goes down the same week, and the invoice that follows sits unpaid because they are already looking elsewhere. Account managers see the marketing side, the ops person sees the outage, the bookkeeper sees the late payment, and nobody sees all three for the same client.
Once a month the agent reads your client map, pulls each client's numbers from the marketing, uptime, website and billing tools you use, and lines them up in one row per client. A client with two or more warnings is marked at risk, with the specifics, so the account manager calls before the client does.
Read the client list from settings: client name, GA4 property ID, Search Console property, Google Ads customer ID, UptimeRobot monitor ID, site URL and the customer name exactly as it appears in the books. A blank cell means the client does not use that service. The client name is the key every other step joins on.
GA4 runReport with two dateRanges (last month and the one before) and metrics sessions and keyEvents; Search Console searchanalytics.query with no dimensions for clicks in both months; Google Ads SELECT metrics.cost_micros, metrics.conversions FROM customer WHERE segments.date DURING LAST_MONTH through your manager account, and the month before with explicit dates.
getMonitors with monitors=<ids>, custom_uptime_ranges=<start>_<end> for last month and logs=1&log_types=1 for the outages. Downtime minutes = (100 - ratio) / 100 x minutes in the month. For WordPress sites, GET <site>/wp-json/wp/v2/posts?per_page=1&_fields=date gives the last published post, and wp plugin list --update=available --format=count the waiting plugin updates where you have WP-CLI.
QuickBooks: SELECT * FROM Invoice WHERE Balance > '0' AND DueDate < '<today>' for overdue invoices and SELECT * FROM Invoice WHERE TxnDate >= '<first of last month>' for what was billed. Xero, if that is your ledger: Invoices with where=Type=="ACCREC" AND Status=="AUTHORISED". Match CustomerRef name or Contact.Name to the map after lowercasing and dropping inc, llc and ltd; days overdue is today minus DueDate.
One row per client. Flags: sessions or organic clicks down 10% or more; Ads conversions down 20% or more on similar spend; 60 or more minutes of downtime; no post in 60 days on a content retainer; an invoice 14 or more days overdue. Two flags or more is at risk. A mapped client with no invoice dated last month is "not billed"; a customer in the books missing from the map goes on an "unmapped" line.
With BRIEFKIN_TOKEN set, the agent sends the result as one card that updates in place every run. Without it, you get the report in the chat.
Pick your agent, where the data comes from and how to schedule it; the commands below follow.
One command. It puts the SKILL.md in ~/.claude/skills and in ~/.agents/skills, which Codex, Cursor, Gemini CLI, Copilot CLI and OpenCode read too.
shellcurl -fsSL https://briefkin.dev/skills/agency-client-health/install | shAsk Claude Code:
promptRun the agency-client-health skillOr paste the prompt instead of installing:
promptCheck the health of each of my agency clients for last month. Here is my client map: <client, GA4 property ID, Search Console property, Google Ads customer ID, UptimeRobot monitor ID, site URL, customer name in QuickBooks>. For each client, compare last month with the month before for GA4 sessions and key events, Search Console clicks and Google Ads conversions and spend. From UptimeRobot get last month's downtime in minutes. For WordPress sites, find the date of the last published post. From QuickBooks (or Xero), find overdue invoices with days overdue and whether the client was billed last month, matching customer names to my map. Flag sessions or clicks down 10% or more, conversions down 20% or more, 60 or more minutes of downtime, no post in 60 days, and invoices 14 or more days overdue. Give me one row per client, mark clients with 2 or more flags as at risk, and tell me who to call first.3rd of the month at 10:00, from cron on any machine where Claude Code is signed in:
crontab0 10 3 * * claude -p "Run the agency-client-health skill"Add it with crontab -e. A laptop asleep at that time runs it when it wakes if you use launchd or Task Scheduler instead; GitHub Actions works for the API route.
Connect Briefkin once with curl -fsSL https://briefkin.dev/install | sh and every run lands on your board as a report card. How setup works
SKILL.md---
name: agency-client-health
description: "Monthly: one row per client with traffic, search clicks, ad results, downtime, site upkeep and overdue invoices, and the clients showing more than one warning. Use when the user asks for this, or when it runs on a schedule."
---
# Client health in one card: marketing, uptime and invoices per client
Monthly: one row per client with traffic, search clicks, ad results, downtime, site upkeep and overdue invoices, and the clients showing more than one warning.
Clients rarely leave over one bad number. They leave when traffic slips, the site goes down the same week, and the invoice that follows sits unpaid because they are already looking elsewhere. Account managers see the marketing side, the ops person sees the outage, the bookkeeper sees the late payment, and nobody sees all three for the same client.
Once a month the agent reads your client map, pulls each client's numbers from the marketing, uptime, website and billing tools you use, and lines them up in one row per client. A client with two or more warnings is marked at risk, with the specifics, so the account manager calls before the client does.
## Two ways to get the data
Use the API or MCP route when you have an MCP server, a CLI or an API token for the app. Otherwise, if you can drive a browser the user is signed in to (Claude in Chrome, a Playwright or Chrome DevTools MCP server), use the browser route. Say which one you used.
## Route A: API or MCP server
### What you need
- A client map in settings with each client's GA4 property, Search Console property, Ads customer ID, UptimeRobot monitor, site URL and customer name in the books, spelled as the books spell it.
- Viewer access to each GA4 property and Search Console site, Ads access through your manager account, a read-only UptimeRobot key, and read access to QuickBooks or Xero invoices. WP-CLI only for plugin update counts.
If something is missing, say what and stop; do not invent numbers.
### Steps
1. **Load the client map**: Read the client list from settings: client name, GA4 property ID, Search Console property, Google Ads customer ID, UptimeRobot monitor ID, site URL and the customer name exactly as it appears in the books. A blank cell means the client does not use that service. The client name is the key every other step joins on.
2. **Pull marketing for both months** (Google Analytics): GA4 `runReport` with two `dateRanges` (last month and the one before) and metrics `sessions` and `keyEvents`; Search Console `searchanalytics.query` with no dimensions for clicks in both months; Google Ads `SELECT metrics.cost_micros, metrics.conversions FROM customer WHERE segments.date DURING LAST_MONTH` through your manager account, and the month before with explicit dates.
3. **Measure downtime and site upkeep** (UptimeRobot): `getMonitors` with `monitors=<ids>`, `custom_uptime_ranges=<start>_<end>` for last month and `logs=1&log_types=1` for the outages. Downtime minutes = (100 - ratio) / 100 x minutes in the month. For WordPress sites, `GET <site>/wp-json/wp/v2/posts?per_page=1&_fields=date` gives the last published post, and `wp plugin list --update=available --format=count` the waiting plugin updates where you have WP-CLI.
4. **Read billing per client** (QuickBooks): QuickBooks: `SELECT * FROM Invoice WHERE Balance > '0' AND DueDate < '<today>'` for overdue invoices and `SELECT * FROM Invoice WHERE TxnDate >= '<first of last month>'` for what was billed. Xero, if that is your ledger: `Invoices` with `where=Type=="ACCREC" AND Status=="AUTHORISED"`. Match `CustomerRef` name or `Contact.Name` to the map after lowercasing and dropping `inc`, `llc` and `ltd`; days overdue is today minus `DueDate`.
5. **Join, flag and rank**: One row per client. Flags: sessions or organic clicks down 10% or more; Ads conversions down 20% or more on similar spend; 60 or more minutes of downtime; no post in 60 days on a content retainer; an invoice 14 or more days overdue. Two flags or more is at risk. A mapped client with no invoice dated last month is "not billed"; a customer in the books missing from the map goes on an "unmapped" line.
## Route B: the browser
### What you need
- Signed in to Google with access to each client's GA4 property, Search Console site and Ads account, and to UptimeRobot, each client's wp-admin, and QuickBooks or Xero.
If something is missing, say what and stop; do not invent numbers.
### Steps
Start at https://analytics.google.com/analytics/web/. The user is signed in already; never type passwords or codes, and ask if a sign-in page appears.
1. **Read marketing per client**: For each client, pick its GA4 property, open Reports > Acquisition > Traffic acquisition, set the date to last month compared with the previous period, and note sessions and key events. In Search Console's Performance report, compare the same months for clicks. In Google Ads, open the client account and read conversions and cost for both months.
2. **Read downtime**: In `https://dashboard.uptimerobot.com/monitors`, open each client's monitor and read last month's uptime and incidents. Downtime minutes are (100 - uptime %) / 100 x minutes in the month.
3. **Check the site**: For WordPress sites, open wp-admin: Posts shows the last published date, and Dashboard > Updates shows plugins waiting for an update.
4. **Read billing**: In QuickBooks, open Sales > Invoices and filter Status `Overdue`; note each client's overdue amount and due date, and check every mapped client has an invoice dated last month. In Xero, use Sales > Invoices, Awaiting Payment, sorted by due date.
5. **Join and flag**: Put one row per client. Flag sessions or clicks down 10% or more, conversions down 20% or more, 60 or more minutes of downtime, no post in 60 days, and invoices 14 or more days overdue. Two flags or more is at risk.
Note: Clicking through five tools per client takes several minutes each, so the browser route suits an agency with a handful of clients; past ten, the API route is the practical one. GA4 and Search Console count visits differently, so their changes will not match exactly.
## The report
- **At risk**: clients with two or more warnings, each with the specifics.
- **All clients** in one table: traffic change, organic clicks change, downtime, site upkeep and invoice status.
- **Billing gaps**: mapped clients not billed last month, and customers in the books missing from the map.
- One line on who to call first and what to say, for example "Call Acme before chasing the invoice: the outage on Sep 18 came first".
## Post it to Briefkin
When `BRIEFKIN_TOKEN` is set, or `~/.config/briefkin/env` exists (the installer from https://briefkin.dev/docs writes it), also send the result to the user's Briefkin board as one card. Keep the `item_key` the same on every run so the card updates in place, use a new `event_id` per run, and put today's real numbers in place of the example ones. If the request fails, say so once and carry on.
```bash
[ -n "${BRIEFKIN_TOKEN:-}" ] || . ~/.config/briefkin/env
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
-H "authorization: Bearer $BRIEFKIN_TOKEN" \
-H "content-type: application/json" \
-d @- <<'JSON'
{
"v": 1,
"event_id": "agency-client-health/<date of this run, e.g. 2026-10-16>",
"item_key": "agency-client-health",
"project": "Acme Agency",
"source": "Client health in one card: marketing, uptime and invoices per client skill",
"kind": "update",
"title": "Acme at risk: traffic down 12%, 2 hours down, invoice 18 days overdue",
"summary": "**1 of 6 clients** is at risk. **Acme**: traffic down 12%, 2 hours of downtime, invoice 18 days overdue. **Harbor Dental** was not billed for September.",
"widget": [
{
"type": "Table",
"props": {
"columns": [
"Client",
"Traffic",
"Organic clicks",
"Downtime",
"Invoice",
"Status"
],
"align": [
"left",
"right",
"right",
"right",
"left",
"left"
],
"rows": [
[
"Acme",
"-12%",
"-9%",
"2 h",
"18 days overdue",
"At risk"
],
[
"Harbor Dental",
"+4%",
"+6%",
"0 min",
"Not billed for Sep",
"Check"
],
[
"Bluebird Bakery",
"-6%",
"-3%",
"0 min",
"Due in 5 days",
"OK"
],
[
"Summit Legal",
"+2%",
"-4%",
"38 min",
"Paid",
"OK"
],
[
"Northwind Foods",
"+8%",
"+11%",
"12 min",
"Paid",
"OK"
],
[
"Cedar Clinics",
"+15%",
"+9%",
"5 min",
"Paid",
"OK"
]
]
}
},
{
"type": "Callout",
"props": {
"text": "Acme's downtime was one outage on Sep 18 (1 h 52 min) and two short ones, and its traffic fell the same week. Call before chasing the invoice.",
"tone": "warn"
}
}
]
}
JSON
```
Widgets come from the Briefkin catalog (https://briefkin.dev/v1/catalog). Without a token, just give the user the report.
## Run it on a schedule
Suggested: 3rd of the month at 10:00 (`0 10 3 * *`). From cron, with the agent signed in on that machine:
```bash
0 10 3 * * claude -p "Run the agency-client-health skill"
# Codex: codex exec, Gemini CLI: gemini -p, Copilot CLI: copilot -p, OpenCode: opencode run
```
Or use the agent's own scheduler (Claude Code routines, Codex automations, Claude or ChatGPT scheduled tasks): https://briefkin.dev/skills/agency-client-health#schedule
Source: https://briefkin.dev/skills/agency-client-health
A client map in settings with each client's GA4 property, Search Console property, Ads customer ID, UptimeRobot monitor, site URL and customer name in the books, spelled as the books spell it.
Viewer access to each GA4 property and Search Console site, Ads access through your manager account, a read-only UptimeRobot key, and read access to QuickBooks or Xero invoices. WP-CLI only for plugin update counts.
analytics-mcp on PyPI, source). It runs locally with pipx run analytics-mcp, offers run_report, run_realtime_report and get_account_summaries, and Google labels it experimental.GOOGLE_APPLICATION_CREDENTIALS at its JSON key. The skill also reads GA4_PROPERTY_ID.gcloud auth application-default login --scopes https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/cloud-platform --client-id-file=client.json gives the MCP server or a script your user's access without a key file.Request only analytics.readonly and keep the service account at Viewer: none of these reports needs Editor.
GOOGLE_APPLICATION_CREDENTIALS at the key and set GSC_SITE_URL to sc-domain:example.com or https://www.example.com/.webmasters.readonly scope and a stored refresh token works for scheduled runs.Ask for webmasters.readonly and add the service account as a Restricted user; the reports only read.
google-ads-mcp, source). Run it locally with pipx run --spec git+https://github.com/googleads/google-ads-mcp.git google-ads-mcp, or deploy it to Cloud Run for remote clients. Its tools are list_accessible_customers, search for GAQL and get_resource_metadata (docs).gcloud auth application-default login with the adwords scope) or an OAuth client, and set GOOGLE_PROJECT_ID. Add GOOGLE_ADS_LOGIN_CUSTOMER_ID when you reach client accounts through a manager account, and write customer IDs without dashes.The adwords scope can't be narrowed, so sign in as a Google Ads user with Read only access; the MCP server has no write tools either way.
https://mcp.uptimerobot.com/mcp. Sign in with your UptimeRobot account and choose read-only or full access, or send an API key as a Bearer token. In Claude Code: claude mcp add --transport http uptimerobot https://mcp.uptimerobot.com/mcp, adding --header "Authorization: Bearer <read-only key>" to use a key. UptimeRobot also publishes ready-made plugins for Claude Code, Cursor and Codex at github.com/uptimerobot/ai (docs).UPTIMEROBOT_API_KEY, and call getMonitors. The free plan allows 10 requests a minute and paid plans allow more.A read-only key covers listing monitors, stats, incidents and integrations; creating, pausing or editing monitors needs the main API key, which a reporting skill should never hold.
https://public-api.wordpress.com/wpcom/v2/mcp/v1. Turn on MCP access under Preferences > AI and MCP in your WordPress.com account, then sign in with OAuth 2.1 and choose Read only on the authorization page. WordPress.com is in Claude's connector directory, and the URL works in any MCP client, such as Claude Code with claude mcp add --transport http wordpress-com https://public-api.wordpress.com/wpcom/v2/mcp/v1. It's included on paid plans; free sites get it for their first 30 days (docs)./wp-json/mcp/mcp-adapter-default-server. Connect with @automattic/mcp-wordpress-remote and an Application Password (WP_API_URL, WP_API_USERNAME, WP_API_PASSWORD), or locally through WP-CLI with wp mcp-adapter serve --server=mcp-adapter-default-server --user=<user>. Only abilities a plugin marks as public are exposed.WP_URL, WP_USERNAME and WP_APP_PASSWORD; requests use basic auth over HTTPS. Published posts need no authentication at all.Run reports as a dedicated user with the lowest role that sees what they need: Editor for other people's drafts and comment moderation, Administrator only if the report lists plugins and updates.
QUICKBOOKS_CLIENT_ID, QUICKBOOKS_CLIENT_SECRET, QUICKBOOKS_REFRESH_TOKEN, QUICKBOOKS_REALM_ID and QUICKBOOKS_ENVIRONMENT.com.intuit.quickbooks.accounting scope, and store the same five values. Access tokens last an hour and the refresh token can change on each refresh, so the skill must save the newest one.The accounting scope has no read-only version; with Intuit's MCP server, set QUICKBOOKS_DISABLE_WRITE, QUICKBOOKS_DISABLE_UPDATE and QUICKBOOKS_DISABLE_DELETE to true so the agent gets only read, search and report tools.
https://mcp.xero.com/mcp, in Claude's connector directory. It connects with read-only access and has tools for profit and loss, financial and cash position, aged receivables and payables, invoices, bills, contacts and bank transactions. Connectors added in claude.ai also appear in Claude Code when you sign in with that account.@xeroapi/xero-mcp-server, runs with a custom connection's client ID and secret or with a bearer token: claude mcp add --env XERO_CLIENT_ID=<id> --env XERO_CLIENT_SECRET=<secret> --transport stdio xero -- npx -y @xeroapi/xero-mcp-server@latest, and the same command and variables in Codex or Cursor. It also has tools that create invoices, payments and journals, so set XERO_SCOPES to read scopes only (source).XERO_CLIENT_ID and XERO_CLIENT_SECRET and request granular read scopes such as accounting.invoices.read, accounting.banktransactions.read, accounting.reports.profitandloss.read, accounting.reports.aged.read and accounting.contacts.read; custom connections created since April 29, 2026 use these granular scopes.The Claude connector is read-only already. On the API, request only .read scopes: a token without write scopes can't raise an invoice or record a payment.
The billing step matches on the customer name after lowercasing and dropping company suffixes, so "Acme, Inc." and "acme inc" meet. Anything still unmatched shows up twice: the client as "not billed" and the books customer as "unmapped". Copy the name exactly as the books spell it into the map and the next run joins them.
Search Console data lags about two days, GA4 settles within a couple of days, and invoices dated the 1st have gone out by then. Downtime and billing are complete for the month at any point after it ends, so the 3rd is the earliest day all sources agree on the same month.
No. The skill runs with your own agent and gives you the report in the chat. With a Briefkin token set, each run also posts it to your board as a report card, so you can glance at it instead of asking again.
Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot CLI, OpenCode, Windsurf and Amp all read SKILL.md files. The install command puts it in ~/.claude/skills and ~/.agents/skills, where they look. ChatGPT, the Claude app and Grok take the prompt instead. Pick your agent under "Install and run it" and the commands change to match.
Yes. Switch the data source to "Your browser" and the steps change to the Google Analytics, Google Search Console, Google Ads, UptimeRobot, WordPress, QuickBooks and Xero pages themselves: the agent opens analytics.google.com/analytics/web in a browser you are signed in to and reads the numbers off the page. Claude Code, Codex, Cursor, Claude app and ChatGPT can drive a browser out of the box; Gemini CLI, Copilot CLI and OpenCode need a Playwright or Chrome DevTools MCP server first. It is slower than the API and needs you signed in, so it suits a report you run by hand or from a scheduler on your own computer.
Skills are free to use with any agent. Briefkin is where their results land: one board for every report, number and question your agents send, with a ping when something needs you.
Free during the open beta. No card.