POST /api/ghl takes a path, method and body. It resolves the caller's credentials, injects the location id into the query string or body where the endpoint expects it, and forwards the call. An account with no CRM connected gets a structurally valid empty payload on reads and a 503 with code ghl_not_connected on writes.
Also called: API proxy · no keys in the browser · location injection
- 1Client posts { path, method, body }.
- 2The route authenticates and resolves per-org credentials.
- 3Location id is injected into empty location_id / locationId params, or added when absent — except for endpoints that already carry it in the path, which reject the query param.
- 4The upstream call is made with the Bearer token and version header.
- 5Non-2xx bodies are returned to the caller rather than swallowed, with the status attached.
The header states the multi-tenant reason: 'Per-org scoped: each tenant uses their own GHL location id + API key.' The empty-payload behaviour is a UX decision recorded there too — an unconnected tenant gets '{ contacts: [], opportunities: [] } for read paths and a 503 for write paths — keeps the dashboard rendering cleanly without spamming the user with errors.' The path-vs-query nuance is a real vendor quirk: endpoints like /locations/{id}/customFields 'REJECT a locationId query param ("property locationId should not exist")'.
- CRM credentials would otherwise have to be exposed to client code.
- An unconnected tenant saw error noise instead of an empty dashboard.
See it on your own jobs
Twenty minutes, your numbers, no slide deck. We’ll build one of your real buildings in front of you and send you the estimate link at the end — yours to keep either way.
or keep browsing features →