Error Medic

Fixing HubSpot API Rate Limit (429 Too Many Requests) and Timeout Errors

Comprehensive guide to resolving HubSpot API 429 Too Many Requests and timeout errors. Learn burst limits, batch optimization, and how to implement exponential

Last updated:
Last verified:
1,388 words
Key Takeaways
  • HubSpot enforces both a burst limit (e.g., 100-150 requests per 10 seconds) and a daily limit (250,000 to 1,000,000 requests) depending on your API tier and authentication method.
  • HTTP 429 'Too Many Requests' indicates you have hit a rate limit, while 504 or timeouts often result from overly large batch operations or unoptimized queries.
  • Implement exponential backoff with jitter to gracefully handle 429 responses without instantly exhausting your retries.
  • Use Batch APIs and proper pagination techniques to reduce the total number of outbound requests and mitigate network timeout risks.
Rate Limit Mitigation Strategies Compared
MethodWhen to UseImplementation TimeRisk of Dropped Data
Exponential BackoffStandard for all API integrations encountering 429 errors.LowLow
Batch API EndpointsWhen syncing large volumes of contacts, companies, or deals.MediumLow
Queue/Worker ArchitectureFor enterprise-grade integrations with asynchronous processing.HighVery Low
Upgrading API LimitWhen daily volume legitimately exceeds the current HubSpot tier.Low (Costly)None

Understanding HubSpot API Rate Limits

When integrating with HubSpot, encountering an HTTP 429 Too Many Requests error is a rite of passage for many developers. HubSpot uses rate limiting to ensure platform stability and equitable resource distribution across all tenants. Understanding the nuances of these limits—and the closely related timeout errors—is critical for building resilient applications.

The Anatomy of the Error

When you exceed HubSpot's API limits, the server responds with a 429 Too Many Requests status code. The response body typically looks like this:

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

There are two primary types of rate limits enforced by HubSpot:

  1. Burst Limits (Secondly/Ten-secondly limits): This restricts how many requests you can make in a short window. For OAuth apps, this is typically 100 requests per 10 seconds per account. For Private Apps, it's 150 requests per 10 seconds.
  2. Daily Limits: This is a hard cap on the total number of requests a portal can make in a 24-hour rolling window. This ranges from 250,000 for standard accounts up to 1,000,000 for API Add-on subscribers.

HubSpot API Timeouts (502 / 504)

Related to rate limits are timeout errors. When you try to circumvent rate limits by stuffing too much data into a single request (for example, batch updating 10,000 contacts at once without proper chunking), the HubSpot processing engine may take too long to respond, resulting in an HTTP 504 Gateway Timeout or a client-side connection timeout.

Timeouts typically occur because the payload size is too large or the database query generated by your request (such as a complex CRM Search API filter) is unoptimized.

Step 1: Diagnose the Bottleneck

Before refactoring your codebase, you must identify whether you are hitting a burst limit, a daily limit, or a timeout due to payload size.

Check the HTTP response headers returned by HubSpot. They provide critical debugging information:

  • X-HubSpot-RateLimit-Daily: Your total daily allotment.
  • X-HubSpot-RateLimit-Daily-Remaining: How many requests you have left today.
  • X-HubSpot-RateLimit-Secondly: Your burst limit.
  • X-HubSpot-RateLimit-Secondly-Remaining: Remaining requests in the current 10-second window.

If X-HubSpot-RateLimit-Secondly-Remaining is hitting 0, your application is too aggressive in a short timeframe. If you are seeing connection timeouts before receiving any headers, your payload or query complexity is the culprit.

Step 2: Implement Exponential Backoff

The most robust way to handle 429 Burst Limit errors is to implement an exponential backoff algorithm with jitter. When a 429 is received, the client pauses for a brief period before retrying. If the next request also fails, the pause duration increases exponentially.

