GoHighLevel's API rate limits are simple to state and easy to trip over: 100 requests per 10 seconds (the burst limit) and 200,000 requests per day (the daily limit), both measured per Marketplace app per resource, where a resource is a single location or company. Stay under those and you never think about them. Loop carelessly over a few thousand contacts and you'll hit the burst wall fast. This guide explains what the limits mean and, more usefully, how to build so you never hit them.
The trick isn't guessing how fast to go. GoHighLevel tells you exactly where you stand in the response headers of every call. Read those, and rate limiting stops being guesswork.
The two limits, precisely
Burst: up to 100 requests every 10 seconds, per app per resource. This is the one you hit during bulk operations.
Daily: up to 200,000 requests per day, per app per resource. This is generous for most apps and only becomes a concern for very large syncs or many locations.
"Per app per resource" matters. If your app is installed on 50 sub-accounts, each sub-account has its own budget, so the limits are per location, not shared across all of them. That's good news for scaling, as long as you don't accidentally fan out all 50 at once from a single burst window.
Read the headers instead of guessing
Every API response includes rate-limit headers. Use them and you never have to hardcode a magic delay:
X-RateLimit-Max: the max requests allowed in the current interval.X-RateLimit-Remaining: how many you have left in this burst window.X-RateLimit-Interval-Milliseconds: the length of the burst window.X-RateLimit-Limit-Daily: your daily ceiling.X-RateLimit-Daily-Remaining: how many requests you have left today.
A tiny wrapper around your HTTP client can watch these and slow down before you get blocked, rather than after:
async function ghlFetch(url, options) {
const res = await fetch(url, options);
const remaining = Number(res.headers.get('X-RateLimit-Remaining'));
const windowMs = Number(res.headers.get('X-RateLimit-Interval-Milliseconds')) || 10_000;
// Getting close to the burst limit? Pause until the window resets.
if (Number.isFinite(remaining) && remaining <= 2) {
await new Promise((r) => setTimeout(r, windowMs));
}
return res;
}
Handle the 429 you'll eventually get anyway
Even with pacing, you'll occasionally get a 429 Too Many Requests, especially if multiple parts of your app call the API at once. Treat 429 as normal, not exceptional: back off and retry with increasing delays.
async function withRetry(fn, tries = 5) {
for (let attempt = 0; attempt < tries; attempt++) {
const res = await fn();
if (res.status !== 429) return res;
// Exponential backoff: 1s, 2s, 4s, 8s... plus a little jitter.
const wait = 2 ** attempt * 1000 + Math.floor(Math.random() * 250);
await new Promise((r) => setTimeout(r, wait));
}
throw new Error('GHL API: still rate-limited after retries');
}
The jitter matters more than it looks. If ten workers all back off for exactly one second, they retry at exactly the same moment and hit the limit together again. A small random offset spreads them out.
Design so you make fewer calls
The best way to respect a rate limit is to need fewer requests in the first place.
Cap concurrency. For bulk jobs, process a handful of records at a time rather than firing off a thousand promises at once. A small worker pool of, say, five concurrent requests keeps you comfortably inside the burst window without babysitting delays.
Prefer webhooks over polling. Polling every location every minute to check for changes is the classic way to waste your daily budget. Subscribe to events instead and let GHL tell you when something changed. See How to Handle GoHighLevel Webhooks.
Cache what doesn't change often. Custom field definitions, pipeline stages, calendar configs: fetch them once, cache them, and refresh occasionally. Re-fetching static config on every operation is pure waste.
Spread scheduled jobs. If you run a nightly sync across many locations, stagger them instead of starting all at once. The limits are per resource, so processing locations in a rolling sequence keeps each one's budget intact.
The takeaway
Rate limits aren't an obstacle so much as a design constraint that pushes you toward a healthier integration: event-driven instead of polling, cached instead of repetitive, paced instead of bursty. Wrap your client to read the headers, retry 429s with jittered backoff, and cap concurrency on bulk jobs. Do that once and you'll forget the limits exist. For the full build context, see the GoHighLevel custom development guide.
Frequently asked questions
What are GoHighLevel's API rate limits?
100 requests per 10 seconds (burst) and 200,000 requests per day, each measured per Marketplace app per resource (a location or company).
Are the limits shared across all the accounts my app is installed on?
No. They're per resource, so each installed location has its own budget. Just avoid firing requests to many locations in the same burst window from one process.
How do I know how close I am to the limit?
Read the rate-limit headers on every response, especially X-RateLimit-Remaining and X-RateLimit-Daily-Remaining. Slow down when they get low instead of guessing at delays.
What should I do when I get a 429?
Back off and retry with exponential delays plus a little random jitter. Expect the occasional 429 under load and handle it as a normal case rather than an error.




