Resolving HubSpot API Rate Limit (429 Too Many Requests) & Timeout Errors
Fix HubSpot API rate limit (429) and timeout errors by implementing exponential backoff, request batching, and optimizing API call concurrency. Step-by-step gui
- HubSpot imposes strict rate limits: typically 100 requests per 10 seconds for standard accounts and 150 for API add-ons.
- Daily limits exist alongside burst limits; exceeding 500,000 requests/day (or higher with add-ons) requires architectural changes.
- Timeouts (504 Gateway Timeout or client-side) often occur when batching too many complex objects or hitting endpoint-specific bottlenecks.
- Quick fix: Implement exponential backoff with jitter and utilize HubSpot's batch APIs to consolidate requests.
| Method | When to Use | Implementation Time | Risk of Data Loss |
|---|---|---|---|
| Exponential Backoff | Always. Required for robust API integrations. | Low (1-2 hours) | Low |
| Request Batching | When syncing multiple contacts, companies, or deals at once. | Medium (1-2 days) | Low |
| Webhook Subscriptions | When polling for changes (e.g., pulling updated contacts every minute). | High (1 week) | Medium (requires reliable endpoint) |
| Concurrency Throttling | When running parallel workers or serverless functions. | Medium | Low |
Understanding the Error
When integrating with the HubSpot API, developers frequently encounter two related issues: Rate Limiting and Timeouts. Understanding the distinction and root causes is critical for building resilient integrations.
1. The 429 Too Many Requests Error
HubSpot enforces strict limits on how many API calls you can make within a specific timeframe. When you exceed this, the API responds with an HTTP 429 Too Many Requests status code. The response body usually looks like this:
{
"status": "error",
"message": "You have reached your minutely limit.",
"errorType": "RATE_LIMIT",
"correlationId": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"policyName": "MINUTELY"
}
HubSpot's Primary Limits:
- Burst Limit (10-second limit): 100 requests per 10 seconds (150 with API add-on).
- Daily Limit: 500,000 requests per day (up to 1,000,000 with add-ons).
2. The HubSpot API Timeout
Timeouts occur when your client closes the connection before HubSpot responds, or when HubSpot's gateway terminates a long-running request (often yielding a 504 Gateway Timeout or 502 Bad Gateway). This is distinct from a rate limit but often caused by similar architectural flaws, such as attempting to process massive payloads in a single synchronous call, or querying non-indexed properties.
Step 1: Diagnose the Bottleneck
Before writing code, identify exactly which limit you are hitting.
A. Inspect Response Headers
HubSpot returns rate limit context in the HTTP response headers of every successful (and 429) API call:
X-HubSpot-RateLimit-Daily: Your total daily quota.X-HubSpot-RateLimit-Daily-Remaining: How many daily calls you have left.X-HubSpot-RateLimit-Max: Your burst limit (per 10 seconds).X-HubSpot-RateLimit-Remaining: How many burst calls you have left in the current 10-second window.
B. Review the API Dashboard Navigate to your HubSpot App settings or the API usage dashboard in your portal to see visual spikes in errors and overall volume.
Step 2: Implement Exponential Backoff
The most critical fix for a 429 error is an exponential backoff retry mechanism. When a 429 is received, your application must pause before retrying, increasing the pause duration with each subsequent failure.
Why Jitter Matters: If you have 50 parallel workers that all hit a rate limit simultaneously, a standard 5-second backoff means all 50 workers will retry at exactly the same moment 5 seconds later, instantly triggering another 429. Adding 'jitter' (randomness) staggers the retries.
Step 3: Utilize Batch Operations
If you are creating 100 contacts using the POST /crm/v3/objects/contacts endpoint, that consumes 100 API calls. By switching to the batch endpoint POST /crm/v3/objects/contacts/batch/create, you can create up to 100 contacts in a single API call.
This immediately reduces your burst API consumption by 99%.
Common Batch Endpoints:
/crm/v3/objects/contacts/batch/create/crm/v3/objects/contacts/batch/update/crm/v3/objects/contacts/batch/read(For fetching multiple records by ID)
Step 4: Resolve HubSpot API Timeouts
If you are experiencing timeouts rather than rate limits, the strategy shifts to optimizing the workload of individual requests.
- Reduce Batch Sizes: If you are batching 100 complex objects with dozens of associations, HubSpot's backend might struggle to process it within the timeout window (typically 10-30 seconds). Reduce your batch size from 100 to 50, or even 25.
- Filter Complexity: When using the Search API (
POST /crm/v3/objects/contacts/search), avoid complexCONTAINSorHAS_PROPERTYfilters on custom properties that aren't indexed. Stick to exact matches (EQ) on standard identifiers whenever possible. - Increase Client Timeout: Ensure your HTTP client isn't giving up too early. Set your client timeout to at least 30 seconds for HubSpot API calls.
Step 5: Architecture Review (Polling vs. Webhooks)
If you are hitting your daily limit of 500,000 requests, you have an architectural problem. Often, this is caused by aggressive polling (e.g., asking HubSpot "Are there any new contacts?" every second).
The Fix: Switch to Webhooks. Configure a HubSpot App to subscribe to contact.creation or contact.propertyChange events. HubSpot will push the data to your server when an event occurs, completely eliminating the need for polling API calls.
Frequently Asked Questions
import time
import random
import requests
from requests.exceptions import RequestException
def hubspot_api_call_with_retry(url, headers, payload, max_retries=5):
"""
Executes a HubSpot API call with exponential backoff and jitter.
Handles 429 Too Many Requests and 502/504 Timeouts.
"""
base_delay = 1.0 # Initial delay in seconds
for attempt in range(max_retries):
try:
response = requests.post(url, headers=headers, json=payload, timeout=30)
# Success
if response.status_code in (200, 201, 202):
return response.json()
# Rate Limit Hit
elif response.status_code == 429:
# HubSpot sometimes provides a Retry-After header
retry_after = int(response.headers.get('Retry-After', 0))
if retry_after > 0:
sleep_time = retry_after
else:
# Exponential backoff: 1s, 2s, 4s, 8s...
sleep_time = base_delay * (2 ** attempt)
# Add jitter (0 to 1 second) to prevent thundering herd
sleep_time += random.uniform(0, 1)
print(f"[429 Rate Limit] Retrying in {sleep_time:.2f}s (Attempt {attempt + 1}/{max_retries})")
time.sleep(sleep_time)
continue
# Gateway Timeouts
elif response.status_code in (502, 503, 504):
sleep_time = base_delay * (2 ** attempt) + random.uniform(0, 1)
print(f"[{response.status_code} Timeout] Retrying in {sleep_time:.2f}s (Attempt {attempt + 1}/{max_retries})")
time.sleep(sleep_time)
continue
# Other errors (400, 401, etc.) should not be retried blindly
else:
response.raise_for_status()
except requests.exceptions.Timeout:
sleep_time = base_delay * (2 ** attempt) + random.uniform(0, 1)
print(f"[Client Timeout] Retrying in {sleep_time:.2f}s (Attempt {attempt + 1}/{max_retries})")
time.sleep(sleep_time)
continue
except RequestException as e:
print(f"Request failed: {e}")
raise
raise Exception("Max retries exceeded for HubSpot API call.")
# Example usage for batch creation
# headers = {"Authorization": "Bearer YOUR_TOKEN", "Content-Type": "application/json"}
# payload = {"inputs": [{"properties": {"email": "test1@example.com"}}, {"properties": {"email": "test2@example.com"}}]}
# result = hubspot_api_call_with_retry("https://api.hubapi.com/crm/v3/objects/contacts/batch/create", headers, payload)Error Medic Editorial
Error Medic Editorial comprises senior SREs, DevOps engineers, and cloud architects dedicated to documenting robust solutions for enterprise API integrations and infrastructure reliability.