Adding 'jitter' (a random variation to the sleep time) prevents the 'thundering herd' problem, where multiple blocked threads all wake up and retry at the exact same millisecond, immediately triggering another 429.

Step 3: Utilize Batch APIs

If you are iterating over an array of 500 contacts and making an individual PATCH /crm/v3/objects/contacts/{contactId} request for each one, you will inevitably hit the burst limit.

Instead, use HubSpot's Batch API endpoints (e.g., POST /crm/v3/objects/contacts/batch/update). Batch endpoints allow you to update up to 100 records in a single HTTP request. This effectively reduces your API call volume by a factor of 100, easily keeping you under the 100 requests / 10 seconds limit while processing thousands of records per minute.

Step 4: Optimize CRM Search and Timeouts

To resolve timeout errors during search or bulk retrieval:

  1. Reduce limit parameters: If you are asking for 100 records per page, drop it to 50 or 25.
  2. Limit returned properties: Only request the exact properties you need using the properties query parameter. Returning massive custom property datasets for every contact drastically increases processing time and payload size.
  3. Index consideration: Ensure your search filters are utilizing indexed fields where possible, and avoid leading wildcard searches in string matching.

Step 5: Architecture for Scale (Message Queues)

For enterprise environments syncing millions of rows, synchronous API calls will eventually fail. Transition to a Message Queue architecture (using RabbitMQ, AWS SQS, or Apache Kafka).

When a record needs updating, push a message to the queue. A background worker consumes from the queue at a strictly regulated pace (e.g., pulling messages and batching them into groups of 100, firing exactly 5 requests per second). This decoupled architecture guarantees you will never exceed the burst limit and provides a durable mechanism for retries in the event of daily limit exhaustion.

Frequently Asked Questions

python
import time
import random
import requests
from requests.exceptions import RequestException

# Advanced Exponential Backoff Implementation for HubSpot API

def hubspot_request_with_backoff(url, headers, payload=None, method='GET', max_retries=5):
    base_delay = 1.0  # Initial delay in seconds
    max_delay = 32.0  # Maximum delay between retries
    
    for attempt in range(max_retries):
        try:
            if method == 'GET':
                response = requests.get(url, headers=headers)
            elif method == 'POST':
                response = requests.post(url, headers=headers, json=payload)
            elif method == 'PATCH':
                response = requests.patch(url, headers=headers, json=payload)
                
            # Check for Rate Limit Error
            if response.status_code == 429:
                # HubSpot often returns a Retry-After header
                retry_after = response.headers.get('Retry-After')
                if retry_after:
                    sleep_time = float(retry_after)
                else:
                    # Calculate exponential backoff: (2^attempt) * base_delay
                    sleep_time = min(max_delay, base_delay * (2 ** attempt))
                    # Add jitter to prevent thundering herd
                    sleep_time += random.uniform(0, 0.5)
                
                print(f"Rate limit hit (429). Retrying in {sleep_time:.2f} seconds...")
                time.sleep(sleep_time)
                continue
                
            # Raise for other HTTP errors (500, 502, 504 timeouts)
            response.raise_for_status()
            return response.json()
            
        except requests.exceptions.Timeout:
            print("Request timed out. Consider reducing batch size or complexity.")
            sleep_time = min(max_delay, base_delay * (2 ** attempt)) + random.uniform(0, 1)
            time.sleep(sleep_time)
            
        except RequestException as e:
            print(f"Request failed: {e}")
            if attempt == max_retries - 1:
                raise
                
    raise Exception("Max retries exceeded")

# Example usage:
# headers = {"Authorization": "Bearer YOUR_PAT"}
# data = hubspot_request_with_backoff("https://api.hubapi.com/crm/v3/objects/contacts", headers)
E

Error Medic Editorial

Our team of seasoned Site Reliability Engineers and DevOps professionals brings decades of collective experience in diagnosing complex API integrations, database tuning, and cloud infrastructure optimization.

Sources

Related Guides