Append yesterday's revenue, signups and traffic to a Google Sheet every morning

Every morning: one new row in your metrics sheet with yesterday's Stripe revenue, GA4 signups and sessions, and a note on how the day compares.

Runs
Every day at 6:30
Posts
Report card

From your apps to your board

How the skill works: your agent reads date isn't there yet from Google Sheets; total yesterday's revenue from Stripe; sessions and signups from Google Analytics; spreadsheet id, ga4 property id from What you tell it, every day at 6:30, and gives you report card and the report in the chat.

Reads

  • Google SheetsDate isn't there yet
  • StripeTotal yesterday's revenue
  • Google AnalyticsSessions and signups
  • What you tell itSpreadsheet ID, GA4 property ID
reads
Your agentClaude Code, Codex, Cursor or GeminiEvery day at 6:30
posts

You get

  • Report cardOn your board, updated in place every run
  • The report in the chatEvery run, with or without Briefkin

What you get

  • The row added: date, revenue, signups, sessions, users.
  • Where it went: tab and row number.
  • Each value against the 7-day average from the sheet.
  • Any day it had to backfill, or a source it couldn't reach.

The card on the right is a sample. Yours shows your own numbers and updates in place every run.

What lands on your board

AcmeGoogle Sheets daily metrics log · Claude Code

Oct 15 logged: $3,212 revenue, 41 signups

Added row 289 to the Daily tab. Revenue and signups are above the 7-day average; sessions are flat.

Date
2026-10-15
Revenue
$3,212 (7-day average $2,980)
Signups
41 (average 34)
Sessions
2,876 (average 2,851)
Users
2,204
Written to
Daily!A289:F289
Revenue, last 8 days$3,212+2% vs previous period, better
Done
An example with made-up numbers. Your agent posts your real ones.

What it does

A daily metrics sheet is the cheapest dashboard there is, and it only works if someone fills in yesterday's row before coffee, every day. Miss a week and the charts built on it have a hole nobody wants to backfill.

This skill writes the row for you: revenue from Stripe, signups and sessions from Google Analytics, appended under the last row with the date in column A. It checks the date first, so a rerun never writes the same day twice, and it fills in days it missed.

How it works

  1. 1Total yesterday's revenue

    List GET /v1/charges with created[gte] and created[lt] for yesterday in your timezone and add up amount_captured minus amount_refunded for charges with status: succeeded, in the account's default currency.

  2. 2Pull sessions and signups

    Call runReport with dateRanges: [{ startDate: "yesterday", endDate: "yesterday" }] and metrics sessions and totalUsers, then eventCount with a dimensionFilter on eventName equal to sign_up (or whichever key event marks a signup).

  3. 3Check the date isn't there yet

    Read Daily!A:A with spreadsheets.values.get. If yesterday is already in column A, stop; if the last date is older than the day before yesterday, queue the missing days too, oldest first.

  4. 4Append the row

    Call spreadsheets.values.append on Daily!A:F with valueInputOption=USER_ENTERED and insertDataOption=INSERT_ROWS, values [date, revenue, signups, sessions, users, "agent"], and read the row number from updates.updatedRange.

  5. 5

    5Report the row

    The values written, the row number, and how each compares with the average of the 7 rows above it.

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

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-daily-metrics-log/install | sh
  2. Run it

    Ask Claude Code:

    promptRun the google-sheets-daily-metrics-log skill

    Or paste the prompt instead of installing:

    promptEvery morning, add yesterday to my metrics log in Google Sheets (spreadsheet ID and the tab "Daily" below). Revenue: from Stripe, the sum of amount captured minus amount refunded for yesterday's succeeded charges, in my timezone. Sessions and users: a GA4 runReport for yesterday. Signups: the eventCount of the sign_up event in GA4. First read column A; if yesterday is already there, stop, and if earlier days are missing, add them too, oldest first. Append with values.append, USER_ENTERED and INSERT_ROWS, as [date, revenue, signups, sessions, users, "agent"]. Then tell me the row number and how each value compares with the average of the 7 rows above.
  3. Put it on a schedule

    Every day at 6:30, from cron on any machine where Claude Code is signed in:

    crontab30 6 * * * claude -p "Run the google-sheets-daily-metrics-log 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 report card. How setup works

