- Fix Guides
- How to Fix HubSpot API Rate Limit Exceeded Errors
How to Fix HubSpot API Rate Limit Exceeded Errors
Step-by-step fix guide with AI-powered diagnosis from BuildForce.
HubSpot returns a 429 response with the body code RATE_LIMITS when a private app or OAuth app crosses one of three limits: the per-second burst limit (default 100 req/10s for Pro+, 150 for Enterprise), the daily request cap (250k–1M depending on tier and add-ons), or the search-specific cap of 5 req/sec per endpoint. The fix is to read the X-HubSpot-RateLimit-Remaining and Retry-After response headers, queue requests behind a token-bucket limiter, batch where possible using the /crm/v3/objects/{object}/batch endpoints, and split high-throughput jobs across multiple private apps when a single integration legitimately exceeds the per-app cap.
Symptoms
Workflow actions or webhooks failing with HTTP 429 and body code 'RATE_LIMITS'
Search endpoints (/crm/v3/objects/{object}/search) failing intermittently while other endpoints succeed
Integration logs showing 'You have reached your daily limit' after a large backfill
Sync paused mid-run with X-HubSpot-RateLimit-Remaining reaching 0
Sporadic 429s only during business hours when other apps in the portal are active
Retry storms after a 429 making the problem worse instead of recovering
Root Causes
Burst limit exceeded (per-10-second window)
Private apps allow 100 requests per 10 seconds on Pro and 150 on Enterprise. A loop that fires 200 GETs in 2 seconds blows past the burst even though the daily total is fine. The burst window slides per token, not per portal.
Search API has its own stricter limit
/crm/v3/objects/{object}/search is capped at 5 requests per second per authentication token, independent of the 100/10s general limit. Polling large result sets via search instead of /v3/lists or /events/v3 hits this cap fast.
Daily limit consumed by inefficient queries
Single-record GETs in a loop (e.g., 50,000 contacts fetched one-by-one) burn the daily cap by mid-afternoon. The /batch/read endpoints accept up to 100 IDs per call and count as one request.
No Retry-After backoff on 429
Naive integrations retry immediately on 429, which keeps the bucket empty and prevents recovery. HubSpot returns a Retry-After header in seconds — ignoring it triggers cascading failures and IP-level throttling.
Multiple apps sharing one portal contend for the same daily cap
The daily request limit is per private app, but if several apps in the portal are doing large syncs simultaneously, the portal's effective throughput collapses. Operations Hub workflow actions also count against the portal limit.
How to Fix It — Step by Step
Read the rate-limit headers on every response
Every HubSpot API response includes X-HubSpot-RateLimit-Daily, X-HubSpot-RateLimit-Daily-Remaining, X-HubSpot-RateLimit-Interval-Milliseconds, X-HubSpot-RateLimit-Max, and X-HubSpot-RateLimit-Remaining. Log these on every response so you can spot exhaustion before a 429.
const remaining = parseInt(res.headers.get('X-HubSpot-RateLimit-Remaining') ?? '100', 10);
const dailyRemaining = parseInt(res.headers.get('X-HubSpot-RateLimit-Daily-Remaining') ?? '0', 10);
if (remaining < 10) await sleep(2000);Honor the Retry-After header on 429 responses
When you receive a 429, parse the Retry-After header (seconds) and wait at least that long before retrying. Combine with exponential backoff and jitter for multi-attempt retries to avoid synchronized retry storms across workers.
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get('Retry-After') ?? '10', 10);
const jitter = Math.random() * 1000;
await sleep(retryAfter * 1000 + jitter);
return retry(req);
}Replace single-record loops with batch endpoints
Use /crm/v3/objects/{objectType}/batch/read, /batch/create, /batch/update, and /batch/archive. Each batch call accepts up to 100 records and counts as one request against the burst and daily caps.
POST /crm/v3/objects/contacts/batch/read
{
"properties": ["email", "firstname", "lifecyclestage"],
"inputs": [{"id": "123"}, {"id": "456"}, /* up to 100 */]
}Throttle Search API calls to 4 req/sec per token
Treat /crm/v3/objects/{object}/search as a separate bucket. Stay at 4 req/sec to leave headroom for the 5/sec cap. For large result sets, prefer the /events/v3 endpoint or paginate with after cursors on standard list endpoints.
Split high-throughput integrations across multiple private apps
Each private app has its own per-second burst bucket. For a large backfill plus an active sync, create two private apps with the right scopes and route the long-running job through the second app so it doesn't starve the production sync.
Implement a token-bucket limiter at the integration boundary
Place a token-bucket limiter (95 tokens / 10s, refilled at 9.5/sec) in front of all HubSpot calls. This gives you headroom under the 100/10s cap and prevents bursty workloads from triggering 429s.
// Bottleneck.js style limiter
const limiter = new Bottleneck({
reservoir: 95,
reservoirRefreshAmount: 95,
reservoirRefreshInterval: 10_000,
maxConcurrent: 10
});Monitor daily-remaining and alert before exhaustion
Track X-HubSpot-RateLimit-Daily-Remaining in your observability stack. Alert when remaining drops below 20% of the daily cap so you can pause non-critical syncs before production webhooks start failing.
Let BuildForce diagnose and fix this automatically
Instead of following manual steps, connect your org and let our AI identify exactly what's broken and how to fix it — in minutes.
Book a DemoCommon Questions
More answers about this issue and how to resolve it.
Stop debugging manually. Let AI do it.
BuildForce runs 200+ automated checks across your Salesforce org and tells you exactly what's broken and how to fix it.