---
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
