title: GHL OAuth Private App — Permanent Sub-Account Access Fix

summary: Private Integration tokens CANNOT use POST /oauth/locationToken — they lack oauth.write scope. ONLY OAuth Marketplace Apps (public or private) can exchange agency tokens for location tokens. Private App requires: no fee, no marketplace review, 5-agency limit (irrelevant for Genesis). MANDATORY scopes: oauth.readonly + oauth.write.

root_cause: Private Integration Tokens are static API keys that cannot hold oauth.write scope, which is required for the /oauth/locationToken endpoint. This endpoint is ONLY available to apps registered on marketplace.gohighlevel.com.

solution: Create Private App on marketplace.gohighlevel.com → Agency distribution → request oauth.readonly + oauth.write + 21 resource scopes → install on Genesis agency → exchange for per-location tokens

blocking: MARKETPLACE_APP_REGISTRATION — need Client ID + Client Secret from marketplace.gohighlevel.com


title: Private Integration Tokens Cannot Exchange Location Tokens

axiom: GHL Private Integration Tokens (pit-*) CANNOT access sub-account resources via the public API. The POST /oauth/locationToken endpoint requires oauth.write scope which ONLY Marketplace Apps can hold. This is documented in GHL changelog 'Create Sub-Account Access Tokens Using Agency Access Tokens' — the feature is SPECIFICALLY for Apps with oauth.write and oauth.readonly scopes, distribution type Agency.

source: GHL official docs + 4 months of production failures

never_again: Never attempt to use Private Integration tokens for cross-sub-account operations. Always use OAuth Marketplace App flow.


title: GHL OAuth Token Lifecycle — Complete Reference

access_token_ttl: 86399 seconds (~24 hours)

refresh_token_ttl: 365 days

token_rotation: MANDATORY — new refresh_token issued on every refresh, old one invalidated

location_token_exchange: POST https://services.leadconnectorhq.com/oauth/locationToken with Bearer agency_token + companyId + locationId

refresh_endpoint: POST https://services.leadconnectorhq.com/oauth/token with grant_type=refresh_token + user_type=Company