Report your Google Sheets KPIs against target every Friday

Fridays: each KPI in your tracking sheet against its target, which ones are behind, which moved since last week, and which rows nobody updated.

Runs
Fridays at 15:00
Posts
Stat tile

What lands on your board

AcmeKPIs on target7of 10-13% vs previous period, worsevs last report
Activation 31% vs 40%NPS 34 vs 40Weekly signups 412 vs 450
An example with made-up numbers. Your agent posts your real ones.

What it does

Plenty of companies run on a KPI sheet: one row per metric, an owner, a target and this week's actual, typed in by whoever owns it. The sheet is the source of truth, and reading it properly every week (which rows are green, which slipped, which nobody touched) is the part that slides.

The agent reads the sheet as it is, judges each KPI against its own target and direction, and posts the count on target with the misses spelled out. Stale rows get called out instead of being judged on last month's number.

How it works

  1. 1Read the KPI tab

    Call spreadsheets.values.get on KPIs!A1:G50 with valueRenderOption=UNFORMATTED_VALUE, so 31% comes back as 0.31. Use the first row as headers: KPI, Owner, Target, Direction, Actual, Last week, Updated.

  2. 2

    2Check each row is usable

    Skip rows with an empty Actual. If Updated is more than 8 days old, mark the KPI as stale rather than judging it on an old number.

  3. 3

    3Judge against target

    With Direction higher, on target means Actual at or above Target; with lower, at or below. A miss within 10% of target is close, anything further is behind. Compare Actual with Last week for the trend.

  4. 4

    4Write the report

    KPIs on target out of the total, then each miss with owner, target, actual and the change since last week, then stale rows grouped by owner.

  5. Post it to the board

    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.

What you get

  • KPIs on target out of the total, and the change since last week.
  • Each KPI that is behind or close: owner, target, actual, trend.
  • KPIs whose row hasn't been updated in over a week, by owner.
  • The one KPI to discuss first, usually the biggest miss that is also getting worse.

Install and run it

Pick your agent, where the data comes from and how to schedule it; the commands below follow.

Agent
Data
Schedule
  1. Install the skill

    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/google-sheets-kpi-tracker/install | sh
  2. Run it

    Ask Claude Code:

    promptRun the google-sheets-kpi-tracker skill

    Or paste the prompt instead of installing:

    promptRead the KPIs tab of my Google Sheet (spreadsheet ID below) with unformatted values. Each row has a KPI, owner, target, direction (higher or lower is better), actual, last week's value and the date it was updated. For each KPI with an actual, say whether it is on target, close (within 10%) or behind, respecting the direction, and whether it improved since last week. Flag rows not updated in more than 8 days as stale instead of judging them. Give me the count on target out of the total, the misses with owner, target, actual and trend, the stale rows by owner, and the one KPI to discuss first.
  3. Put it on a schedule

    Fridays at 15:00, from cron on any machine where Claude Code is signed in:

    crontab0 15 * * 5 claude -p "Run the google-sheets-kpi-tracker 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.

  4. Send it to your board

    Connect Briefkin once with curl -fsSL https://briefkin.dev/install | sh and every run lands on your board as a stat tile. How setup works

View the SKILL.mdRaw file
SKILL.md---
name: google-sheets-kpi-tracker
description: "Fridays: each KPI in your tracking sheet against its target, which ones are behind, which moved since last week, and which rows nobody updated. Use when the user asks for this, or when it runs on a schedule."
---

# Google Sheets KPI tracker report

Fridays: each KPI in your tracking sheet against its target, which ones are behind, which moved since last week, and which rows nobody updated.

Plenty of companies run on a KPI sheet: one row per metric, an owner, a target and this week's actual, typed in by whoever owns it. The sheet is the source of truth, and reading it properly every week (which rows are green, which slipped, which nobody touched) is the part that slides.

The agent reads the sheet as it is, judges each KPI against its own target and direction, and posts the count on target with the misses spelled out. Stale rows get called out instead of being judged on last month's number.

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

