Technical7 min read

The GoHighLevel API: Auth, the Version Header, and the Limits That Bite

I lost an afternoon to a 401 that had nothing to do with my token. Here is what the GoHighLevel API actually wants from you, tested against the live endpoint.

Farhad Hossain, founder of GHL Spark
Farhad Hossain · Founder & Certified GoHighLevel Expert

In short

The GoHighLevel API v2 lives at services.leadconnectorhq.com. Every call needs a bearer token and a Version header, and leaving the header off returns a 401 that looks like an auth failure. Most jobs need a webhook, not the API.

I lost an afternoon to a 401 last year.

The token was right. I'd made it ten minutes earlier, pasted it carefully, checked it twice. Every call came back 401, so I did what everyone does and assumed the token was at fault, made a new one, got the same 401, read the scopes twice and then made a third one just to be sure.

The token was never the problem. I'd left off a header.

That is the single most common way people lose a day to the GoHighLevel API, and you won't find it in a tutorial, because tutorials show you the request that works, not the four that don't. So this piece is the opposite. Here's what the API turns down, what the errors really say, and when you shouldn't be calling it at all.

Everything below I tested against the live endpoint in October 2026.

Where does the GoHighLevel API actually live?

Two hosts, and only one of them matters now, though you'd never guess that from the number of tutorials still pointing at the old one.

API v2 runs at services.leadconnectorhq.com. The old v1 host, public-api.gohighlevel.com, still resolves but answers with a redirect. If you are starting today, build on v2 and don't look back. If you inherited a v1 script, it is on borrowed time.

The naming trips people first. The firm is HighLevel, the product is GoHighLevel, and the API host is LeadConnector, which is the white-label brand. Three names, one platform. Nothing is broken.

Why does a correct token still return 401?

Because v2 wants a Version header on every call, and it won't tell you that politely.

Call it with no headers at all and here is the literal response:

{"statusCode":401,"message":"version header was not found."}

Read that again. Status 401, note, which every coder on earth reads as "your login is wrong", for a thing that has nothing to do with your login. A 400 would have saved me three hours. It isn't a 400.

The header takes a date string:

Version: 2021-07-28

Most endpoints want 2021-07-28. A few older ones take 2021-04-15, and a handful of rebuilt ones want the plain string v3. No single value works everywhere. That's annoying. But at least it holds still from one month to the next, which is more than you can say for a lot of APIs at this end of the market.

So rule one. When you get a 401, read the message body before you lay a finger on the token.

Which token should you be using?

Two kinds, and people pick the hard one by default, usually because the OAuth docs are the first thing you find and they look official enough that you assume there's no simpler road. There is.

Private integration tokens are for your own scripts. You make one inside a location, it starts with pit-, and you send it as a bearer token. No redirect flow, no refresh, no app to sign up. Send a bad one and the API names it outright:

{"statusCode":401,"message":"Invalid Private Integration token"}

That message is a gift. It tells you the API knew what kind of token you meant, and turned down that exact thing.

OAuth is for apps other people install. You sign up in the marketplace, run the redirect flow, and get an access token plus a refresh token. HighLevel's docs put the access token at about a day. The refresh token rotates too, so each refresh hands you a new one and burns the old. Save it every time. Store the first one and keep replaying it and your app works fine for a day, then dies. Horrible bug to chase, because the gap hides the cause.

Here's the rule I use: if you're building for yourself or for one client, take the private token, and if you're building something strangers will install, it has to be OAuth and nothing else will do.

What are the rate limits, and how should you actually find out?

HighLevel's dev docs list a burst cap of 100 requests per 10 seconds and a daily cap of 200,000, counted per app for each location or company. Those numbers get repeated everywhere and I've no reason to doubt them.

But I couldn't open the official page to check them myself. It draws itself in the browser and serves nothing to a plain request. So every number in the last paragraph is second hand, and I'd rather say so than pretend.

Which leads to the advice that counts. Don't trust any blog's rate limit numbers, mine included. Read your own headers.

Every authenticated response carries them:

  • X-RateLimit-Max, the ceiling for the current window
  • X-RateLimit-Remaining, what is left in it
  • X-RateLimit-Interval-Milliseconds, how long the window runs
  • X-RateLimit-Limit-Daily and X-RateLimit-Daily-Remaining, the day's budget

Log those on every call for a week and you'll know your real limits, on your plan, today, which beats guessing from a table somebody wrote eighteen months ago. A turned-down call doesn't carry them, though. I checked, and a 401 comes back with no rate limit headers at all, so you can't probe the limits without a working token.

When you do hit the ceiling you get a 429. Back off and retry instead of hammering, and spread big jobs over minutes rather than firing them in a loop.

When should you not use the API at all?

This is the part I wish someone had told me before I sat down and wrote a polling script that fetched every contact every five minutes for a fortnight.

