# Session Handoff — GHL OAuth Live + Test Sandbox (2026-07-30)

## Where we are

GHL marketplace app "Hermes1" is **fully installed and working** with auto-refreshing OAuth. A dedicated **Test Sandbox** sub-account exists with a location-level token (full read/write). All core endpoints verified. Skill `gohighlevel-ops` is fully up to date — read it first on pickup.

## Live infrastructure

- **OAuth handler:** `/root/.hermes/ghl-oauth/ghl_oauth_server.py` on 127.0.0.1:9120 (`ghl-oauth.service`, enabled). Uses `curl` subprocess for token exchange — Python urllib is Cloudflare-1010-blocked by TLS fingerprint, do NOT revert to urllib/requests for leadconnectorhq.com calls.
- **nginx:** `/ghl/` proxied from the 443 block of `/etc/nginx/sites-enabled/robblake.cloud`.
- **SSL:** real Let's Encrypt cert for robblake.cloud (certbot --nginx, auto-renews via certbot.timer). Expires 2026-10-28.
- **DNS:** robblake.cloud A record → 2.25.172.164 (added 2026-07-30 at Hostinger). Windows machines may still cache NXDOMAIN — `ipconfig /flushdns` + incognito, or hosts entry fallback.
- **Token store:** `/root/.hermes/secrets/ghl-oauth.json` (chmod 600). Health: `curl -s https://robblake.cloud/ghl/health`.

## Current token state — IMPORTANT

The handler stores **ONE token only** — last install wins. Currently holds the **Test Sandbox location token** (`userType: Location`, locationId `VLPUVcvUDOaXhgFPKiGl`). The **agency token was clobbered** by the sandbox install.

**Known limitation / next task candidate:** patch `ghl_oauth_server.py` to store multiple tokens keyed by locationId (dict instead of single JSON). Only do this if agency-level reads are actually needed — deferred deliberately.

## Sandbox inventory (Test Sandbox — VLPUVcvUDOaXhgFPKiGl)

| asset | id | notes |
|-------|-----|-------|
| sub-account | `VLPUVcvUDOaXhgFPKiGl` | created via SaaS-mode `POST /locations/` |
| admin user | `lM48EWUOOgCPX4Y18fDT` | "Test User" blake.rob1@gmail.com — added via GHL UI (gets real invite) |
| contact | `VwU4Qytvs6dZXmTluSnN` | "Sandbox Test" |
| conversation | `BhlakeDhvGNCvDFu2gVp` | |
| calendar | `JTMLSekqIHUi7wkzN1ii` | "Sandbox Booking" round-robin, 30min slots |
| pipeline | `835TmAdQpg8ZlXTS73JQ` | "Sandbox Sales" — New Lead / Contacted / Booked / Won |
| opportunity | `ybHauaXYEs9ZEy6xYMsW` | $1,500, New Lead stage |

## Open items (none blocking — pick up as needed)

1. **Multi-token handler patch** (see above) — only if agency access needed.
2. **App pricing stuck on "freemium"** — marketplace UI won't let Rob edit it. Consequence: direct oauth install links sometimes throw "Paid apps can only be installed within the platform." Workaround (proven): uninstall from sub-account, then use direct link while logged into AGENCY, pick location from chooser. If a fresh app is ever needed, create it with pricing=free from the start.
3. **RRR PIT still exists** in `/root/.hermes/secrets/ghl.env` — legacy but working; prefer OAuth for new work.
4. **Sandbox test data** is tagged `sandbox`/`demo` — safe to delete anytime, or leave as fixtures.

## How to use the token (from skill)

```bash
TOKEN=$(python3 -c "import json; print(json.load(open('/root/.hermes/secrets/ghl-oauth.json'))['access_token'])")
curl -s -H "Authorization: Bearer $TOKEN" -H "Version: 2021-07-28" -H "Accept: application/json" "https://services.leadconnectorhq.com/contacts/?locationId=VLPUVcvUDOaXhgFPKiGl"
```

## Pickup instructions

Start by reading skill `gohighlevel-ops` — it has the full runbook including tonight's 5 API write gotchas (calendar needs real userId, pipeline stages need position ints, calendar slugs globally unique, pipelines path is /opportunities/pipelines, one-token limitation). Then check `curl -s https://robblake.cloud/ghl/health` to confirm token is alive.
