{"slug":"private-api","title":"GHL Private API — backend.leadconnectorhq.com","tags":["api","workflows","automation","playwright"],"agent_summary":"How to discover and use GHL's undocumented private API at backend.leadconnectorhq.com to perform workflow CRUD operations unavailable in the public API.","trigger_phrases":["ghl workflow api","clone workflow","private api","backend.leadconnectorhq","ghl automation","workflow replication","ghl undocumented api"],"runnable":true,"markdown":"\n# GHL Private API — `backend.leadconnectorhq.com`\n\n## The Discovery\nCodex found it via Playwright. Logged into GHL in a real browser, dumped storage state JSON (cookies + localStorage), then replayed requests programmatically. The app talks to `backend.leadconnectorhq.com` — **not** the documented `services.leadconnectorhq.com` / `rest.gohighlevel.com` public API.\n\nThis endpoint exposes everything the UI can do, including things the public API explicitly does not: **workflow CRUD, trigger editing, action insertion, version control**.\n\n## Why It Matters\nPublic API can't create or modify workflows. You clone by hand in the UI, every sub-account, every time. Private API means one-shot workflow replication across 50+ sub-accounts from a script.\n\n## How To Use It\n\n### 1. Capture auth state\n```python\nfrom playwright.sync_api import sync_playwright\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch(headless=False)\n    ctx = browser.new_context()\n    page = ctx.new_page()\n    page.goto(\"https://app.gohighlevel.com\")\n    input(\"Log in manually, then press Enter...\")\n    ctx.storage_state(path=\"ghl_auth.json\")\n```\n\n### 2. Replay requests\nOpen DevTools → Network on the GHL workflow builder. Filter `backend.leadconnectorhq.com`. Copy as cURL. Convert to requests/httpx with headers + cookies from storage state.\n\nKey headers:\n- `token-id` — bearer from localStorage\n- `channel: APP`\n- `source: WEB_USER`\n- `version: 2021-07-28`\n- `location-id` — target sub-account\n\n### 3. Workflow endpoints\n- `GET  /workflows/?locationId={id}` — list\n- `GET  /workflows/{workflowId}` — full definition\n- `POST /workflows/` — create\n- `PUT  /workflows/{workflowId}` — update\n- `POST /workflows/{workflowId}/publish` — publish version\n\nPayload schema mirrors what DevTools shows. Diff two workflows in the UI to learn the field shape.\n\n## Risks\n- Unofficial. GHL can change or lock it.\n- Tokens expire — refresh via storage-state reload.\n- Rate limits exist but are undocumented. Throttle to ~1 req/sec.\n\n## Lineage\nDiscovered on stream via Codex (GPT-5 coding harness). Claude Code took over for consistency once the endpoint was mapped.\n","html":"<h1>GHL Private API — <code>backend.leadconnectorhq.com</code></h1>\n<h2>The Discovery</h2>\n<p>Codex found it via Playwright. Logged into GHL in a real browser, dumped storage state JSON (cookies + localStorage), then replayed requests programmatically. The app talks to <code>backend.leadconnectorhq.com</code> — <strong>not</strong> the documented <code>services.leadconnectorhq.com</code> / <code>rest.gohighlevel.com</code> public API.</p>\n<p>This endpoint exposes everything the UI can do, including things the public API explicitly does not: <strong>workflow CRUD, trigger editing, action insertion, version control</strong>.</p>\n<h2>Why It Matters</h2>\n<p>Public API can't create or modify workflows. You clone by hand in the UI, every sub-account, every time. Private API means one-shot workflow replication across 50+ sub-accounts from a script.</p>\n<h2>How To Use It</h2>\n<h3>1. Capture auth state</h3>\n<pre><code class=\"language-python\">from playwright.sync_api import sync_playwright\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch(headless=False)\n    ctx = browser.new_context()\n    page = ctx.new_page()\n    page.goto(\"https://app.gohighlevel.com\")\n    input(\"Log in manually, then press Enter...\")\n    ctx.storage_state(path=\"ghl_auth.json\")\n</code></pre>\n<h3>2. Replay requests</h3>\n<p>Open DevTools → Network on the GHL workflow builder. Filter <code>backend.leadconnectorhq.com</code>. Copy as cURL. Convert to requests/httpx with headers + cookies from storage state.</p>\n<p>Key headers:</p>\n<ul>\n<li><code>token-id</code> — bearer from localStorage</li>\n<li><code>channel: APP</code></li>\n<li><code>source: WEB_USER</code></li>\n<li><code>version: 2021-07-28</code></li>\n<li><code>location-id</code> — target sub-account</li>\n</ul>\n<h3>3. Workflow endpoints</h3>\n<ul>\n<li><code>GET  /workflows/?locationId={id}</code> — list</li>\n<li><code>GET  /workflows/{workflowId}</code> — full definition</li>\n<li><code>POST /workflows/</code> — create</li>\n<li><code>PUT  /workflows/{workflowId}</code> — update</li>\n<li><code>POST /workflows/{workflowId}/publish</code> — publish version</li>\n</ul>\n<p>Payload schema mirrors what DevTools shows. Diff two workflows in the UI to learn the field shape.</p>\n<h2>Risks</h2>\n<ul>\n<li>Unofficial. GHL can change or lock it.</li>\n<li>Tokens expire — refresh via storage-state reload.</li>\n<li>Rate limits exist but are undocumented. Throttle to ~1 req/sec.</li>\n</ul>\n<h2>Lineage</h2>\n<p>Discovered on stream via Codex (GPT-5 coding harness). Claude Code took over for consistency once the endpoint was mapped.</p>\n"}