The API is for asking questions and changing things. Webhooks are for being told a thing happened. Most jobs people reach for the API to solve are webhook jobs in a wig.

Here's the test. If your script's first line is "every five minutes, fetch all contacts and look for new ones", stop, because you want a webhook and you're about to spend an entire day's rate limit finding out that nothing changed. Stop there.

Three jobs that really do need the API:

  • Pulling one report across many locations, because no webhook fires for "nothing happened"
  • Writing data in from a system GoHighLevel has never heard of
  • Moving records over in bulk, once

And one more group that needs neither. Anything a workflow already does inside the account. Before you write a line of code, check whether the platform does it already. I listed the common ones in GoHighLevel workflow automation use cases, and about half the "we need the API" conversations I have end there.

How do you test a call before you build anything?

Start with one request and no code at all.

Make a private token in the location you're in, then send one call from your terminal with the token and the Version header set. If it comes back 200, you have a working pair, and every later failure is your logic rather than your access. Two minutes. Saves hours.

Then read the first record it hands back and keep it. Not the whole thing. Just the shape, because the shape is what you code against, and it answers what no doc page will: which fields come back empty, which are IDs rather than names, and how dates are written.

One more habit worth having. Run that same call against a second location before you trust it, because fields, pipelines and calendars are all per location. A script that passes against one account and fails against the next is almost always field IDs, and finding that on day one costs nothing.

Keep a scratch file of calls that work. Mine holds about a dozen now, and most new jobs start life as a copy of an old one with two fields swapped.

What breaks once it is live?

Four things, in the order they've bitten me, and three of the four only show up weeks after you shipped and stopped watching.

A location gets cut off and the token quietly stops working. Your script keeps running and keeps failing, and nobody spots it for a fortnight, because nothing is on fire. Alert on failures in a row rather than on single ones.

Custom field IDs are not names. They're blank IDs, and they differ in every location, so a script that works for one client writes into nothing for the next. Map them per location at start-up.

Pagination is easy to get half right. Fetch page one, chew through it, and quietly never ask for page two. Loop until the reply says there's no more.

And time zones. The account has one, the location has one, your server has one, and the three of them only have to disagree once for a booking to land an hour out. You'll blame the calendar first. I did.

If the account underneath all this isn't set up cleanly in the first place, none of the integration work will hold. The setup order is in how to set up GoHighLevel.

And when the break is in the platform rather than in your code, going through GoHighLevel support is the faster route by a mile.

We build it, you own it

GoHighLevel is the software and HighLevel bills you for it. GHL Spark is a separate company. We build on the account and take nothing on the platform fee.

What we do is the plumbing: private tokens scoped right, webhooks pointed where they belong, field maps per location, retries and alerts so a dead token wakes somebody up, and the workflow side built in the app wherever that kills a script outright. The best build is the one you didn't have to write.

Where to go next

If you want the account built right before anything talks to it, our done-for-you GoHighLevel snapshots arrive with the structure, custom fields and workflows already in place.

Or bring the build you're planning to a free consultation and we'll tell you which half needs code and which half the platform already does.

Frequently asked questions

What is the GoHighLevel API base URL?
API v2 calls go to services.leadconnectorhq.com. The older v1 host, public-api.gohighlevel.com, now answers with a redirect. Build anything new on v2.
Why does the GoHighLevel API return 401 when my token is correct?
Usually because the Version header is missing. I tested this against the live endpoint in October 2026 and the response was "version header was not found" with a 401 status, which reads like a token problem but is not.
What is a GoHighLevel private integration token?
It is a token you create inside one location for your own scripts, and it starts with pit-. The API names it directly in errors, so a bad one comes back as "Invalid Private Integration token".
Do I need OAuth for the GoHighLevel API?
Only if you are building something other people install. For your own agency's scripts a private integration token is simpler, because there is no redirect flow and no refresh token to rotate.
What are the GoHighLevel API rate limits?
HighLevel's developer docs list a burst limit of 100 requests per 10 seconds and a daily limit of 200,000, counted per app per location. Read the X-RateLimit headers on your own responses rather than trusting any figure in a blog post, including this one.
Should I use the API or a webhook?
Use a webhook when GoHighLevel should tell you something happened. Use the API when you need to ask it a question or change data it holds. Most jobs people write API polling for are really webhook jobs.

About the author

Farhad Hossain, founder of GHL Spark

Farhad Hossain

Founder & Certified GoHighLevel Expert

Farhad is the founder of GHL Spark, where he builds and white-labels GoHighLevel SaaS platforms for agencies and SaaS operators. He writes about the parts of GoHighLevel that actually break in production — A2P registration, onboarding, support load and automation.

More from Farhad Hossain

Want this handled for you?

We set up, configure and white-label your GoHighLevel SaaS — so you can sell it instead of building it.

Fixed quote · No lock-in · Launch-ready in ~7 days