- Read access to the spreadsheet: share it with the agent's Google account or service account (Viewer is enough), and give the agent the spreadsheet ID from its URL.
- A Google Sheets connector or MCP server, or the Sheets API with `GOOGLE_APPLICATION_CREDENTIALS` pointing at the service account key.

If something is missing, say what and stop; do not invent numbers.

### Steps

1. **Read the KPI tab** (Google Sheets): Call `spreadsheets.values.get` on `KPIs!A1:G50` with `valueRenderOption=UNFORMATTED_VALUE`, so 31% comes back as `0.31`. Use the first row as headers: KPI, Owner, Target, Direction, Actual, Last week, Updated.
2. **Check each row is usable**: Skip rows with an empty Actual. If Updated is more than 8 days old, mark the KPI as stale rather than judging it on an old number.
3. **Judge against target**: With Direction `higher`, on target means Actual at or above Target; with `lower`, at or below. A miss within 10% of target is close, anything further is behind. Compare Actual with Last week for the trend.
4. **Write the report**: KPIs on target out of the total, then each miss with owner, target, actual and the change since last week, then stale rows grouped by owner.

## Route B: the browser

### What you need

- Signed in to Google with an account that has at least Viewer access to the spreadsheet.

If something is missing, say what and stop; do not invent numbers.

### Steps

Start at https://docs.google.com/spreadsheets/. The user is signed in already; never type passwords or codes, and ask if a sign-in page appears.

1. **Open the KPI sheet**: Open `https://docs.google.com/spreadsheets/`, open the KPI tracker (or paste its URL), and click the **KPIs** tab at the bottom. Read rows 1 to 50 of columns A to G: KPI, Owner, Target, Direction, Actual, Last week, Updated.
2. **Check each row is usable**: Skip rows with an empty Actual. Compare the Updated date with today; more than 8 days old is stale and gets reported rather than judged. Cells show formatted values, so `31%` reads as a percentage; click a cell and read the formula bar when a number is ambiguous.
3. **Judge against target**: With Direction `higher`, on target means Actual at or above Target; with `lower`, at or below. A miss within 10% of target is close, further is behind. Compare Actual with Last week for the trend.
4. **Write the report**: KPIs on target out of the total, each miss with owner, target, actual and trend, the stale rows grouped by owner, and the one KPI to raise first. Do not type into the sheet.

Note: The sheet shows formatted values, so a target of `≥ 40%` written as text and an actual of `31%` need reading as numbers. On a very wide sheet, File > Download > Comma-separated values gives the agent the whole tab at once.

## The report

- **KPIs on target** out of the total, and the change since last week.
- Each KPI that is behind or close: owner, target, actual, trend.
- KPIs whose row hasn't been updated in over a week, by owner.
- The one KPI to discuss first, usually the biggest miss that is also getting worse.