View the SKILL.mdRaw file
SKILL.md---
name: google-sheets-daily-metrics-log
description: "Every morning: one new row in your metrics sheet with yesterday's Stripe revenue, GA4 signups and sessions, and a note on how the day compares. Use when the user asks for this, or when it runs on a schedule."
---

# Google Sheets daily metrics log

Every morning: one new row in your metrics sheet with yesterday's Stripe revenue, GA4 signups and sessions, and a note on how the day compares.

A daily metrics sheet is the cheapest dashboard there is, and it only works if someone fills in yesterday's row before coffee, every day. Miss a week and the charts built on it have a hole nobody wants to backfill.

This skill writes the row for you: revenue from Stripe, signups and sessions from Google Analytics, appended under the last row with the date in column A. It checks the date first, so a rerun never writes the same day twice, and it fills in days it missed.

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

- Edit access to the spreadsheet for the agent's Google account or service account, and the spreadsheet ID.
- Read access to Stripe (a restricted key in `STRIPE_API_KEY` or the Stripe MCP server) and to the GA4 property (Viewer, through a Google Analytics MCP server or a service account).

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

### Steps

1. **Total yesterday's revenue** (Stripe): List `GET /v1/charges` with `created[gte]` and `created[lt]` for yesterday in your timezone and add up `amount_captured` minus `amount_refunded` for charges with `status: succeeded`, in the account's default currency.
2. **Pull sessions and signups** (Google Analytics): Call `runReport` with `dateRanges: [{ startDate: "yesterday", endDate: "yesterday" }]` and metrics `sessions` and `totalUsers`, then `eventCount` with a `dimensionFilter` on `eventName` equal to `sign_up` (or whichever key event marks a signup).
3. **Check the date isn't there yet** (Google Sheets): Read `Daily!A:A` with `spreadsheets.values.get`. If yesterday is already in column A, stop; if the last date is older than the day before yesterday, queue the missing days too, oldest first.
4. **Append the row** (Google Sheets): Call `spreadsheets.values.append` on `Daily!A:F` with `valueInputOption=USER_ENTERED` and `insertDataOption=INSERT_ROWS`, values `[date, revenue, signups, sessions, users, "agent"]`, and read the row number from `updates.updatedRange`.
5. **Report the row**: The values written, the row number, and how each compares with the average of the 7 rows above it.

## Route B: the browser

### What you need

- Signed in to Stripe with the Analyst role or higher, and to Google with an account that has Viewer access to the GA4 property and Editor access to the spreadsheet.

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

### Steps

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

1. **Total yesterday's revenue in Stripe**: Open `https://dashboard.stripe.com/payments` in live mode, set the **Date** filter to yesterday in your time zone and the **Succeeded** view, and total the **Amount** column (click **Export** when the list runs past a page). Switch to the **Refunded** view for the same day and subtract the refunded amounts.
2. **Read sessions and users in GA4**: Open `https://analytics.google.com/`, pick the property, go to **Reports** > **Acquisition** > **Traffic acquisition**, set the date picker to **Yesterday** and read the **Sessions** and **Total users** totals at the top of the table.
3. **Count signups**: In the same property go to **Reports** > **Engagement** > **Events**, keep the date on **Yesterday**, find the `sign_up` row (or whichever event marks a signup) and read its **Event count**.
4. **Check the sheet for the date**: Open the metrics spreadsheet from `https://docs.google.com/spreadsheets/`, click the **Daily** tab and jump to the last row of column A (Ctrl+Down on the column). If yesterday is already there, stop. If the last date is older than the day before yesterday, note the missing days to fill first, oldest first.
5. **Type the row**: In the first empty row type date, revenue, signups, sessions, users and `agent` across columns A to F, in the date format the column already uses, and press Enter after the last cell. Then report the row number and how each value compares with the average of the 7 rows above.

Note: GA4 standard reports can take 24 to 48 hours to finalize, so yesterday's sessions may rise a little after the row is written; the API route has the same lag. Stripe Payments amounts are in the charge currency, not the settlement currency. Typing into a sheet through a browser agent is fragile: check the row after it is written.

