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
1Read the KPI tab
Call
spreadsheets.values.getonKPIs!A1:G50withvalueRenderOption=UNFORMATTED_VALUE, so 31% comes back as0.31. Use the first row as headers: KPI, Owner, Target, Direction, Actual, Last week, Updated.- 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
3Judge against target
With Direction
higher, on target means Actual at or above Target; withlower, at or below. A miss within 10% of target is close, anything further is behind. Compare Actual with Last week for the trend. - 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.
Post it to the board
With
BRIEFKIN_TOKENset, 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.
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.
shell
curl -fsSL https://briefkin.dev/skills/google-sheets-kpi-tracker/install | shRun it
Ask Claude Code:
prompt
Run the google-sheets-kpi-tracker skillOr paste the prompt instead of installing:
prompt
Read 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.Put it on a schedule
Fridays at 15:00, from cron on any machine where Claude Code is signed in:
crontab
0 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.Send it to your board
Connect Briefkin once with
curl -fsSL https://briefkin.dev/install | shand 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_CREDENTIALSpointing at the service account key.
Connecting Google Sheets
- 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-onlyspreadsheets.readonlyanddrive.readonlyscopes available (setup). - 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.
- 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_CREDENTIALSandSHEET_ID(the long ID in the sheet's URL). Use thespreadsheets.readonlyscope 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.