Error Medic

How to Fix HubSpot API Rate Limit (429) and Timeout Errors

Fix HubSpot API 429 Too Many Requests and 504 timeouts. Learn to implement exponential backoff, optimize bulk endpoints, and monitor API usage limits.

Last updated:
Last verified:
1,506 words
Key Takeaways
  • Root Cause 1: Exceeding the burst limit of 100 or 150 requests per 10 seconds, resulting in HTTP 429 Too Many Requests.
  • Root Cause 2: Inefficient API usage, such as looping over single-object updates instead of utilizing HubSpot's Bulk API endpoints.
  • Root Cause 3: Complex Search API queries or oversized payloads causing the server to exceed the 10-second processing window, resulting in HTTP 504 Gateway Timeouts.
  • Quick Fix: Implement a robust retry mechanism with exponential backoff and jitter, and reduce batch sizes for endpoints that frequently timeout.
Fix Approaches Compared
MethodWhen to UseTimeRisk
Exponential BackoffImmediate fix for intermittent 429 burst errors1-2 HoursLow
Bulk API MigrationHigh volume data syncs (e.g., thousands of contacts)1-2 DaysMedium
Caching Layer (Redis)Read-heavy integrations fetching unchanged data3-5 DaysHigh
Webhook SubscriptionsSyncing data changes from HubSpot to your system2-3 DaysMedium

Understanding the Error

When integrating with HubSpot, developers frequently encounter two major roadblocks that halt data synchronization: API Rate Limits (HTTP 429) and API Timeouts (HTTP 504/502).

The HTTP 429 Too Many Requests Error

HubSpot enforces strict API limits to maintain system stability. When you exceed these limits, HubSpot rejects the request and returns an HTTP 429 error. The error payload typically looks like this:

{
  "status": "error",
  "message": "You have reached your ten second limit.",
  "errorType": "RATE_LIMIT",
  "correlationId": "a1b2c3d4-e5f6-7890-abcd-1234567890ab"
}

There are two primary limits to be aware of:

  1. Burstly Limit (10-Secondly): Most accounts are limited to 100 or 150 requests per 10-second window per access token. This is the most common cause of 429 errors. A tight for loop creating contacts one by one will instantly trigger this.
  2. Daily Limit: Depending on the HubSpot subscription tier (Free, Starter, Professional, Enterprise) and whether you have the API Add-on, the daily limit ranges from 250,000 to 1,000,000 requests. Hitting this limit means your integration will be locked out until midnight Eastern Time (EST/EDT).

The HTTP 504 Gateway Timeout Error

While rate limits restrict the number of requests, timeouts occur when a single request takes too long to process. HubSpot drops connections that remain open for more than 10 seconds. If you are using the CRM Search API with deeply nested filter groups, or if you are sending a bulk request with 100 heavily populated records, the server may fail to process the batch in time, returning a 504 Gateway Timeout or 502 Bad Gateway.


Step 1: Diagnose the Bottleneck

Before refactoring your codebase, you must identify whether you are hitting the 10-second burst limit, the daily limit, or a timeout.

Inspecting Response Headers

Every successful API response from HubSpot includes specific headers that detail your current rate limit consumption. You should log these headers in your application:

  • X-HubSpot-RateLimit-Daily: Your total allotted daily requests.
  • X-HubSpot-RateLimit-Daily-Remaining: How many requests you have left for the day.
  • X-HubSpot-RateLimit-Secondly: Your 10-second burst limit (often labeled secondly, but evaluated over a rolling 10s window).
  • X-HubSpot-RateLimit-Secondly-Remaining: Remaining burst capacity.

If X-HubSpot-RateLimit-Daily-Remaining hits 0, you need architectural changes to reduce API consumption. If you are getting 429s but have plenty of daily limits left, you need a queuing or throttling mechanism.

Identifying Timeouts

If your logs show exceptions like ReadTimeoutError or HTTP status 504, inspect the specific endpoint. Timeouts are almost exclusively tied to:

  1. POST /crm/v3/objects/{objectType}/search (Complex queries)
  2. POST /crm/v3/objects/{objectType}/batch/update (Large batch sizes)

Step 2: Implement Exponential Backoff (The Quick Fix)

The most immediate and necessary fix for 429 errors is to implement exponential backoff. When your application receives a 429 status code, it should not fail immediately. Instead, it should pause, and retry the request. If it fails again, it should pause for a longer duration.

Why Exponential Backoff? If multiple threads or workers in your application hit the limit simultaneously and retry at the exact same time, they will cause a "thundering herd" problem, repeatedly triggering 429s. Exponential backoff adds a progressively longer delay between retries. Adding "jitter" (a random amount of milliseconds) ensures that retrying threads space out their requests.