## The report

- The row added: date, revenue, signups, sessions, users.
- Where it went: tab and row number.
- Each value against the 7-day average from the sheet.
- Any day it had to backfill, or a source it couldn't reach.

## 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-daily-metrics-log/<date of this run, e.g. 2026-10-16>",
  "item_key": "google-sheets-daily-metrics-log",
  "project": "Acme",
  "source": "Google Sheets daily metrics log skill",
  "kind": "update",
  "title": "Oct 15 logged: $3,212 revenue, 41 signups",
  "summary": "Added row **289** to the Daily tab. Revenue and signups are above the 7-day average; sessions are flat.",
  "widget": [
    {
      "type": "KeyValue",
      "props": {
        "items": [
          {
            "key": "Date",
            "value": "2026-10-15"
          },
          {
            "key": "Revenue",
            "value": "$3,212 (7-day average $2,980)"
          },
          {
            "key": "Signups",
            "value": "41 (average 34)"
          },
          {
            "key": "Sessions",
            "value": "2,876 (average 2,851)"
          },
          {
            "key": "Users",
            "value": "2,204"
          },
          {
            "key": "Written to",
            "value": "Daily!A289:F289"
          }
        ]
      }
    },
    {
      "type": "Sparkline",
      "props": {
        "values": [
          3010,
          3150,
          2710,
          3050,
          2890,
          2900,
          3150,
          3212
        ],
        "label": "Revenue, last 8 days",
        "prefix": "$"
      }
    }
  ]
}
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: Every day at 6:30 (`30 6 * * *`). From cron, with the agent signed in on that machine:

```bash
30 6 * * * claude -p "Run the google-sheets-daily-metrics-log 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-daily-metrics-log#schedule

Source: https://briefkin.dev/skills/google-sheets-daily-metrics-log

What your agent needs

  • Edit access to the spreadsheet for the agent's Google account or service account, and the spreadsheet ID.

  • Read access to Stripe (a restricted key in STRIPE_API_KEY or the Stripe MCP server) and to the GA4 property (Viewer, through a Google Analytics MCP server or a service account).

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.

Connecting Stripe

  1. Stripe MCP server, Stripe's official remote server at https://mcp.stripe.com. Add Stripe from Claude's connector directory, or run claude mcp add --transport http stripe https://mcp.stripe.com/, and sign in with OAuth; clients without OAuth can send a restricted key as a Bearer token.
  2. The Stripe CLI. Run stripe login once, then stripe subscriptions list --status active --limit 100 or stripe invoices list --status open. The CLI defaults to test mode, so add --live for real data.
  3. API with a restricted key. Create a restricted key (rk_live_...) in the Dashboard under Developers > API keys, store it in STRIPE_API_KEY, and give it Read on the resources the report touches: Subscriptions, Invoices, Charges, Customers, PaymentIntents, Balance and Disputes.

Never hand an agent the secret key (sk_live_...); a restricted key with Read where needed and None everywhere else can't refund, cancel or charge anything.

Connecting Google Analytics

  1. Google Analytics MCP server, Google's own open-source server (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.
  2. Data API with a service account. Enable the Google Analytics Data API in a Google Cloud project, create a service account, add its email as a Viewer on the GA4 property, and point GOOGLE_APPLICATION_CREDENTIALS at its JSON key. The skill also reads GA4_PROPERTY_ID.
  3. Your own Google login. 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.

Questions

What happens if the job misses a day?

The next run reads the last date in column A, sees the gap and appends the missing days one row each, oldest first, before yesterday's. Rows stay in date order and nothing gets written twice.

Why `USER_ENTERED` and not `RAW`?

With USER_ENTERED, Sheets parses the values as if they were typed, so the date is a real date and the numbers are numbers, and the charts, pivot tables and formulas on the tab keep working. RAW would store the date as text.

Do I need Briefkin to use the Google Sheets daily metrics log 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 report card, 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, Stripe and Google Analytics pages themselves: the agent opens dashboard.stripe.com/payments 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.

Read this report on your board, not in a chat log

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.