# Wiring HubSpot into the Live Dashboard

The static dashboard at `index.html` calls `POST /api/hs` (a Cloudflare Pages
Function in `functions/api/hs.js`) which proxies to HubSpot using a
**Private App access token** stored as a Pages secret. This file walks you
through getting the token and storing it.

Setup is one-time. After this, every `./deploy.sh` keeps using the same token.

---

## 1. Create a HubSpot Private App

1. In HubSpot, top-right gear → **Settings**.
2. Left sidebar → **Integrations → Private Apps**.
3. **Create a private app**.
   - Name: `TalentCorps Exec Dashboard`
   - Description: `Read-only proxy for the executive dashboard`
4. **Scopes** tab. Tick the *read* scopes for everything the dashboard reads:

   **CRM:**
   - `crm.objects.deals.read`
   - `crm.objects.companies.read`  *(safe to add even if unused now)*
   - `crm.objects.contacts.read`   *(same)*
   - `crm.objects.owners.read`
   - `crm.schemas.deals.read`

   **Engagements (used by the activity scorecard):**
   - `sales-email-read`
   - `crm.objects.calls.read` *(if shown — sometimes bundled under Sales)*
   - **Or, simplest:** under "Standard" tick `crm.objects.engagements.read`
     if available; otherwise the granular ones above cover meetings, calls,
     notes, tasks, and emails.

   You only need *read* scopes — never write.

5. **Create app** → confirm. HubSpot shows the access token once.
   Copy it. It looks like `pat-na1-xxxxxxxx-xxxx-xxxx-...`.

---

## 2. Store the token as a Pages secret

In Terminal, from the project folder:

```sh
cd "/Users/bhunt/Library/CloudStorage/OneDrive-TalentCorps/Documents/Claude/Projects/Recruiting"
wrangler pages secret put HUBSPOT_TOKEN --project-name=talentcorps-recruiting
```

Paste the token when prompted, hit Enter. The token never lands on disk —
Cloudflare keeps it encrypted on the Pages project.

---

## 3. Redeploy

```sh
./deploy.sh
```

Open `https://talentcorps-recruiting.pages.dev/` in an incognito window.
Within a few seconds the KPI tiles, stage chart, exception report, and
manager scorecard should all populate. If you see an orange "HubSpot proxy
isn't configured yet" banner, the secret isn't being read — re-check the
project name in step 2 matches exactly.

---

## Rotating or replacing the token

```sh
# delete and re-add
wrangler pages secret delete HUBSPOT_TOKEN --project-name=talentcorps-recruiting
wrangler pages secret put    HUBSPOT_TOKEN --project-name=talentcorps-recruiting
./deploy.sh   # any deploy picks up the new secret
```

If a HubSpot admin disables the Private App (e.g. someone leaves), do the
above with a fresh app's token.

---

## Troubleshooting

| Symptom                                  | Fix                                                                |
| ---------------------------------------- | ------------------------------------------------------------------ |
| Banner: "HUBSPOT_TOKEN secret not set"   | Run the `wrangler pages secret put` command + redeploy.            |
| Tiles say `err` after loading            | Open browser DevTools → Console → look at `[hs N]` log entries. Most likely a missing scope. Add it on the Private App and retry. |
| "401" or "403" in console                | Token revoked, expired, or scope missing.                           |
| Activity scorecard rows are sparse        | The proxy caps each engagement type at 200 records (HubSpot's per-page limit). Tile totals are exact; per-rep is from the latest 200. Same as the Cowork artifact. |
| `405 Method Not Allowed` if you GET `/api/hs` directly | Expected — the proxy only accepts POST. Not a bug. |

---

## Why a Private App + Worker (vs. exposing the token in the page)

The HubSpot token is a long-lived, broad credential. We never ship it to the
browser. The Pages Function runs in Cloudflare's network with the token in
encrypted storage, accepts a small whitelist of `tool` names, and forwards
to HubSpot. The browser only ever sees the resulting deal/owner data —
which is the same data your sales team can already see in HubSpot.