See the code block below for a complete Python implementation using the tenacity library.


Step 3: Architecting for Scale (The Long-Term Fix)

Exponential backoff acts as a shock absorber, but it does not fix an inefficient integration. To resolve API rate limits and timeouts permanently, you must change how you interact with the HubSpot API.

1. Migrate to Bulk API Endpoints

Never use single-object endpoints (e.g., POST /crm/v3/objects/contacts) inside a loop. This is guaranteed to exhaust your 10-second limit. Instead, accumulate your records in memory and dispatch them using the Batch endpoints (e.g., POST /crm/v3/objects/contacts/batch/create).

  • Single Endpoint: 100 contacts = 100 API calls.
  • Batch Endpoint: 100 contacts = 1 API call.

By batching requests, you instantly reduce your API consumption by up to 99%.

2. Optimize Batch Sizes to Prevent Timeouts

While batching solves 429s, overly large batches cause 504 Timeouts. HubSpot's maximum batch size is typically 100 records. However, if your contacts have hundreds of custom properties, a batch of 100 might take 12 seconds to process, triggering a timeout.

The Fix: Dynamically reduce batch sizes. If a batch of 100 returns a 504 Timeout, your code should catch the exception, split the batch into two batches of 50, and retry.

3. Simplify CRM Search Queries

The CRM Search API is powerful but resource-intensive. To prevent search timeouts:

  • Avoid leading wildcards: Searching for *smith@example.com forces a full table scan.
  • Limit requested properties: Do not request properties=all or an array of 50 properties if you only need the email and hs_object_id. The more properties requested, the longer the database query takes.
  • Use targeted filters: Ensure your filter groups use indexed fields where possible.

4. Utilize Webhooks for Inbound Syncs

If you are continuously polling HubSpot (e.g., running a cron job every 5 minutes to check for updated deals), you are wasting API calls. Switch to HubSpot Webhooks. By registering a webhook subscription, HubSpot will push a payload to your server the millisecond a Deal changes stage, entirely eliminating the need for polling and drastically reducing your daily API usage.

Frequently Asked Questions

python
import requests
import logging
from tenacity import retry, wait_exponential, stop_after_attempt, retry_if_exception_type

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class HubSpotRateLimitError(Exception):
    pass

class HubSpotTimeoutError(Exception):
    pass

# Retry decorator: waits 2^x * 1 second between retries, up to 10 seconds, max 5 attempts
@retry(
    wait=wait_exponential(multiplier=1, min=2, max=10),
    stop=stop_after_attempt(5),
    retry=retry_if_exception_type((HubSpotRateLimitError, HubSpotTimeoutError))
)
def make_hubspot_request(url, headers, payload=None, method='GET'):
    logger.info(f"Making {method} request to {url}")
    
    try:
        if method == 'POST':
            response = requests.post(url, headers=headers, json=payload, timeout=15)
        else:
            response = requests.get(url, headers=headers, timeout=15)
            
        # Log remaining burst limit
        remaining = response.headers.get('X-HubSpot-RateLimit-Secondly-Remaining')
        if remaining:
            logger.debug(f"Remaining 10s quota: {remaining}")

        if response.status_code == 429:
            logger.warning("Hit 429 Rate Limit. Backing off...")
            raise HubSpotRateLimitError("Rate limit exceeded")
            
        if response.status_code in (502, 504):
            logger.warning("Hit Timeout. Server needs more time. Backing off...")
            raise HubSpotTimeoutError("Gateway timeout")
            
        response.raise_for_status()
        return response.json()
        
    except requests.exceptions.Timeout:
        logger.warning("Local request timeout. Backing off...")
        raise HubSpotTimeoutError("Local timeout")

# Example Usage
if __name__ == "__main__":
    TOKEN = "your_private_app_token"
    HEADERS = {
        "Authorization": f"Bearer {TOKEN}",
        "Content-Type": "application/json"
    }
    URL = "https://api.hubapi.com/crm/v3/objects/contacts"
    
    try:
        data = make_hubspot_request(URL, HEADERS)
        print("Successfully fetched contacts.")
    except Exception as e:
        print(f"Operation failed after retries: {e}")
E

Error Medic Editorial

The Error Medic Editorial team consists of senior Site Reliability Engineers and Integration Architects dedicated to solving complex API bottlenecks, database optimization challenges, and cloud infrastructure limits.

Sources

Related Articles in Hubspot Api

Explore More API Errors Guides