## 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": "google-sheets-kpi-tracker/<date of this run, e.g. 2026-10-16>",
  "item_key": "google-sheets-kpi-tracker",
  "project": "Acme",
  "source": "Google Sheets KPI tracker report skill",
  "kind": "metric",
  "title": "7 of 10 KPIs on target, activation furthest behind",
  "metric": {
    "label": "KPIs on target",
    "value": 7,
    "unit": "of 10",
    "better": "higher",
    "period_start": "<start of the period, ISO 8601>",
    "period_end": "<end of the period, ISO 8601>"
  },
  "glance": {
    "type": "Badges",
    "props": {
      "items": [
        {
          "label": "Activation 31% vs 40%",
          "tone": "bad"
        },
        {
          "label": "NPS 34 vs 40",
          "tone": "bad"
        },
        {
          "label": "Weekly signups 412 vs 450",
          "tone": "warn"
        }
      ]
    }
  },
  "widget": [
    {
      "type": "Table",
      "props": {
        "columns": [
          "KPI",
          "Owner",
          "Target",
          "Actual",
          "Status"
        ],
        "rows": [
          [
            "Activation rate",
            "Priya",
            "≥ 40%",
            "31%",
            "Behind"
          ],
          [
            "NPS",
            "Sam",
            "≥ 40",
            "34",
            "Behind"
          ],
          [
            "Weekly signups",
            "Maya",
            "≥ 450",
            "412",
            "Close"
          ],
          [
            "MRR",
            "Alex",
            "≥ $45,000",
            "$48,210",
            "On target"
          ],
          [
            "Trial conversion",
            "Maya",
            "≥ 35%",
            "42%",
            "On target"
          ],
          [
            "Logo churn",
            "Alex",
            "≤ 3%",
            "2.4%",
            "On target"
          ],
          [
            "First response time",
            "Sam",
            "≤ 4 h",
            "2.6 h",
            "On target"
          ],
          [
            "Uptime",
            "Jordan",
            "≥ 99.9%",
            "99.95%",
            "On target"
          ],
          [
            "CAC payback",
            "Alex",
            "≤ 12 mo",
            "9 mo",
            "On target"
          ],
          [
            "Burn multiple",
            "Alex",
            "≤ 2.0",
            "1.7",
            "On target"
          ]
        ]
      }
    },
    {
      "type": "Callout",
      "props": {
        "text": "Activation fell from 34% to 31% this week and is the biggest miss. Start Monday's meeting there.",
        "tone": "warn"
      }
    }
  ],
  "size": "md"
}
JSON
```

Widgets come from the Briefkin catalog (https://briefkin.dev/v1/catalog). Without a token, just give the user the report.

Set `period_start` and `period_end` to the window the number covers: the tile names it from them ("Last 7 days", "Today", "This month"). For a count right now (open items, a balance), send `"period_label": ""` so no period shows; for another name, send it as `period_label` (at most 24 characters).

## Run it on a schedule

Suggested: Fridays at 15:00 (`0 15 * * 5`). From cron, with the agent signed in on that machine:

```bash
0 15 * * 5 claude -p "Run the google-sheets-kpi-tracker 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/google-sheets-kpi-tracker#schedule

Source: https://briefkin.dev/skills/google-sheets-kpi-tracker

What your agent needs

  • Read access to the spreadsheet: share it with the agent's Google account or service account (Viewer is enough), and give the agent the spreadsheet ID from its URL.

  • A Google Sheets connector or MCP server, or the Sheets API with GOOGLE_APPLICATION_CREDENTIALS pointing at the service account key.

Connecting Google Sheets

  1. Google Sheets MCP server, Google's remote server at https://sheetsmcp.googleapis.com/mcp/v1, part of the Google Workspace MCP servers in developer preview, with read-only spreadsheets.readonly and drive.readonly scopes available (setup).
  2. Claude's Google Drive connector searches and opens files in your Drive, which is enough to find a sheet and read it in a chat.
  3. Sheets API with a service account. Enable the Google Sheets API, create a service account, share the spreadsheet with its email, and set GOOGLE_APPLICATION_CREDENTIALS and SHEET_ID (the long ID in the sheet's URL). Use the spreadsheets.readonly scope unless the skill appends rows.

Share the sheet with the service account as Viewer unless the skill writes; it can only open files someone shared with it, so the rest of your Drive stays out of reach.

Questions

Does my sheet need exactly these columns?

No. Tell the agent the tab, the range and which column holds what; it reads the header row and maps them. The only must-have is a target and a direction per KPI, or it can't tell a miss from a win.

Why do percentages come back as decimals?

With UNFORMATTED_VALUE the API returns the number behind the cell, so 31% arrives as 0.31. That avoids parsing "31%" or "$48,210" as text. The agent reads the cell format, or makes a second call with FORMATTED_VALUE, to show values the way the sheet does.

Do I need Briefkin to use the Google Sheets KPI tracker report skill?

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 stat tile, so you can glance at it instead of asking again.

Which agents can run it?

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.

Can it work without an API or MCP server, from my browser?

Yes. Switch the data source to "Your browser" and the steps change to the Google Sheets pages themselves: the agent opens docs.google.com/spreadsheets 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.

Watch kpis on target on your board, run after run

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.