Snapshot your HubSpot pipeline weekly in Google Sheets using an agent skill
Prerequisites
- Claude Code, Cursor, or another AI coding agent that supports skills
- HubSpot private app token stored as an environment variable (
HUBSPOT_TOKEN) - Google Cloud service account JSON key file stored as an environment variable (
GOOGLE_SERVICE_ACCOUNT_KEY) - Google Spreadsheet ID stored as an environment variable (
GOOGLE_SPREADSHEET_ID) - The service account email shared as an Editor on the target Google Sheet
Overview
Instead of building a persistent automation, you can create an agent skill — a reusable instruction set that tells your AI coding agent how to snapshot the pipeline from HubSpot and append a row to Google Sheets on demand. This works with Claude Code, and the open Agent Skills standard means the same skill can work across compatible tools.
This approach is ideal for teams that want to capture snapshots on demand — before a board meeting, after a big push, or at the end of a quarter — without waiting for a weekly schedule.
Step 1: Prepare the Google Sheet
Create a new Google Sheet and add headers in the first row:
Date | Total Value | Deal Count | Appointment Scheduled (Count) | Appointment Scheduled (Value) | Qualified (Count) | Qualified (Value) | Proposal (Count) | Proposal (Value) | Closed Won (Count) | Closed Won (Value)Share the sheet with your Google service account email as an Editor.
Step 2: Create the skill directory
Set up a skill directory in your project:
mkdir -p .claude/skills/pipeline-snapshot/scriptsStep 3: Write the SKILL.md file
Create .claude/skills/pipeline-snapshot/SKILL.md:
---
name: pipeline-snapshot
description: Captures a snapshot of the HubSpot pipeline and appends a summary row to Google Sheets. Tracks date, total value, deal count, and per-stage breakdowns.
disable-model-invocation: true
allowed-tools: Bash(python *)
---
Capture a pipeline snapshot by running the bundled script:
1. Run: `python $SKILL_DIR/scripts/snapshot.py`
2. Review the output for any errors
3. Confirm the row was appended to Google SheetsKey configuration:
disable-model-invocation: true— this skill has external side effects (writing to Google Sheets), so only you can trigger it with/pipeline-snapshotallowed-tools: Bash(python *)— restricts the skill to only running Python scripts
Step 4: Write the snapshot script
Create .claude/skills/pipeline-snapshot/scripts/snapshot.py:
#!/usr/bin/env python3
"""
Pipeline Snapshot: HubSpot → Google Sheets
Fetches all active deals, calculates summary metrics, appends a row.
"""
import os
import sys
from datetime import datetime, timezone
try:
import requests
from google.oauth2 import service_account
from googleapiclient.discovery import build
except ImportError:
os.system("pip install requests google-api-python-client google-auth -q")
import requests
from google.oauth2 import service_account
from googleapiclient.discovery import build
# --- Config ---
HUBSPOT_TOKEN = os.environ.get("HUBSPOT_TOKEN")
SPREADSHEET_ID = os.environ.get("GOOGLE_SPREADSHEET_ID")
GOOGLE_KEY_FILE = os.environ.get("GOOGLE_SERVICE_ACCOUNT_KEY")
if not all([HUBSPOT_TOKEN, SPREADSHEET_ID, GOOGLE_KEY_FILE]):
print("ERROR: Missing env vars: HUBSPOT_TOKEN, GOOGLE_SPREADSHEET_ID, GOOGLE_SERVICE_ACCOUNT_KEY")
sys.exit(1)
HEADERS = {"Authorization": f"Bearer {HUBSPOT_TOKEN}", "Content-Type": "application/json"}
# --- Fetch pipeline stages ---
print("Fetching pipeline stages...")
stages_resp = requests.get("https://api.hubapi.com/crm/v3/pipelines/deals", headers=HEADERS)
stages_resp.raise_for_status()
stage_map = {}
stage_order = []
for pipeline in stages_resp.json()["results"]:
for stage in pipeline["stages"]:
stage_map[stage["id"]] = stage["label"]
stage_order.append(stage["label"])
print(f"Stages: {', '.join(stage_order)}")
# --- Fetch all deals (paginated) ---
print("Fetching deals...")
all_deals = []
after = 0
while True:
resp = requests.post(
"https://api.hubapi.com/crm/v3/objects/deals/search",
headers=HEADERS,
json={
"filterGroups": [{"filters": [{"propertyName": "pipeline", "operator": "EQ", "value": "default"}]}],
"properties": ["dealname", "amount", "dealstage", "closedate", "createdate"],
"sorts": [{"propertyName": "amount", "direction": "DESCENDING"}],
"limit": 100,
"after": after,
},
)
resp.raise_for_status()
data = resp.json()
all_deals.extend(data["results"])
if "paging" in data and "next" in data["paging"]:
after = data["paging"]["next"]["after"]
else:
break
print(f"Found {len(all_deals)} deals")
# --- Calculate snapshot ---
total_value = 0
by_stage = {}
for deal in all_deals:
props = deal["properties"]
amount = float(props.get("amount") or 0)
total_value += amount
stage_id = props.get("dealstage", "unknown")
stage_name = stage_map.get(stage_id, stage_id)
if stage_name not in by_stage:
by_stage[stage_name] = {"count": 0, "value": 0}
by_stage[stage_name]["count"] += 1
by_stage[stage_name]["value"] += amount
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
row = [today, total_value, len(all_deals)]
for stage_name in stage_order:
data = by_stage.get(stage_name, {"count": 0, "value": 0})
row.append(data["count"])
row.append(data["value"])
# Print summary
print(f"\nSnapshot for {today}:")
print(f" Total pipeline: ${total_value:,.0f}")
print(f" Deal count: {len(all_deals)}")
for stage_name in stage_order:
data = by_stage.get(stage_name, {"count": 0, "value": 0})
print(f" {stage_name}: {data['count']} deals (${data['value']:,.0f})")
# --- Append to Google Sheets ---
print("\nAppending to Google Sheets...")
creds = service_account.Credentials.from_service_account_file(
GOOGLE_KEY_FILE, scopes=["https://www.googleapis.com/auth/spreadsheets"]
)
service = build("sheets", "v4", credentials=creds)
result = service.spreadsheets().values().append(
spreadsheetId=SPREADSHEET_ID,
range="Sheet1!A1",
valueInputOption="USER_ENTERED",
insertDataOption="INSERT_ROWS",
body={"values": [row]},
).execute()
updated = result.get("updates", {}).get("updatedRows", 0)
print(f"Done — appended {updated} row(s) to spreadsheet {SPREADSHEET_ID}")Step 5: Run the skill
Invoke it from your AI coding agent:
# Claude Code
/pipeline-snapshot
# Or run the script directly
python .claude/skills/pipeline-snapshot/scripts/snapshot.pyThe agent will execute the script. You'll see the snapshot summary printed to the console and a confirmation that the row was appended to Google Sheets.
Step 6: Schedule it (optional)
Option A: Claude Desktop Cowork (scheduled tasks)
If you use Claude Desktop (Cowork), you can schedule this as a recurring task:
- Open Cowork and go to the Schedule tab in the sidebar
- Click + New task
- Set the description to: "Run
/pipeline-snapshotto capture the weekly pipeline snapshot" - Set frequency to Weekly on Monday mornings
- Set the working folder to your project directory
Cowork scheduled tasks only run while your computer is awake and the Claude Desktop app is open. For reliable weekly snapshots, use cron or GitHub Actions instead.
Option B: Cron + CLI
# crontab -e
0 8 * * 1 cd /path/to/project && python .claude/skills/pipeline-snapshot/scripts/snapshot.pyOption C: GitHub Actions
name: Weekly Pipeline Snapshot
on:
schedule:
- cron: '0 13 * * 1'
workflow_dispatch: {}
jobs:
snapshot:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install requests google-api-python-client google-auth
- run: python .claude/skills/pipeline-snapshot/scripts/snapshot.py
env:
HUBSPOT_TOKEN: ${{ secrets.HUBSPOT_TOKEN }}
GOOGLE_SPREADSHEET_ID: ${{ secrets.GOOGLE_SPREADSHEET_ID }}
GOOGLE_SERVICE_ACCOUNT_KEY: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_KEY }}When to use this approach
- You want a snapshot right now — before a board meeting or end-of-quarter review
- You're iterating on what to track — just edit the script and re-run
- You want ad-hoc snapshots — "snapshot just the Enterprise pipeline" or "snapshot deals created this month only"
- You want the snapshot logic version-controlled alongside your code
- You're already using an AI coding agent and want to extend it with automation
When to graduate to a dedicated tool
- You need reliable weekly scheduling without depending on your machine being awake
- You want visual debugging and execution history
- Multiple non-technical team members need to modify the workflow
- You need error handling and retry logic beyond what a script provides
Because this skill uses the open Agent Skills standard, the same SKILL.md and script work across Claude Code, Cursor, and other compatible tools. The script itself is just Python — it runs anywhere.
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.